This question already has an answer here:
WPF ComboBox bind itemssource to different datacontext in MVVM
(1 answer)
Closed 4 years ago.
I Need a collection of ComboBoxes with a common collection of possible selections.
Codebehind excerpt:
namespace ComboBoxesInCollection
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
D = new DataContainer();
this.DataContext = D;
}
private DataContainer D;
}
public class DataContainer
{
public ObservableCollection<Item> ComboboxItems
{
get { return comboboxItems; }
}
public ObservableCollection<Selection> ComboboxSelections
{
get { return comboboxSelections; }
}
public DataContainer()
{
comboboxItems = new ObservableCollection<Item>
{
new Item(100, "Entry #1"),
new Item(101, "Entry #2"),
new Item(102, "Entry #3")
};
comboboxSelections = new ObservableCollection<Selection>()
{
new Selection(1),
new Selection(2),
new Selection(3)
};
}
private ObservableCollection<Item> comboboxItems;
private ObservableCollection<Selection> comboboxSelections;
}
}
XAML excerpt:
<Window.Resources>
<DataTemplate x:Key="CSTemplate">
<Border BorderBrush="Black" BorderThickness="1,1,0,0" Margin="0,0,0,4" Padding="4">
<StackPanel>
<Label Content="{Binding Id}"/>
<ComboBox
ItemsSource="{Binding ComboboxItems}" //<= does not work
DisplayMemberPath="Name"
SelectedValue="{Binding SelectedId}"
SelectedValuePath="Id"
/>
</StackPanel>
</Border>
</DataTemplate>
...
<ItemsControl ItemsSource="{Binding ComboboxSelections}" ItemTemplate="{StaticResource CSTemplate}"/>
The ItemsControl shows the items, but the Combobox is empty.
I know that i try to access a property/collection inside of a Selection that is not existing right now.
How would i correctly specify the DataBinding so i can see the items?
Used Element Binding to access the DataContext Property of the window.
First Name the Window x:Name="Window1" and the while binding, use element binding.
ItemsSource="{Binding ElementName=Window1, Path=DataContext.ComboboxItems}"
XAML of whole window
<Window x:Class="WpfApp6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp6"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" x:Name="Window1">
<Window.Resources>
<DataTemplate x:Key="CSTemplate">
<Border BorderBrush="Black" BorderThickness="1,1,0,0" Margin="0,0,0,4" Padding="4">
<StackPanel>
<Label Content="{Binding Id}"/>
<ComboBox
ItemsSource="{Binding ElementName=Window1, Path=DataContext.ComboboxItems}"
DisplayMemberPath="Name"
SelectedValue="{Binding SelectedId}"
SelectedValuePath="Id"
/>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding ComboboxSelections}" ItemTemplate="{StaticResource CSTemplate}" />
</Grid>
Related
This question already has answers here:
DataGridTemplateColumn Two way binding is not working
(2 answers)
Closed 3 years ago.
I have a lilltle issue using Dependency Property for binding data;
I have a user control which contains a textBox. I created a DataGrid, in which I added the usercontrol for one of its columns. Then i did a binding using dataContext.
When I first run the apllication, the usercontrol is filled with data (Data binded to a specific field 'name' in my case), But when I modify the value, the new value is not written, it still holds the old value.
TT.xaml.cs
namespace WPF_Prj.View
{
public partial class TT : UserControl
{
public float MyText
{
get {
return (float)GetValue(MyTextProperty);
}
set {
SetValue(MyTextProperty, value );
}
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(float), typeof(TT), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });
public TT()
{
InitializeComponent();
}
}
}
TT.xaml
<UserControl x:Class="WPF_Prj.View.TT"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Height="20" Width="100"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl},
Path=MyText,
UpdateSourceTrigger=PropertyChanged}">
</TextBox>
</Grid>
mainWindow.xaml
<Window x:Class="WPF_Prj.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:local="clr-namespace:WPF_Prj.View"
Title="[Portfolio] MainWindow" Height="500" Width="800">
<StackPanel Orientation="Vertical">
<DataGrid x:Name="datagrid" AutoGenerateColumns="False" CanUserAddRows="False"
Width="Auto" Margin="5,5,0,5" HorizontalAlignment="Left" CellEditEnding="datagrid_CellEndEditing">
<DataGrid.Columns>
<DataGridTextColumn Header="" IsReadOnly="True" Width="1"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name, Mode=TwoWay, NotifyOnTargetUpdated=True}" IsReadOnly="True" Width="Auto"/>
<DataGridTextColumn Header="Owned Qty" Binding="{Binding OwnedQty, Mode=TwoWay, NotifyOnTargetUpdated=True}" IsReadOnly="True" Width="Auto"/>
<DataGridTemplateColumn Header="Ordered/Eligible">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:TT x:Name="test" MyText="{Binding OwnedQty, Mode=TwoWay}"></local:TT>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
in the mainWindow.xaml.cs I get data from a ViewModel class which implements ObservableCollection and the Model is a class implementing INotifyPropertyChanged
So in the picture below : the green box is what is loaded from my DB to the grid, in the red is my UserControl which hold and edit, at first it contains the values loaded, but when I change it to some other values, the values in the Green still unchanged.
This fixes it for me. You don't need Mode=TwoWay; it's already that by default in the dependency property definition. You do need UpdateSourceTrigger=PropertyChanged, whether that's in the DP definition or not.
<DataTemplate>
<local:TT
x:Name="test"
MyText="{Binding OwnedQty, UpdateSourceTrigger=PropertyChanged}"
></local:TT>
</DataTemplate>
I would expect to be able to do the same thing by initializing DefaultUpdateSourceTrigger in the Dependency Property definition, but this does not have the same effect. I don't know why that is the case.
Note that I also changed the default value to 0F; null will throw an exception.
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(float), typeof(TT),
new FrameworkPropertyMetadata(0F) {
BindsTwoWayByDefault = true
// Nope.
//, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
This is my viewmodel property. I'm putting a breakpoint on OnPropertyChanged() to detect when the property is updated by the binding.
private float _ownedQty = 0F;
public float OwnedQty
{
get { return _ownedQty; }
set
{
if (value != _ownedQty)
{
_ownedQty = value;
OnPropertyChanged();
}
}
}
I've 4 different collections. Currently I display thoses 4 collections, but I can only have one element selected at the time.
The view where I display the 4 collection:
<UserControl x:Class="xxx.yyy.vvv.Menu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:xxx.yyy.vvv.Menu"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="450">
<Grid Name="RootContainer">
<Grid.DataContext>
<local:MenuViewModel/>
</Grid.DataContext>
<ItemsControl ItemsSource="{Binding Collection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CollectionControl Collection="{Binding}" SelectedElement="{Binding Path=DataContext.GlobalSelectedElement,ElementName=RootContainer, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
This is bound to a ViewModel:
public class MenuViewModel : SomeBaseViewModelThatHandleTheNotify
{
public IMyElement GlobalSelectedElement
{
get => GetValue<IMyElement>();
set => SetValue(value); //I NEVER COME HERE!!!)
}
public SomeCollectionContainer Collection
{
get => GetValue<SomeCollectionContainer>();
set => SetValue(value);
}
}
My sub control, has a dependency property, which is changed when the internal ViewModel of the UserControl is changed.
public IMyElement SelectedElement
{
get { return (IMyElement)GetValue(SelectedElementProperty); }
set { SetValue(SelectedElementProperty, value);/*HERE I COME!*/ }
}
public static readonly DependencyProperty SelectedElementProperty =
DependencyProperty.Register("SelectedElement", typeof(IMyElement), typeof(CollectionControl), new PropertyMetadata(null, OnSelectedElementChanged));
private static void OnSelectedElementChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
//Retrieve the sub control ViewModel and set the property
SubControlViewModel subControlViewModel = (SubControlViewModel)((CollectionControl)dependencyObject).RootContainer.DataContext;
subControlViewModel.SelectedElement = (IMyElement)dependencyPropertyChangedEventArgs.NewValue;
}
//In the constructor, I register to PropertyChanged of the ViewModel, and I set the SelectedElement when it change.
So, basically, I come in the SetValue of the dependency property of the UserControl, but I never come in the GlobalSelectedElement property of my main ViewModel.
What did I miss?
EDIT
I tried to directly use two-way binding between my ViewModel and the Dependency Property, doesn't work either:
In my sub control:
<UserControl x:Class="xxx.yyy.vvv.CollectionControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:xxx.yyy.vvv.Menu"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Name="RootContainer" Orientation="Vertical">
<StackPanel.DataContext>
<local:CollectionControlViewModel/>
</StackPanel.DataContext>
<Label Content="{Binding Collection.Name}" Margin="5,0,0,0" />
<ListBox ItemsSource="{Binding Collection.Items}" HorizontalContentAlignment="Stretch" Padding="0" BorderThickness="0" SelectedItem="{Binding SelectedElement, RelativeSource={RelativeSource AncestorType={x:Type local:CollectionControl}}, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</StackPanel>
</UserControl>
I feel that my UserControl DependencyProperty is bound from 2 sides
I've tried to make a small diagram to show my classes.
So my CollectionControl.SelectedElement are correctly set, but the MenuViewModel.SelectedItem are not.
Try to bind to the DataContext of the parent ItemsControl using a RelativeSource:
<local:CollectionControl Collection="{Binding}"
SelectedElement="{Binding Path=DataContext.GlobalSelectedElement, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=TwoWay}"/>
Obviously using an ElementName doesn't work. This is because of namescopes. The CollectionControl element in the ItemTemplate is not in the same namescope as "RootContainer".
I'm completely new to WPF so please apologise any "stupid" mistakes.
I have a datagrid with only one column, that is a combobox. The datagrid shows a new empty line as expected. But if I select a value in the combobox on the new line, no additional new row is added. I already tried to add an edit template according to this answer: datagrid showing one new row, but not any subsequent but that did no help.
<Window x:Class="WPFSpielplatz.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="clr-namespace:WPFSpielplatz"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<d:MainWindowViewModel />
</Window.DataContext>
<Grid>
<StackPanel HorizontalAlignment="Left" Height="319" VerticalAlignment="Top" Width="517">
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GroceryItems}" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Combo">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.GroceryItemTypes}" SelectedItem="{Binding GroceryItemType, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" ></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.GroceryItemTypes}" SelectedItem="{Binding GroceryItemType, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" ></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Grid>
My ViewModel:
using System.Collections.ObjectModel;
using System.ComponentModel;
using WPFSpielplatz.Annotations;
namespace WPFSpielplatz
{
public class MainWindowViewModel:INotifyPropertyChanged
{
private ObservableCollection<GroceryItem> _groceryItems;
public MainWindowViewModel()
{
GroceryItemTypes = new ObservableCollection<GroceryItemType>
{
new GroceryItemType("Food"),
new GroceryItemType("Non-Food")
};
_groceryItems=new ObservableCollection<GroceryItem>
{
new GroceryItem(){GroceryItemType=GroceryItemTypes[0]},
new GroceryItem(){GroceryItemType=GroceryItemTypes[1]}
};
}
public ObservableCollection<GroceryItem> GroceryItems
{
get { return _groceryItems; }
set
{
_groceryItems = value;
OnPropertyChanged("GroceryItems");
}
}
public ObservableCollection<GroceryItemType> GroceryItemTypes { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The "domain" classes:
GroceryItem:
namespace WPFSpielplatz
{
public class GroceryItem
{
public GroceryItemType GroceryItemType { get; set; }
public GroceryItem()
{
}
}
}
GroceryItemType:
namespace WPFSpielplatz
{
public class GroceryItemType
{
public GroceryItemType()
{
}
public GroceryItemType(string name)
{
Name = name;
}
public string Name { get; set; }
}
}
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GroceryItems}"/>
is simply enough for binding your collection to the DataGrid. It is already relative, however you just need to bind your properties of the GroceryItem class to your DataGrid as follows:
<ComboBox ItemsSource="{Binding GroceryItemType.Name, Mode=TwoWay"}/>
This will insert the name for every GroceryItemType to the associated GroceryItem in a ComboBox. You will probably end up in having just the name of the GroceryItemType in the ComboBox since you did specify this as your ItemsSource. I don't think that is what you want but for now, I don't see any more code in order to understand, what you want to archive.
Please note that you will have to implement INotifyPropertyChanged in both Models (GroceryItem and GroceryItemType) if you want to reflect the changes to the ViewModel and vice versa. This is where the Mode=TwoWay comes in handy.
Ok, I solved it. It worked all the time, the problem was, that my <DataGridTemplateColumn.CellTemplate> and my <DataGridTemplateColumn.CellEditingTemplate> looked the same so I could change values in the combobox without ever entering editing mode. But only after entering and successfully leaving editing mode a new row is added to the datagrid.
To get this working you have to click multiple times in the cell you want to edit and then tab out of it for the new row to be added.
So the updated code for the View looks like this:
<Window x:Class="WPFSpielplatz.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="clr-namespace:WPFSpielplatz"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<d:MainWindowViewModel />
</Window.DataContext>
<Grid>
<StackPanel HorizontalAlignment="Left" Height="319" VerticalAlignment="Top" Width="517">
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GroceryItems}" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Combo">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=GroceryItemType.Name}" ></TextBlock>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>Edit</TextBlock>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.GroceryItemTypes}" SelectedItem="{Binding GroceryItemType, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" ></ComboBox>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Grid>
I have a WPF user control that contains a DataGrid. This DG contains several columns including a ComboBox for states. The list of states is populated and stored as a property in my ViewModel.
I am trying to bind the StateList Property to the ItemsSource of my Combobox but when I run the form and try to edit the DG, the combobox does not contain any values, the combobox is empty.
Here is the XAML for the usercontrol.
<UserControl x:Class="myproject.View.ucContactView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="475" d:DesignWidth="977">
<UserControl.Resources>
<ResourceDictionary Source="/Templates/MyResourceDictionary.xaml"/>
</UserControl.Resources>
<Grid DataContext="{Binding ViewModel}">
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding AddressCollectionViewSource.View}">
<DataGridTemplateColumn Header="State" Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding StateDescription}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox Name="cboState"
SelectedValuePath="StateKey"
ItemTemplate="{StaticResource dtStateTemplate}"
ItemsSource="{Binding StateList}"
SelectedItem="{Binding StateKey, Mode=TwoWay}"
Width="100" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid>
</Grid>
</UserControl>
The odd thing is that if I create another combobox on this usercontrol with the exact same combobox, this combobox works as expected.
<!-- this works as long as it's not in the DG -->
<StackPanel Height="126" HorizontalAlignment="Left" Margin="766,275,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="200" >
<ComboBox Name="cboState2"
SelectedValuePath="StateKey"
ItemTemplate="{StaticResource dtStateTemplate}"
ItemsSource="{Binding StateList}"
SelectedItem="{Binding StateKey, Mode=TwoWay}"
Width="100" />
</StackPanel>
Why won't the combobox in the DG display the values from the StateList property? Any why does the separate combobox work properly?
It's not working because your ComboBox is looking for StateList as a property of the DataContext of the DataGrid. That is, it's trying to bind to ViewModel.AddressCollectionViewSource.View.StateList when it needs to be binding to ViewModel.StateList. Check your output window while debugging and I bet you'll see a binding error to the effect of Could not find property StateList on object AddressCollectionViewSource (or maybe ICollection).
Try this instead:
<ComboBox Name="cboState2"
SelectedValuePath="StateKey"
ItemTemplate="{StaticResource dtStateTemplate}"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.StateList}"
SelectedItem="{Binding StateKey, Mode=TwoWay}"
Width="100" />
if your viewmodel is a property at the window you can do this
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=ViewModel.StateList, Mode=OneWay}"
<Window x:Class="WpfStackOverflowSpielWiese.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2"
Height="300"
Width="300"
x:Name="window">
<Grid DataContext="{Binding ElementName=window, Path=ViewModel}">
<DataGrid x:Name="grid"
AutoGenerateColumns="False"
ItemsSource="{Binding AddressCollectionViewSource, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="State"
Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding StateKey}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox Name="cboState"
SelectedValuePath="StateKey"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=ViewModel.StateList, Mode=OneWay}"
SelectedItem="{Binding StateKey, Mode=TwoWay}"
Width="100" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfStackOverflowSpielWiese
{
/// <summary>
/// Interaction logic for Window2.xaml
/// </summary>
public partial class Window2 : Window
{
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(ViewModelClass), typeof(Window2), new PropertyMetadata(default(ViewModelClass)));
public ViewModelClass ViewModel {
get { return (ViewModelClass)this.GetValue(ViewModelProperty); }
set { this.SetValue(ViewModelProperty, value); }
}
public Window2() {
this.InitializeComponent();
this.grid.Items.Clear();
this.ViewModel = new ViewModelClass();
}
}
public class StateClass : DependencyObject
{
public static readonly DependencyProperty StateKeyProperty =
DependencyProperty.Register("StateKey", typeof(string), typeof(ViewModelClass), new PropertyMetadata(default(string)));
public string StateKey {
get { return (string)this.GetValue(StateKeyProperty); }
set { this.SetValue(StateKeyProperty, value); }
}
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(string), typeof(StateClass), new PropertyMetadata(default(string)));
public string State {
get { return (string)this.GetValue(StateProperty); }
set { this.SetValue(StateProperty, value); }
}
}
public class ViewModelClass : DependencyObject
{
public static readonly DependencyProperty StateListProperty =
DependencyProperty.Register("StateList", typeof(ObservableCollection<string>), typeof(ViewModelClass), new PropertyMetadata(default(ObservableCollection<string>)));
public static readonly DependencyProperty AddressCollectionViewSourceProperty =
DependencyProperty.Register("AddressCollectionViewSource", typeof(ObservableCollection<StateClass>), typeof(ViewModelClass), new PropertyMetadata(default(ObservableCollection<StateClass>)));
public ObservableCollection<StateClass> AddressCollectionViewSource {
get { return (ObservableCollection<StateClass>)this.GetValue(AddressCollectionViewSourceProperty); }
set { this.SetValue(AddressCollectionViewSourceProperty, value); }
}
public ObservableCollection<string> StateList {
get { return (ObservableCollection<string>)this.GetValue(StateListProperty); }
set { this.SetValue(StateListProperty, value); }
}
public ViewModelClass() {
this.StateList = new ObservableCollection<string>(new[] {"one", "two"});
this.AddressCollectionViewSource = new ObservableCollection<StateClass>(new[] {new StateClass {State = "state", StateKey = "one"}});
}
}
}
I am trying to declaratively bind a ComboBox within a DataGrid CellEditingTemplate using a ViewModel. The ComboBox is not being bound. What am I doing wrong?
XAML:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:data="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
x:Class="SilverlightApplication1.EmployeeDetail"
Width="640" Height="480">
<UserControl.Resources>
<data:EmployeeDetailsViewModel
x:Key="ViewModel"
d:IsDataSource="True" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource ViewModel}}" Background="White">
<sdk:DataGrid ItemsSource="{Binding Employees,Mode=TwoWay}" AutoGenerateColumns="False" CanUserSortColumns="True" CanUserReorderColumns="True" CanUserResizeColumns="True" GridLinesVisibility="All" Height="317" HorizontalAlignment="Left" Margin="12,136,0,0" Name="EmployeesGrid" VerticalAlignment="Top" Width="605">
<sdk:DataGrid.Columns>
<!-- snipped from brevity -->
<sdk:DataGridTemplateColumn Header="Status">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding EmployeeStatus.Description}" TextWrapping="Wrap"></TextBlock>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=EmployeeStatuses}" SelectedItem="{Binding EmployeeStatus, Mode=TwoWay}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<TextBlock x:Name="SearchLabel" HorizontalAlignment="Left" Margin="12,95,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="106" Height="34"><Run FontWeight="Bold" Text="Search By Name: "/><Run FontSize="9.333" Text="(Last, First)"/></TextBlock>
<TextBox x:Name="SearchParam" HorizontalAlignment="Left" Margin="144,101,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="162"/>
<Button x:Name="SearchButton" Content="Search" HorizontalAlignment="Right" Margin="0,102,242,0" VerticalAlignment="Top" Width="75" Click="SearchButton_Click"/>
</Grid>
</UserControl>
VIEW MODEL:
using System.Collections.ObjectModel;
using SilverlightApplication1.EmployeeService;
using SilverlightApplication1.ViewModels;
namespace SilverlightApplication1
{
public class EmployeeDetailsViewModel : ViewModelBase
{
readonly IEmployeeServiceAgent _serviceAgent;
ObservableCollection<EmployeeStatus> _employeeStatuses { get; set; }
ObservableCollection<Employee> _employees { get; set; }
public EmployeeDetailsViewModel() : this(new EmployeeServiceAgent()) { }
public EmployeeDetailsViewModel(IEmployeeServiceAgent serviceAgent)
{
if (!IsDesignTime)
{
_serviceAgent = serviceAgent;
GetAllEmployees();
GetEmployeeStatuses();
}
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set
{
if(_employees!=value)
{
_employees = value;
OnNotifyPropertyChanged("Employees");
}
}
}
public ObservableCollection<EmployeeStatus> EmployeeStatuses
{
get { return _employeeStatuses; }
set
{
if (_employeeStatuses != value)
{
_employeeStatuses = value;
OnNotifyPropertyChanged("EmployeeStatuses");
}
}
}
private void GetAllEmployees()
{
_serviceAgent.GetAll((s, e) => Employees = e.Result);
}
private void GetEmployeeStatuses()
{
_serviceAgent.GetEmployeeStatuses((s, e) => EmployeeStatuses = e.Result);
}
}
}
Update:
This seems wrong but I figured out how to get the binding working by re-referencing the ViewModel in the ItemSource Binding:
<ComboBox ItemsSource="{Binding Source={StaticResource ViewModel},Path=EmployeeStatuses}"
DisplayMemberPath="Description"
SelectedItem="{Binding EmployeeStatus, Mode=TwoWay}" />
However, a am now experiencing a problem where the SelectedItem is not bound! What am I doing wrong?
The problem is a common one that people run into. When you're in the data template of the column, you're no longer bound the the view model. At that point your data context is the EmployeeStatus object (which doesn't have an EmployeeStatuses property to bind to).
So to get the combobox binding to work you can use the ElementName=LayoutRoot to bind back up the tree to the root ViewModel.
Update: Here would be the full syntax for your binding:
{Binding DataContext.EmployeeStatuses, ElementName=LayoutRoot}
Update2: I've actually run into this as well and there is a workaround you have to implement to get the element name binding to work inside a datagrid.
If Bryant's solution does not work (in SL4), use static resources. See this link: http://blog.digitaltools.com/post/2011/05/06/Binding-a-Datagride28099s-ComboBox.aspx
Or, by creating the static resource in xaml: http://forums.silverlight.net/post/370135.aspx