Bind nested object to WPF data grid - wpf

I have a DataGrid that has two columns (A and B). Each column cell contains a combo box control.
I have an object that contains two list items that I want to bind to the grid view, but the binding does not work.
Binding in both DataTemplates not working correctly. UI shows the namespace instead of value.
Sample Project:
https://1drv.ms/u/s!Ah27QQNpKj4jj2xsIsQamaOXOlin?e=jNAj2H
ViewModel
public class MainWindowViewModel {
public List < MainObject > MainObjects { get; set; }
public MainWindowViewModel() {
MainObjects = new List < MainObject > {
new MainObject {
AColumns = new List < ColumnA > {
new ColumnA { Name = "A1" },
new ColumnA { Name = "A1" }
},
BColumns = new List < ColumnB > {
new ColumnB { Name = "B1" },
new ColumnB { Name = "B1" }
},
}
};
}
}
public class MainObject {
public List < ColumnA > ?AColumns { get; set; }
public List < ColumnB > ?BColumns { get; set; }
}
public class ColumnA {
public string ? Name { get; set; }
}
View:
<Window x:Class="TestDataGrid.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DataGrid ItemsSource="{Binding MainObjects}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Column A" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Center">
<TextBlock.Text>
<PriorityBinding>
<Binding Path="Name" Mode="TwoWay" />
</PriorityBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox IsEditable="True" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding AColumns}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Window>

You have to use the "DisplayMemberPath" property instead of the "Text" property. like this
<ComboBox IsEditable="True" ItemsSource="{Binding AColumns}" SelectedValuePath="Name" DisplayMemberPath="Name"/>

Related

Caliburn.Micro: How to bind a function to a context menu item in the RowDetailsTemplate of a DataGrid?

I have a datagrid of Items that includes a datagrid of SubItems for the row details. There is a context menu for the main "Items" datagrid with functions to add or remove items, this is working fine. There is also a context menu for the row details datagrid "SubItems" with similar functions that I cannot get to bind properly. If executed i get the following exception "System.Exception: 'No target found for method AddSubItem.'"
I have desperately tried many other solutions that were suggested on other posts but nothing has worked so far. I appreciate any insight. Below is my test code.
ShellViewModel:
using System;
using Caliburn.Micro;
using ContextMenuTest.Models;
namespace ContextMenuTest.ViewModels
{
public class ShellViewModel : Screen
{
public static Random rnd = new Random();
private BindableCollection<ItemModel> items = new BindableCollection<ItemModel>();
public BindableCollection<ItemModel> Items
{
get { return items; }
set
{
items = value;
NotifyOfPropertyChange(() => Items);
}
}
private ItemModel selectedItem = new ItemModel(rnd);
public ItemModel SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
NotifyOfPropertyChange(() => SelectedItem);
}
}
private SubItemModel selectedSubItem = new SubItemModel(rnd);
public SubItemModel SelectedSubItem
{
get { return selectedSubItem; }
set
{
selectedSubItem = value;
NotifyOfPropertyChange(() => SelectedSubItem);
}
}
public ShellViewModel()
{
for(int i = 0; i < 10; i++)
{
Items.Add(new ItemModel(rnd));
}
}
public void AddItem()
{
Items.Add(new ItemModel(rnd));
}
public void RemoveItem()
{
Items.Remove(SelectedItem);
}
public void AddSubItem(object e)
{
var _item = e as ItemModel;
_item.SubItems.Add(new SubItemModel(rnd));
}
public void RemoveSubItem(object e)
{
var _item = e as ItemModel;
_item.SubItems.Remove(SelectedSubItem);
}
}
}
ShellView:
<Window x:Class="ContextMenuTest.Views.ShellView"
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:ContextMenuTest.Views"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
Title="ShellView" Height="450" Width="800"
>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DataGrid x:Name="Items" Grid.Row="0" AutoGenerateColumns="False" CanUserResizeColumns="True" CanUserAddRows="false"
VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" MinHeight="100" SelectedItem="{Binding SelectedItem}">
<DataGrid.ContextMenu>
<ContextMenu cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Add Item" cal:Message.Attach="AddItem()"/>
<MenuItem Header="Remove Item" cal:Message.Attach="RemoveItem()"/>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns >
<DataGridTextColumn Header="ID" Binding="{Binding Path=ID, UpdateSourceTrigger=PropertyChanged}" Width="*" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" Width="*"/>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding Path=SubItems}" AutoGenerateColumns="False" CanUserResizeColumns="True" CanUserAddRows="false"
VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" MinHeight="100"
SelectedItem="{Binding Path=SelectedSubItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}">
<DataGrid.ContextMenu>
<ContextMenu cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Add Sub Item" cal:Message.Attach="AddSubItem($dataContext)"/>
<MenuItem Header="Remove Sub Item" cal:Message.Attach="RemoveSubItem($dataContext)"/>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Path=ID}" Width="auto" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" Width="auto"/>
<DataGridTextColumn Header="Value" Binding="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}" Width="auto"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</Window>
ItemModel:
using Caliburn.Micro;
using System;
namespace ContextMenuTest.Models
{
public class ItemModel
{
public Guid ID { get; set; }
public string Name { get; set; }
public BindableCollection<SubItemModel> SubItems { get; set; } = new BindableCollection<SubItemModel>();
public ItemModel(Random rnd)
{
ID = Guid.NewGuid();
Name = "Item " + ID.ToString().Substring(3, 5);
for (int i = 0; i < 5; i++)
{
SubItems.Add(new SubItemModel(rnd));
}
}
}
}
SubItemModel:
using System;
namespace ContextMenuTest.Models
{
public class SubItemModel
{
public Guid ID { get; set; }
public string Name { get; set; }
public int Value { get; set; }
public SubItemModel(Random rnd)
{
ID = Guid.NewGuid();
Name = "SubItem " + ID.ToString().Substring(3,5);
Value = rnd.Next();
}
}
}
Try using TagHelper. It works for your requirement.sample code is as below.
<Grid Background="White" HorizontalAlignment="Stretch" Height="200" Name="GridLayout">
<ListBox x:Name="ListBoxItems">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Tag="{Binding DataContext, ElementName=GridLayout}">
<Grid.ContextMenu>
<ContextMenu Name="cm" cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Open"
cal:Message.Attach="Open($dataContext)">
</MenuItem>
</ContextMenu>
</Grid.ContextMenu>
<TextBlock VerticalAlignment="Center">
.. text..
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>

WPF DataGrid with only one combobox in templatecolumn not adding new rows

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>

WPF ContentControl not displaying anything

I am trying to build a Control that selectively displays different things based upon the types that are passed in, but for some reason I end up displaying nothing at all.
Is there some fundamental thing that I am missing here? (This code is massively stripped down from my real production app bu exhibits the same behavior)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new List<ContactInformation>
{
new Address {Street = "21 Jump", City = "Sparta", State = "Denial"},
new Phone {Number = "734-555-1212"}
};
}
}
public class ContactInformation
{
}
public class Address : ContactInformation
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
}
public class Phone : ContactInformation
{
public string Number { get; set; }
}
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:contentControlExample="clr-namespace:ContentControlExample"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl ItemsSource="{Binding /}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl DataContext="{Binding /}" Content="{Binding /}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type contentControlExample:Address}">
<StackPanel>
<TextBlock Text="{Binding Street}"/>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="City"/>
<Binding Path="State"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type contentControlExample:Phone}">
<TextBlock Text="{Binding Number}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
All you need is to remove a couple of '/' as follow:
XAML:
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:contentControlExample="clr-namespace:ContentControlExample"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl ItemsSource="{Binding }">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl DataContext="{Binding }" Content="{Binding }">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type contentControlExample:Address}">
<StackPanel>
<TextBlock Text="{Binding Street}"/>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="City"/>
<Binding Path="State"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type contentControlExample:Phone}">
<TextBlock Text="{Binding Number}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
The Code behind:
namespace ContentControlExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new List<ContactInformation>
{
new Address {Street = "21 Jump", City = "Sparta", State = "Denial"},
new Phone {Number = "734-555-1212"}
};
}
}
public class ContactInformation
{
}
public class Address : ContactInformation
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
}
public class Phone : ContactInformation
{
public string Number { get; set; }
}
}
The Ouput:
I hope this helps.
Try changing your code slightly. This works because the ItemsControl automatically picks the correct DataTemplate based on the type of the item being bound to.
public class ViewModel
{
public ViewModel()
{
this.Items = new List<ContactInformation>
{
new Address
{
Street = "21 Jump",
City = "Sparta",
State = "Denial"
},
new Phone { Number = "734-555-1212" }
};
}
public List<ContactInformation> Items { get; set; }
}
<Window.DataContext>
<contentControlExample:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type contentControlExample:Address}">
<StackPanel>
<TextBlock Text="{Binding Street}"/>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="City"/>
<Binding Path="State"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type contentControlExample:Phone}">
<TextBlock Text="{Binding Number}"/>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemsSource="{Binding Items}"/>
</Grid>
Or to bind the current item to a content control:
<Grid>
... resources
<ContentControl Content="{Binding Items/}"/>
</Grid>

Specific control for a specific DataType

I have a List which contains different types of objects:
List<object> myList = new List<object>();
DateTime date = DateTime.Now;
myList.Add(date);
int digit = 50;
myList.Add(digit);
myList.Add("Hello World");
var person = new Person() { Name = "Name", LastName = "Last Name", Age = 18 };
list.ItemsSource = myList;
public class Person
{
public string Name { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
I want to see them in a ListBox with different kinds of controls. For instance: DatePicker for DateTime, TextBlock for string, TextBox for Person's Name and LastName...
Is it possible to do this task with XAML?
Help appreciated.
<Window x:Class="MiscSamples.DataTemplates"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="DataTemplates"
Height="300"
Width="300">
<Window.Resources>
<!-- DataTemplate for strings -->
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding Path=.}" />
</DataTemplate>
<!-- DataTemplate for DateTimes -->
<DataTemplate DataType="{x:Type sys:DateTime}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding Path=.}" />
</DataTemplate>
</DataTemplate.Resources>
<DatePicker SelectedDate="{Binding Path=.}" />
</DataTemplate>
<!-- DataTemplate for Int32 -->
<DataTemplate DataType="{x:Type sys:Int32}">
<Slider Maximum="100"
Minimum="0"
Value="{Binding Path=.}"
Width="100" />
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding}" />
</Window>
Code Behind:
public partial class DataTemplates : Window
{
public DataTemplates()
{
InitializeComponent();
var myList = new List<object>();
myList.Add(DateTime.Now);
myList.Add(50);
myList.Add("Hello World");
DataContext = myList;
}
}
Result:
As you can see, there's no reason at all to use code to manipulate UI elements in WPF (except some very very specific cases)
Edit:
Note that you don't usually create a DataTemplate for classes inside the System namespace (such as System.String. This is only to give you an example. If you really needed this you would probably have to create a ViewModel for each type.

ComboBox in my WPF DataGrid won't display any items

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"}});
}
}
}

Resources