Append DataGrid inside of DataGrids RowDetailsTemplate - wpf

this appears to bind, but rows in Details Grid are empty. Something is off/missing?
I've also tried {Binding SubCustomers}
SubCustomers is a List on parent object.
I am able to bind this way to single Fields such as FirstName etc.. just not the subcollection..
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Source=SubCustomers}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>

The problem is that you are trying to bind to a property on the DataContext of the parent, not on that particular row. So, the DataContext of the RowDetails is the row item, and in order to get the parent's property, you need to use RelativeSource bindings. If you bind to the DataContext of the parent, you can then "dot-down" to the property you actually care about:
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid AutoGenerateColumns="True"
ItemsSource="{Binding DataContext.SubCustomers, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>

Related

WPF DataGrid cell binded on a parent property

I have a DataGrid that has the ItemSource property binded to a propery from my model.and i want to have a combobox column in the grid binded also to a property form the same model, not inside the property object that is binded on ItemSource
<DataGrid ItemsSource="{Binding Path=Model.ObjectList}" AutoGenerateColumns="False" AllowDrop="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Item No.">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="?????" SelectedValue="{Binding Path=ItemNumber}" SelectedValuePath="Id"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGrid>
The columns of the grid can have bindings only to properties that are inside an Object element from my ObjectList, Is there a way to bind a property from the parent model to a grid cell?
You should use something like that :
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path= DataContext.ItemSourcePropery}"
where ItemSourceProperty is the ObservableCollection which you want to bind to. Also if you are in a window, set type "Window" or whatever else.

Binding ComboBox to DataGrid Entry

Solved problem, but still have questions. See end of post, or read on for context.
I'm trying to setup a WPF datagrid which has many templated columns with comboboxes inside of them. In particular I'm having trouble with data-binding.
Data Model
First, I have Entry, which contains 4 properties:
Name
Customer
Color
Type
The most interesting property is Color which has two more sub properties:
ColorString
Index
Goal
I need to create a data grid where each row corresponds to an Entry object. The Customer, Color, and Type properties all have comboboxes that allow for a selection of dynamically populated choices. I need the selected item for each combobox to bind to the entry's respective property.
Screenshot
Questions:
How do I properly set the data contexts for the data grid and for each combobox?
For the data grid, I'm setting the data context programmatically to an instance of ObservableCollection.
private ObservableCollection<Entry> entries = new ObservableCollection<Entry>();
public MainWindow()
{
InitializeComponent();
entries.Add(new Entry("Foo", "Customer1", new MyColor("#00000000", 1), "Type1"));
entries.Add(new Entry("Bar", "Customer2", new MyColor("#00000000", 1), "Type2"));
LayerMapGrid.DataContext = entries; //Set data-context of datagrid
}
For the color combobox, I'm using an ObjectDataProvider in my XAML:
<Window.Resources>
<ObjectDataProvider x:Key="Colors" ObjectType="{x:Type local:MyColor}" MethodName="GetColors"/>
</Window.Resources>
This is how I bind the ObjectDataProvider
<ComboBox ItemsSource="{Binding Source={StaticResource Colors}}"/>
In the MyColor class, I've created the method below:
public static ObservableCollection<MyColor> GetColors()
{
ObservableCollection<MyColor> colors = new ObservableCollection<MyColor>();
//... Fill collection... (omitted for brevity)
return colors;
}
The good news is that ALL of the above code WORKS. However is it a good approach to the problem at hand?
This my next and more important question:
How do I bind the selected items of the comboboxes so that ObservableCollection<Entry> is updated?
I'm aware that I need to set the UpdateSourceTrigger="PropertyChanged", so that my source, which is the Entry collection is updated.
Here is my current XAML code for setting up my entire data grid. Note: I haven't implemented the customer and type comboboxes yet. I'm really just concerned with the color combobox:
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Name="LayerMapGrid">
<DataGrid.Columns>
<!--Name Column-->
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Control.HorizontalContentAlignment" Value="Center" />
</Style>
</DataGridTemplateColumn.HeaderStyle>
</DataGridTemplateColumn>
<!--Customer Column-->
<DataGridTemplateColumn Header="Customer">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding CustomerName, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--Color Column-->
<DataGridTemplateColumn Header="Color">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource Colors}}" SelectedItem="{Binding Color, ElementName=LayerMapGrid, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<DockPanel Margin="2">
<Border DockPanel.Dock="Left" HorizontalAlignment="Right" BorderThickness="1" BorderBrush="Black" Margin="1" Width="10" Height="10">
<Rectangle Name="ColorRec" Fill="{Binding ColorString}"/>
</Border>
<TextBlock Padding="4,2,2,2" Text="{Binding ColorString}" VerticalAlignment="Center"/>
</DockPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--Type Column-->
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Type, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Your assistance is greatly appreciated. I've been banging my head on the wall for about 16 hours with this.
-Nick Miller
EDIT:
I found the solution (and as always immediately after requesting help), but I don't understand how it works.
In the XAML, I've changed the combobox binding to be the following:
<ComboBox ItemsSource="{Binding Source={StaticResource Colors}}" SelectedItem="{Binding Color, UpdateSourceTrigger=PropertyChanged}">
What exactly is happening here?
Why is the combobox now referring to the datagrid's data context? Doesn't that get overriden when I set the ItemsSource to point to my ObjectDataProvider?
How do I properly set the data contexts for the data grid and for each combobox?
You don't. Normally in WPF, we set the DataContext property on the UserControl, or Window that we are designing, not on individual controls. In this way, all the controls in the view have access to the data properties.
How do I bind the selected items of the comboboxes so that ObservableCollection is updated?
Again, you don't do what you are doing. Rather than using an ObjectDataProvider, you should just have a property of type ObservableCollection<MyColor> in the code behind and bind to that directly. And you should be binding a public Entries property to the DataGrid.ItemsSource property, not setting the private entries field as the DataContext. You really need to read through the Data Binding Overview page on MSDN for further help with this.
Set the DataContext of MainWindow.xaml to itself (which generally is a not a good idea):
public MainWindow()
{
InitializeComponent();
Entries.Add(new Entry("Foo", "Customer1", new MyColor("#00000000", 1), "Type1"));
Entries.Add(new Entry("Bar", "Customer2", new MyColor("#00000000", 1), "Type2"));
DataContext = this; //Set DataContext to itself so you can access properties here
}
Then in MainWindow.xaml:
<DataGrid ItemsSource="{Binding Entries}" ... />
The DataTemplates in each row of the DataGrid automatically have the relevant item in the data bound collection set as the DataContext, so you automatically get access to the properties from the relevant class. Again... no need to set any DataContexts manually. If you have further questions, please read the linked article, because the answers are there.
UPDATE >>>
Let's be clear about something... I am not your personal mentor, so this will be my last reply.
How do I overcome the combobox inheriting the data context of the data grid?
To data bind to the object that is set as the Window.DataContext, you just need to use a valid Binding Path and some basic logic:
<ComboBox ItemsSource="{Binding DataContext.MyColors, RelativeSource={RelativeSource
AncestorType={x:Type YourLocalPrefix:MainWindow}}}" />

WPF Datagrid with databinding, changing ItemsSource

I've got a datagrid in WPF that shows a grid of some data. The data is retrieved form a ViewModel, which contains the following properties:
Public ReadOnly Property Devices() As List(Of Device)
Get
Return FDevices
End Get
.
Public ReadOnly Property ClientNetworks() As List(Of network)
Get
Return fnetwork
End Get
End Property
Both properties are filled with data after constructing the view model.
To use the properties in the Datagrid i use the following XAML.
<DataGrid ItemsSource="{Binding Devices}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Customer" >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate>
------------------ <TextBlock Text="{Binding ClientNetwork.Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
------------------> <ComboBox ItemsSource="{Binding ClientNetwork}" DisplayMemberPath="Description"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid>
This should show a textbox with the description and a combobox with strings when edited.
Outside of the Datagrid the combobox works fine. I know this is because of the set ItemsSource on the Datagrid but i can't seem to find how to make it work. i've tried several alterations of the combobox code but none have worked so far.
The goal is to make the user be able to edit the cell and have a combobox presented, from which he can select an string, then the corresponding int will be saved in the database.
UPDATE 1
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext.ClientNetworks}"
DisplayMemberPath="Description"
SelectedItem="{Binding ClientNetwork}"
/>
This is how i fixed the resting of the datacontext
I found a way to get it done but i am not sure this is the way it should be done
<Window.Resources>
<CollectionViewSource Source="{Binding ClientNetworks}" x:Key="clientnetworks" />
</Window.Resources>
and in the combobox
<ComboBox ItemsSource="{Binding Source={StaticResource clientnetworks}}" DisplayMemberPath="Description" />

Binding ListBox Items using Templates

I am going crazy trying to figure this out without success.
I have a DependencyObject, ("UserObject"). It has a "DataItems" DependecyProperty that is an ObservableCollection. "UserDefiniton" is a DependencyObject with a DependencyProperty of "Data". Data has two properties: DataType (an enumeration) and Value (a string).
I am trying to define a ListBox in XAML that uses the "DataItems" property as its ItemsSource. In the ItemTemplate, I have several different controls. For simplicity of this issue, I am using a CheckBox and a TextBox. I want CheckBox to be available and visible when DataType is 0, while I want the TextBox to be available and visible when the DataType is 1. Only one control can be available and visible at a time.
This works:
<ListBox
ItemsSource={Binding DataItems, Mode=OneWay}>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox
Visibility="{Binding Path=Data.DataType, Mode=OneWay, Converter={StaticResource VisibilityConverter}, ConverterParameter=0}"
IsChecked="{Binding Path=Data.Value, Mode=TwoWay, Converter={StaticResource StringToBoolean}}" />
<TextBox
Visibility="{Binding Path=Data.DataType, Mode=OneWay, Converter={StaticResource VisibilityConverter}, ConverterParameter=1}"
Text="{Binding Path=Data.Value, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
<Listbox.ItemTemplate>
</ListBox>
The problem is that even though only one is visible, both are fighting over the Data.Value property (the boolean of the checkbox will show in the textbox, even though the checkbox is hidden).
Basically, though, the binding in this case is working--but the implementation is incorrect.
So, I switched to using Templates. The problem I am having is that I can't get the binding to work.
This is the code that I have for the Template. The Template selector is working correctly, but the Text property of the TextBox and the IsChecked property of the checkbox are not binding to Data.Value:
<DataTemplate x:Key="TextBoxItem">
<TextBox
Text="{Binding Path=Data.Value, Mode=TwoWay}" />
</DataTemplate>
<DataTemplate x:Key="CheckBoxItem">
<CheckBox
IsChecked="{Binding Path=Data.Value, Mode=TwoWay, Converter={StaticResource StringToBoolean}}" />
</DataTemplate>
...
<ListBox
ItemsSource={Binding DataItems, Mode=OneWay}>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl
Content="{Binding Path=Data.DataType, Mode=OneWay}"
ContentTemplateSelector="{DynamicResource UserDefinitionTemplateSelector}"/>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
So how do I fix the binding?
Content should be set to {Binding}, since the Content will be the DataContext of the data-templates, hence you should just pass on the current DataContext. If you need to pass specific data to the template selector you can just drill down in the whole object.
There also is a template selector on the level of the ListBox, so you do not really need the internal ContentControl.
(You might also be interested in generic methods of debugging data bindings.)

Binding a WPF ComboBox to a different ItemsSource within a ListBox DataTemplate

I have a ListBox that contains a textbox and a combobox in its datatemplate:
<ListBox Height="147" Margin="158,29,170,0" Name="PitcherListBox" VerticalAlignment="Top" ItemsSource="{Binding SomeCollectionOfObjects}" Background="Black">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=Name}" />
<ComboBox ItemsSource="{Binding LocalArrayOfIntsProperty}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to bind the listbox to a collection of objects (which I've done successfully), but I want the combobox in the above datatemplate to have its itemssource set to a local property on the window (array of ints). I still want the combobox to have a two-way bind between its selected item and a property on the collection of objects...
I have the following in code:
PitcherListBox.DataContext = this;
Basically in the end, I want the combobox within the listbox to have a different itemssource than the listbox itself. I can't seem to figure out how to change the ComboBox's ItemsSource in XAML. Can someone provide me some feedback? Thanks!
Try this:
<ComboBox ItemsSource="{Binding LocalArrayOfIntsProperty, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type YourWindowTypeHere}}}" />
Note that you need to replace YourWindowTypeHere with the type of the Window containing the LocalArrayOfIntsProperty! Also remember that you will need to define an xml namespace for that type!

Resources