WPF DataTemplate and Binding - Is this possible in xaml? - wpf

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?

Related

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

ComboBox ItemsSource has a different datacontext from SelectedItem

I have ComboBox where I get my ItemsSource from my "Settings" object with the UserTypes ObservableCollection inside, and I need to bind its SelectedItem to my "Employee" object's type property. There are other fields within the window, which binds properly to other properties in the "Employee" object, the ItemsSource is correctly populated with the UserTypes property within Settings, and the only thing that is not working is the type property not binding with the combobo'x selecteditem, possibly because it's datacontext is set to the "Settings" object. Here is what I have so far:
<CollectionViewSource x:Key="settingsViewSource" d:DesignSource="{d:DesignInstance my:Settings, CreateList=True}" />
<CollectionViewSource x:Key="settingsUserTypesViewSource" Source="{Binding Path=UserTypes, Source={StaticResource settingsViewSource}}" />
<ComboBox DataContext="{StaticResource settingsUserTypesViewSource}" Name="userTypeBox" VerticalAlignment="Top" Width="100" Margin="2" ItemsSource="{Binding}"
SelectedItem="{Binding Path=Type, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
In the code behind, I have:
InitializeComponent();
this.emp = emp;
InfoTab.DataContext = emp;
userTypeBox.DataContext = MainWindow.ViewSettings.UserTypes;
emp is a specific employee bound to the fields correctly, only the combo boxes' selecteditem is not working. Any help would be greatly appreciated.
I don't know if you have a typo in the actual code or if it's just here, but you are missing a bracket
SelectedItem="Binding Path=Type, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
should be
SelectedItem="{Binding Path=Type, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"

Relative Binding in Silverlight

I have an ItemsControl in my application. The page associated with the ItemsControl is bound to a view-model. The view-model includes two properties: People and Options. For each person, I want to display a list of options in a ComboBox. The options are defined in my view-model. My code looks like the following:
<ItemsControl ItemsSource="{Binding Path=People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="Options" DisplayMemberPath="FullName" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
However, because each Item represents a Person, the ComboBox is looking at the Person object for a property called "Options". How do I reference the view model for the from the ComboBox instead of the Person?
Thanks!
You can use the following technique
<ComboBox ItemsSource="{Binding ElementName=LayoutRoot, Path=DataContext.Options}" DisplayMemberPath="FullName" />
Assuming that your LayoutRoot's DataContext is the View Model. If not you can give your ItemsControl a name and use that for the ElementName.

WPF : How to create separate Resources for each item in a bound ItemsControl

I want to achieve the following:
My ViewModel exposes a property named 'Categories' which is a collection of CategoryViewModel objects
Each CategoryViewModel object exposes a property called 'Items' which is a collection of strings*.
On my View, I want a TabControl with a TabItem for each object in the 'Categories' collection.
The Content of each TabItem should be a xceed DataGrid control displaying the contents of the selected tab's Items collection.
<TabControl ItemsSource="{Binding Categories}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding CategoryName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<xcdg:DataGridControl
ItemsSource="{Binding Items}"
AutoCreateColumns="True">
</xcdg:DataGridControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
This works ok when I bind directly to the ItemsSource property of the DataGridControl. However, in order to utilize all of the functionality of the DataGridControl, I need to bind the ItemsSource property of the DataGridControl to a DataGridCollectionViewSource object that is bound to my Items collection. I do this when the grid ISN'T nested in another control by creating a DataGridCollectionViewSource object in the Resources section of the UserControl and binding to that.
<UserControl>
<UserControl.Resources>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
Source="{Binding Items}" />
</UserControl.Resources>
<Grid>
<xcdg:DataGridControl
ItemsSource="{Binding Source={StaticResource GridData}}"
AutoCreateColumns="True">
</xcdg:DataGridControl>
</Grid>
</UserControl>
How do I need to structure the XAML so that when the TabControl is being bound, a DataGridCollectionViewSource object is created for each TabItem so that the DataGridControl that is generated within the content of the TabItem can be bound to it?
Clear as mud, right? :)
Thanks!
Notes:
*In the real solution the collection contains objects of a class that is more complex than a simple string, but a string was used to make the example more simple.
OK, this is a bit of a long-shot, but could you use the DataGrid.Tag ...
<TabControl.ContentTemplate>
<DataTemplate>
<xcdg:DataGridControl
ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=Tag}"
AutoCreateColumns="True">
<xcdg:DataGridControl.Tag>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
Source="{Binding Items}" />
</xcdg:DataGridControl.Tag>
</xcdg:DataGridControl>
</DataTemplate>
</TabControl.ContentTemplate>
Or ... resources can be defined on any FrameworkElement, so you could try:
<TabControl.ContentTemplate>
<DataTemplate>
<xcdg:DataGridControl
ItemsSource="{Binding Source={StaticResource GridData}}"
AutoCreateColumns="True">
<xcdg:DataGridControl.Resources>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
Source="{Binding Items}" />
</xcdg:DataGridControl.Resources>
</xcdg:DataGridControl>
</DataTemplate>
</TabControl.ContentTemplate>
I don't use the eXceed Grid so cannot test whether these work - just a couple of ideas to try!
Colin E.
You can use x:Shared="True" attribute on a resource. That means a new instance is created for every use of that resource.
Example:
<UserControl.Resources>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
x:Shared="False"
Source="{Binding Items}" />
</UserControl.Resources>

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