Binding ComboBox to DataGrid Entry - wpf

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}}}" />

Related

Bind the Text Property of Textblock to a Combobox selectedItem with different DataContext in Silverlight

I have a Data Grid that is bound to a object of type MyStaff. Apart from other properties MyStaff contains a column named LookupID. Now, in my ViewModel I have a collection Named Lookups that have a description for each LookupID.
I have a Template column that has a Textblock in Cell Template and Combobox in CellEdit Template. How do I bind the Textblock so that it dsiplays the description from ComboBox based on LookupID.
I know it would be pretty simple if the datacontext for both the Textblock and ComboBox were simple but that is not the case.
I have tried this but this doesn't work. Any suggestions? Also would appreciate any information on how to best use different Data Context for different controls in Silverlight. For this I have added a static resource pointing to the ViewModel Class.
<sdk:DataGridTemplateColumn Header="Action Point"
Width="500"
CanUserReorder="False"
HeaderStyle="{StaticResource dthFull2}"
IsReadOnly="False">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ElementName=LookupList,
Path=SelectedItem.Description}"
MinHeight="24"
VerticalAlignment="Top"
Padding="2"
TextTrimming="WordEllipsis"/>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<my:AutoCompleteComboBox x:Name="LookupList"
FilterMode="Custom" Margin="2,0,0,0"
SelectedValue="{Binding LookupID, Mode=TwoWay}"
SelectedValuePath="LookupID"
ItemsSource="{Binding Path=AnalysisLookupList.Values,
Source={StaticResource ViewModel}}"/>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>

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" />

WPF DataGrid, in a Binding in a ColumnTemplate access a Element outside of the DataGrid

I'd like to access a Object from my UserControl from within the Datgrids ColumnTemplate.
This doesn't work. Now I've read it's because of the Datacontext.
I found this Example, which should fix this: http://blog.errorok.com/2010/09/09/212/
But the Event: ColumnDataContextChanged is never called in my Project!
here's a part of my XAML:
<DataGridTemplateColumn Header="Database-Fieldtype" Width="Auto" IsReadOnly="False" SortMemberPath="DatabaseFieldType">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DatabaseFieldType}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ConfigurationTool:EditProtocolDatasets}}, Path=grdDatasets.SelectedItem.Storage.DatabaseFieldTypes}"
SelectedItem="{Binding DatabaseFieldType}"
VerticalAlignment="Top" Width="179" Padding="0" Margin="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
ConfigurationTool:EditProtocolDatasets is my UserControl, grdDatasets is another Datagrid, to which SelectedItem I'd like to bind!
Okay, I'm going to suggest a completely different direction than my first one. My guess is that you have the ItemsSource for grdDatasets bound to something.
For the item that's going to act as your datacontext for the control, make sure it has the following characteristics, or at least a comparable structure:
public class ListOfDataSets : DependencyObject
{
public IEnumerable<DataSetOption> Items
{
get
{
...Whatever you normally use to get your DataSetOptions...
}
}
public DataSetOption SelectedItem
{
get { return (DataSetOption)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(DataSetOption), typeof(ListOfDataSets), new PropertyMetadata(null));
}
The key here is that you have one property that is a list of your choices, and another property that represents one of those items.
Then, in your XAML, your control can have the following structure:
<UserControl>
<UserControl.Resources>
<ConfigurationTool:ListOfDatasets x:Key=DataSetOptions />
</UserControl.Resources>
<StackPanel Name="LayoutRoot">
<DataGrid Name="grdDatasets"
ItemsSource="{Binding Source={StaticResource DataSetOptions}, Path=Items}"
SelectedItem="{Binding Source={StaticResource DataSetOptions}, Path=SelectedItem}"
...
</DataGrid>
...
<DataGrid Name="OtherDataGrid" ItemsSource="{Binding OtherSource}">
<DataGrid.Columns>
...
<DataGridTemplateColumn Header="Database-Fieldtype" Width="Auto" IsReadOnly="False" SortMemberPath="DatabaseFieldType">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DatabaseFieldType}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{StaticResource DataSetOptions}, Path=SelectedItem.Storage.DatabaseFieldTypes}" SelectedItem="{Binding DatabaseFieldType, Mode=TwoWay}"
VerticalAlignment="Top" Width="179" Padding="0" Margin="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</Datagrid.Columns>
</DataGrid>
</StackPanel>
</UserControl>
I actually tried this structure out, and the databinding works fine. If the DataSetOptions change a lot, though, this solution may not work, unless you're using MVVM, and the ViewModel is good at tracking what options are available, and presents them properly to the View.
Hopefully this makes sense. I actually tried this one before answering.
I was not correct with my original answer, and overestimated the capabilities of RelativeSource before I experimented with it.
---Original text below---
I think I can help, but I need a few more details. For now I'm just going to work off assumptions.
Assumption 1: You're in a WPF UserControl with a DataGrid that has a defined ItemsSource.
Assumption 2: The UserControl has another element that you want a column within your DataGrid to have access to.
If these two assumptions are correct, it is a much better problem to have in WPF than in Silverlight.
Each row in your DataGrid is going to be working from within a DataContext that consists of the Item for that row. But, you can reach outside of the cell's (or any) DataContext with a RelativeSource.
So, if you wanted to go up the Visual Tree to get to your control's Width, you would use:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyUserControl}}, Path=Width}
This will trace upward in the Visual Tree until an object of type "MyUserControl" is found, at which point it will grab the "Width" property and bind to it.
The Path doesn't have to be only one item deep, either. You can run up an down your visual tree as required. As this gets more complex, though, your code is going to be more fragile.
If this isn't correct, please post your XAML (or something similar) and say so, and I'll spin up a test environment and edit my post.

WPF DataTemplate and Binding - Is this possible in xaml?

I am DataTemplating a listbox's ItemSource to display a series of comboboxes. I want to give the DisplayMemberPath of the combo to a property, which is in a different source than its own ItemsSource. (Assuming DisplayMemberPath is just a string representing name of a property, I am getting this from the user). I have achieved this with a CollectionViewSource, but all the comboboxes are displaying the same list.
What I am expecting to have after data templating is to have comboboxes display,
ComboboxInstance1.DisplayMemberPath = PropertyMapOfEmployee in FilterControls[0]
ComboboxInstance2.DisplayMemberPath = PropertyMapOfEmployee in FilterControls[1]
Is this possible to achieve in XAML ?
Thanks. Mani
UserControl:
<Resources>
<CollectionViewSource x:Key="bindingSource" Source="{Binding BindingItems}"/>
<CollectionViewSource x:Key="FilterSource" Source="{Binding FilterControls}"/>
<DataTemplate DataType="{x:Type CustomTypes:FilterElement}">
<ComboBox ItemsSource="{Binding Source={StaticResource bindingEmp}"
DisplayMemberPath="{Binding Source={StaticResource FilterSource},
Path=PropertyMapofEmployee}" />
</DataTemplate>
<Resources>
---
<DockPanel>
<ListBox x:Name="lstBox" ItemsSource="{Binding FilterControls}" />
</DockPanel>
ViewModel:
List<FilterElement> FilterControls;
List<Employee> Employees
class FilterElement
{
string Caption;
String PropertyMapofEmployee
}
<ComboBox ItemsSource="{Binding Source={StaticResource bindingEmp}"
DisplayMemberPath="{Binding PropertyMapofEmployee}" />
I'm not sure you can do that in XAML. (Having the DisplayMemberPath point to a property that is on an object other than the DataContext). You may want to look at the RelativeSource Class to see if that would meet your needs.
Have you thought about providing a reference in your Employee object to the FilterElement and then hooking up to the binding the Employee.PropertyMapOfEmployee property that you've created?

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