WPF DataGrid with mutable row/cell types - reuse templates - wpf

I have a complex table of data (about 150 rows, between 1 and 100 columns) which I want to display and edit using a DataGrid in WPF, but I've hit a big stumbling block. Please forgive me (and correct me) if my terminology is off in points, as I'm quite new to WPF and XAML.
To understand my problem, my requirements are:
The data consists of a variable number of rows and columns which are loaded through AJAX
Every row (class "Record" in my test implementation) has a few fixed properties that need to be displayed as well as a varying number of properties (though all rows have the same number of such properties) in a collection
Each row/Record has an type (e.g. String, Integer, Boolean) for its properties inferred through an Enum property "VType". Properties should be displayed and edited with a template according to the VType value.
Columns may be added or removed at run time
(Some) rows may also be added or removed at run time
Rows can change their "type" at run time
So far, I've built a working example with DataGridTextColumns that creates the columns from simulated data and fills the bound collection. I've implemented INotifyPropertyChanged and used ObservableCollections where necessary, so reactivity works, and my propoerty values are pulled from the binding to the individual property and correctly shown.
When adding the columns, I passed the correct binding. For my example app, I use the column index to bind each column to the correct Property object in the Record's collection:
// Amounts to "Properties[0].Value", "Properties[1].Value", etc.
var binding = new Binding(string.Format("Properties[{0}].Value", column.Index));
dataGrid.Columns.Add(new DataGridTextColumn() { Header = column.Name, Binding = binding });
Now I tried to tackle using different templates for different "Record types", i.e. a vType property in my Record class. I've created data templates in Window.Resources (very crude ones to start), set up a lookup and implemented the RecordTemplateSelector:
<!--BOOL TEMPLATE-->
<DataTemplate x:Key="booleanTemplate">
<CheckBox IsChecked="{Binding Value}" Background="LightGray" Margin="5, 0, 0, 0"/>
</DataTemplate>
<!--STRING TEMPLATE-->
<DataTemplate x:Key="stringTemplate">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
<!--INTEGER TEMPLATE-->
<DataTemplate x:Key="integerTemplate">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
<local:RecordTemplateSelector x:Key="myRecordTemplateSelector"
BooleanTemplate="{StaticResource booleanTemplate}"
StringTemplate="{StaticResource stringTemplate}"
IntegerTemplate="{StaticResource integerTemplate}"/>
And this is my TemplateSelector:
class RecordTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selectedTemplate = StringTemplate;
var record = item as Record;
if (item == null) return selectedTemplate;
switch (record.VType)
{
case Record.ValueType.Checkbox:
selectedTemplate = BooleanTemplate;
break;
case Record.ValueType.Integer:
selectedTemplate = IntegerTemplate;
break;
case Record.ValueType.String:
selectedTemplate = StringTemplate;
break;
}
return selectedTemplate;
}
}
It does pull the correct template, but when I thought I had it working, I noticed that I can't correctly bind my DataGridTemplateColumn - it is always implicitly bound to the whole row (i.e. Record object) and I don't see a way how my template can know which element in the Record's Property collection it should apply to.
I'm at a loss where to go from here. Is there a way to inherit the column's binding down to the template? Is there some other way to pass the correct item (an index would be okay too) to the template? Or do I have to use a completely different approach?
Big Thanks in advance for any input you can give me.

Related

WPF MVVM enable button in child DataGrid with Command and CommandParameter

I have a WPF application wherein a Window has a master/child relationship. When a user selects a Buyer from a ListView, a DataGrid is bound with related Items they have purchased. In the GridView, there is a button on the left where a Payment for this individual item can be made. However, I want to disable/enable the button based on the state of the Item, whether or not an existing Payment is associated with it. Using Galasoft MVVM Light, RelayCommand throughout the application. I have the Pay button working, but ONLY when clicked (row selected). This works fine when bound to a command in this way:
<DataGrid ..
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
IsSynchronizedWithCurrentItem="True"
.. >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button ...
Command="{Binding Path=DataContext.PayItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
CommandParameter="{Binding SelectedItem, Mode=OneWay, ElementName=itemsPurchasedGrid}"
>Pay</Button>
...
// in ctor
PayItemCommand = new RelayCommand<Item>(PayItem, IsItemUnPaid);
// in body
private void PayItem(Item item)
{
PaymentMode = PaymentMode.Item;
Messenger.Default.Send<BuyerViewModel>(this, "LaunchPayWindow");
}
// CanExecute impl
private bool IsItemUnPaid(Item item)
{
// code to determine if item is already paid
}
So, when I click the button, it correctly passes the SelectedItem to the RelayCommand delegate. However, the CanExecute implementation: IsItemUnPaid(Item item) never gets the item, because it is not (yet) selected. I tried: IsSynchronizedWithCurrentItem="True", which always passes the first Item, but not any others. Is there a way to get a handle to the Item while it is binding to the DataGrid to disable/enable it? I'm sure there are ugly ways in the codebehind during binding, but is there an MVVM way to check the current item and toggle that button? Many thanks!
P.S. I wrote a simple converter which works, but is that really the best way?
public class PayItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is Item item))
return true;
// if you find any payments for this item...
var payments = (item.Payments != null && item.Payments.Any(p => p.ItemId.HasValue && p.ItemId.Value == item.Id))
? item.Payments.Where(p => p.ItemId.HasValue && p.ItemId.Value == item.Id).ToArray()
: null;
// if you find payments for this item, are the sum of the payments less than the total of the item TotalAmount, it is said to be unpaid
return (payments != null && payments.Length > 0)
? payments.Sum(p => p.Amount) < item.TotalAmount
: true;
}
No, a converter is not the best way of implementing this. The whole point of MVVM is to maintain good separation-of-concerns between your view and your view logic. Converters most definitely reside in the view layer; as soon as you find yourself adding application-type logic to a converter it's usually a sign that your view model is not coercing your data into a format that can be readily consumed by the view.
The problem here is that your Items property seems to be model data, presumably from a database or something. What it needs to be is a collection of view models, one per item, which exposes the properties of the item (or even the whole item itself) and also contains extra fields for the logic needed by your view i.e.:
public bool PayButtonEnabled {get; private set;}

Using base control in XAML, but loading a derived control

Here's a situation I am trying to solve:
I have a base UserControl from which I derive a number of other Controls that handle derivations of a base Object in a specific manner. (The purpose of this being to create a template for when additional derivations of the base control are needed later down the road.) What I would like to do is use the base control name as the tag in XAML, but when the control is actually rendered, show the derived control.
class BaseControl : UserControl { }
class DerivedControl1 : BaseControl { }
class DerivedControl2 : BaseControl { }
class BaseObject { }
class DerivedObject1 : BaseObject { // Requires DerivedControl1 to display }
class DerivedObject2 : BaseObject { // Requires DerivedControl2 to display }
class BaseContainerObject { }
class ContainerObject1 : BaseContainerObject
{
DerivedObject1 dObject0;
DerivedObject1 dObject1;
DerivedObject2 dObject2;
}
class ContainerObject2 : BaseContainerObject
{
DerivedObject2 dObject0;
DerivedObject2 dObject1;
DerivedObject1 dObject2;
}
window.xaml
<!-- Here is what I would like to do -->
<StackPanel>
<BaseControl Name="Object0" DependencyProperties="{Binding BaseContainerObject.dObject0}" />
<BaseControl Name="Object1" DependencyProperties="{Binding BaseContainerObject.dObject1}" />
<BaseControl Name="Object2" DependencyProperties="{Binding BaseContainerObject.dObject2}" />
</StackPanel>
I've played around with styles and data triggers to detect the specific type of ContainerObject, but I haven't found the right pattern to encapsulate a ContainerObject in a single template-able "package" yet.
I could dynamically add the controls from the code-behind, but I haven't had any luck with that so far. (The top-level of the control appears on VisualTree, but no children appear on the tree and none are rendered.)
Any thoughts?
EDIT:
I can't post a screenshot at the moment, but perhaps I can add a little more detail.
I have a data object (the DataContext for the window) that has up to nine attributes (the DerivedObjects) that the user will need to edit in my window. The meaning of those nine attributes, and, in turn, how they should be expressed in UI controls, changes based on the attributes of a second data object the user selects in a previous step. (That is the ContainerObject. The other data object is not referenced in the above code, although it contains a reference to the second data object.)
Those attributes can be expressed in four different ways: a text box (for continuous values), a combobox (for discrete values), a checkbox (for boolean values) and radio buttons (for a choice between two values).
I have created UserControls that package those controls in a horizontal Grid with 1) a label for the value's definition, 2) the value's units (if applicable) and, if applicable, 3) a checkbox to view the value in an alternate format (i.e. viewing a decimal number in hex). (Those are the DerivedControls that inherit from an XAML-less BaseControl that stores common properties and functions.) To maintain proper column alignment over the entire collection, I specify four column widths in a Style at the Window level and use a Converter to handle alignment for attributes that do not require the units and/or the alt-display checkbox.
When the user selects the second object in the previous step, the nine rows of the collection control should look to the second data object reference of the DataContext object to select the proper template and populate the other labels. Because I will need to use this collection in other programs, I am creating it in a separate assembly.
I know I am pigeon-holing myself in some fashion on this. I am trying to do this with as little code as possible, but I can't think of the right code pattern to use here. Every component is working fine, but I can't seem to get it all to come together in a simple way so I can work out the last few little bugs.
Thanks. I am just learning WPF, and I really like. I'm just at the point of trying to get my head wrapped around some of the finer details.
Here is a pretty good example from wpftutorial.net of what it sounds like you need. To summarize, you can use a DataTemplate to define how an object is displayed within a repeating control such as a ListBox, ComboBox or ListView. You can override the styles of those to make them appear as you want, or sometimes it's just easier to use ItemsControl (the control they inherit from) directly. They have a property named ItemsPanel that will allow you to specify a StackPanel as the ItemsPanelTemplate so you get the same desired layout of the objects as you showed above.
Setting how an object is dispalyed via a DataTemplate is great, but you want to dynamically change that template based on the type of the bound object if I understand correctly. This can be accomplished by creating a DataTemplateSelector.
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultDataTemplate { get; set; }
public DataTemplate DerivedObject1Template { get; set; }
public DataTemplate DerivedObject2Template { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DataTemplate selectedTemplate = DefaultDataTemplate;
if (item is DerivedObject1)
{
selectedTemplate = DerivedObject1Template
}
else if (item is DerivedObject2)
{
selectedTemplate = DerivedObject2Template;
}
return selectedTemplate;
}
}
And then your XAML can use the template selector on the repeating control:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:..."
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Window.Resources>
<!-- Default DataTemplate -->
<DataTemplate x:Key="DefaultDataTemplateResource">
...
</DataTemplate>
<!-- DataTemplate for Booleans -->
<DataTemplate x:Key="DerivedObject1TemplateResource">
<local:DerivedControl1 .../>
</DataTemplate>
<!-- DataTemplate for Enums -->
<DataTemplate x:Key="DerivedObject2TemplateResource">
<local:DerivedControl2 .../>
</DataTemplate>
<!-- DataTemplate Selector -->
<local:PropertyDataTemplateSelector x:Key="myCustomTemplateSelector"
DefaultnDataTemplate="{StaticResource DefaultDataTemplateResource}"
DerivedObject1Template = "{StaticResource DerivedObject1TemplateResource}"
DerivedObject2Template = "{StaticResource DerivedObject2TemplateResource}"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource myCustomTemplateSelector}"/>
</Grid>
</Window>
Hopefully that will get you started!

TreeView incorrectly shows items as expanded when using VirtualizationMode.Recycling

Whenever I used TreeView I always had just few nodes and each of them usually had less than 100 items. I never really needed any kind of ui virtualization for that but now for the first time I need it.
The problem appears when using ui virtualization with recycling mode the TreeView seems to expand items even though I never expanded them manually.
I googled the issue and as far I understood recycling mode of virtualization in TreeView the containers get reused.
So I assume that the cause might be applying already expanded reused container to an item which wasn't expanded before.
Here is a simple example:
https://github.com/devhedgehog/wpf/
For those who cannot download code for whatever reason here is basically what I have tried to do with the TreeView.
This is what I have in XAML.
<Grid>
<TreeView ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Parts}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
And this is code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
IList<Car> list = new List<Car>();
for (int i = 0; i < 5000; i ++)
{
list.Add(new Car() { Name = "test1" + i });
}
foreach (var car in list)
{
car.Parts = new List<string>();
for (int i = 0; i < 500; i++)
{
car.Parts.Add("asdf" + i);
}
}
this.DataContext = list;
}
}
public class Car
{
public string Name
{
get;
set;
}
public List<string> Parts
{
get;
set;
}
}
I hope somebody can provide me a solution to this issue. Is this a known bug?
I am sorry in case its a duplicate. Futhermore I hope you guys tell me what I did wrong since this is my first post before you downgrade the question.
As you probably know, this problem can be solved easily by using standard recycling mode:
<TreeView VirtualizingStackPanel.VirtualizationMode="Standard" ...>
This shouldn't have too much of an impact on your TreeView's performance, as the tree will still be virtualized and a container will only be created for visible items. The benefits of the recycling mode only come into play when scrolling (when items are both being virtualized and realized), and usually the standard virtualization mode is good enough.
However, in case performance is really critical (or if you really want a solution for this while keeping the recycling mode, or if you're looking to do things the right way), you can use backing data and data binding to solve this problem.
The reason why this problem occurs in the first place is this:
Let's say you have a TreeViewItem which has its IsExpanded property set to true. When it's being recycled, i.e. its data is replaced, its IsExpanded property remains the same because it has no way to know whether it should be expanded or not, because that data is not available anywhere. The only place where it exists is the IsExpanded property of the TreeViewItem, and it's not going to be relevant because that item is being reused along with its properties.
If however you have a viewmodel for each tree item you'll be able to bind each TreeViewItem to the IsExpanded property in your TreeViewItemViewModel (you will have a view model for each tree item) and you will always get the correct value because you've made that data available and bound each item to it.
Your TreeView's ItemsSource will be bound to a collection of TreeViewItemViewModel objects, and your TreeViewItemViewModel class will look something like this:
class TreeViewItemViewModel : INotifyPropertyChanged
{
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
TreeViewItemViewModel Parent { get; }
ObservableCollection<TreeViewItemViewModel> Children { get; }
}
You can find more information on how exactly to create such view model in Josh Smith's excellent article Simplifying the WPF TreeView by Using the ViewModel Pattern.

DataGrid: dynamic DataTemplate for dynamic DataGridTemplateColumn

I want to show data in a datagrid where the data is a collection of
public class Thing
{
public string Foo { get; set; }
public string Bar { get; set; }
public List<Candidate> Candidates { get; set; }
}
public class Candidate
{
public string FirstName { get; set; }
public string LastName { get; set; }
...
}
where the number of candidates in Candidates list varies at runtime.
Desired grid layout looks like this
Foo | Bar | Candidate 1 | Candidate 2 | ... | Candidate N
I'd like to have a DataTemplate for each Candidate as I plan changing it during runtime - user can choose what info about candidate is displayed in different columns (candidate is just an example, I have different object). That means I also want to change the column templates in runtime although this can be achieved by one big template and collapsing its parts.
I know about two ways how to achieve my goals (both quite similar):
Use AutoGeneratingColumn event and create Candidates columns
Add Columns manually
In both cases I need to load the DataTemplate from string with XamlReader. Before that I have to edit the string to change the binding to wanted Candidate.
Is there a better way how to create a DataGrid with unknown number of DataGridTemplateColumn?
Note: This question is based on dynamic datatemplate with valueconverter
Edit: As I need to support both WPF and Silverlight, I've created my own DataGrid component which has DependencyProperty for bindig a collection of columns. When the collection changes, I update the columns.
For example we create 2 DataTemplates and a ContentControl:
<DataTemplate DataType="{x:Type viewModel:VariantA}"> <dataGrid...> </DataTemplate>
<DataTemplate DataType="{x:Type viewModel:VariantB}"> <dataGrid...> </DataTemplate>
<ContentControl Content="{Binding Path=GridModel}" />
Now if you set your GridModel Property (for example type object) to VariantA or VariantB, it will switch the DataTemplate.
VariantA & B example Implementation:
public class VariantA
{
public ObservableCollection<ViewModel1> DataList { get; set; }
}
public class VariantB
{
public ObservableCollection<ViewModel2> DataList { get; set; }
}
Hope this helps.
I don't know if this is a "better" way, since this remains pretty ugly, but I personnaly did like this:
make the template in xaml
use a multibind that takes the current binding + a binding to the column to get the "correct" dataContext (i.e.: the cell instead of the row)
use a converter on this binding to get the value of the property you like, an optionally add a parameter if you have many properties to retrieve.
e.g.: (sorry, I did not adapt my code to suit your project, but you should be able to do it yourself from there)
here is my dataTemplate:
<DataTemplate x:Key="TreeCellTemplate">
<Grid>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5,0,0,0">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource RowColumnToCellConverter}" ConverterParameter="Text">
<Binding />
<Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="Column" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
and here is my converter:
public class RowColumnToCellConverter : MarkupExtension, IMultiValueConverter
{
public RowColumnToCellConverter() { }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
XwpfRow row = values[0] as XwpfRow;
XwpfTreeColumn column = values[1] as XwpfTreeColumn;
if (row == null || column == null) return DependencyProperty.UnsetValue;
TreeCell treeCell = (TreeCell)row[column.DataGrid.Columns.IndexOf(column)];
switch ((string)parameter)
{
case "Text": return treeCell.Text;
case "Expanded": return treeCell.Expanded;
case "ShowExpandSymbol": return treeCell.ShowExpandSymbol;
case "CurrentLevel": return new GridLength(treeCell.CurrentLevel * 14);
default:
throw new MissingMemberException("the property " + parameter.ToString() + " is not defined for the TreeCell object");
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new RowColumnToCellConverter();
}
}
this saves the MVVM model, and I prefer this way of doing things because I really dislike using xaml parsers to make "dynamic" datatemplates, but it's still an ugly Hack from my point of view.
I wish the guys at MS would give us a way to get cells instead of rows as dataContexts to be able to generate templated columns on the fly...
hope this helps
EDIT: In your case, the converter ought to be a lot simpler actually (you can return the cell's instance directly if I'm not mistaken, and you don't need any parameter), but I left the more complex version nonetheless, just in case somebody else has a similar issue
I've been looking at a similar problem and have only found a handful of useful patterns. The whole 'dynamic column' problem is an interesting one in silverlight.
Yesterday I found this page Silverlight DataGrid with Dynamic Columns on Travis Pettijohn's site during my searches.
Previously I'd been using the 'index converter' pattern outlined by Colin Eberhardt which works fantastically well... as long as you use DataGridTextColumn. Everything can be done in code behind, and I had no trouble applying styles at run time. However my requirement is now to apply some 'cell level' formatting - change the background for the cell, etc - which means a DataGridTemplateColumn is required.
The big problem with a DataGridTemplateColumn for me was that I can't set the binding in code. I know we can build it by parsing xaml, but like everyone else that seems like a massive hack and unmaintainable to the nth.
The pattern outlined by Travis (the first link above) is completely different. At 'run time' (i.e. page load time), create the columns you need in your grid. This means iterate through your collection, and add a column for each item with the appropriate header etc. Then implement a handler for the RowLoaded event, and when each row is loaded simply set the DataContext for each cell to the appropriate property / property index of the parent.
private void MyGrid_RowLoaded(object sender, EventArgs e)
{
var grid = sender as DataGrid;
var myItem = grid.SelectedItem as MyClass;
foreach (int i = 0; i < myItem.ColumnObjects.Count; i++)
{
var column = grid.Columns[i];
var cell = column.GetCellContent(e.Row)
cell.DataContext = myItem.ColumnObjects[i];
}
}
This has removed the need for me to use the index converter. You can probably use a Binding when setting the cell.DataContext but for me it's easier to have the template simply bind directly to the underlying object.
I now plan on having multiple templates (where each can bind to the same properties on my cell object) and switching between them at page load. Very tidy solution.

Access DataGridCell's children from another DataGridCell?

I have a DataGridCell that contains a ComboBox.
I want, that when I fire 'SelectionChanged' event of it, a CollectionViewSource of a different column (eventually - at runtime, cell) CellEditingTemplate's Resources should be populated with data according to the selected value for this row.
Maybe DataTrigger, ActionTrigger, EventTrigger, maybe by code, XAML I don't care, I just need a solution.
Thanks a lot!
Related: Accessing control between
DataGridCells, dynamic cascading
ComboBoxes
If I understand your question right, you will fill the contents of a combobox in a cell based on the selection of a combobox in another cell that is in the same row of the DataGrid.
If yes:
First Solution (IMO the preferable)
Make a ViewModel that represents the rows data (a simple wrapper around your data object). Bind the ItemsSource-property of the destination ComboBox to a IEnumerable-property that you provide from your viewmodel.
Bind the SelectedItem from the source-ComboBox to another property of your ViewModel. Every time this source-property changes in your ViewModel, you change the contents of the list that is provided by the ViewModel.
Use for the desintation (list) property a ObservableCollection<T>. The source property is up to you.
Here is an approximately example. I call the class VM (for ViewModel) but this changes nothing on your current solution. MVVM can also be used partial.
public class DataObjectVM : DependencyObject {
public static readonly DependencyProperty SelectedCategoryProperty =
DependencyProperty.Register("SelectedCategory", typeof(CategoryClass), typeof(DataObjectVM), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,delegate (DependencyObject d,DependencyPropertyChangedEventArgs e){
((DataObjectVM)d).SelectedCategoryChanged(e);
}));
ObservableCollection<ItemClass> _items=new ObservableCollection<ItemClass>();
void SelectedCategoryChanged(DependencyPropertyChangedEventArgs e) {
// Change here the contents of the _items collection.
// The destination ComboBox will update as you desire
// Do not change the _items reference. Only clear, add, remove or
// rearange the collection-items
}
// Bind the destination ComboxBox.ItemsSource to this property
public IEnumerable<ItemClass> DestinationItems {
get {
return _items;
}
}
// Bind to this property with the source ComboBox.SelectedItem
public CategoryClass SelectedCategory {
get { return (CategoryClass)GetValue(SelectedCategoryProperty); }
set { SetValue(SelectedCategoryProperty, value); }
}
}
Add a constructor to this class that takes your data object and make some wrapper properties to the rest the properties you need to provide in the DataGrid. If they are alot, you can also make one property that provides your data object and the bind directly to it. Not nice, but it will do the job.
You also can (must) pre-initialize the SelectedCategory with data from your business object. Do this also in the constructor.
As a ItemsSource for the DataGrid you give an IEnumerable of the DataObjectVM-class that wrapps all items you want to show.
Alternative way with VisualTreeHelper
If you want to do it manual, register in the code behind a handler for the ComboBox.SelectionChangedEvent and change then the ItemsSource of the destination ComboBox manual. The business-object you will get with the EventArgs. The destination ComboBox you must search in the visual tree (Use the VisualTreeHelper). The events can be wired also if you use the DataGridTemplateColumn class and add a DataTemplate with the corresponding ComboBoxes.
But I think this is realy not very simple to do and can be error prone. The above solution is much easier.
Here is the code you propably are looking for:
private void CboSource_SelectionChanged(object sender, SelectionChangedEventArgs e) {
ComboBox cbo = (ComboBox)sender;
FrameworkElement currentFe = VisualTreeHelper.GetParent(cbo) as FrameworkElement;
while (null != currentFe && !(currentFe is DataGridRow)) {
currentFe = VisualTreeHelper.GetParent(currentFe) as FrameworkElement;
}
if (null != currentFe) {
List<ComboBox> list = new List<ComboBox>();
FindChildFrameworkElementsOfType<ComboBox>(currentFe,list);
// Requirement 1: Find ComboBox
foreach (ComboBox cboFound in list) {
if (cboFound.Name == "PART_CboDestination") {
// This is the desired ComboBox
// Your BO is available through cbo.Found.DataContext property
// If don't like to check the name, you can also depend on the
// sequence of the cbo's because I search them in a deep search
// operation. The sequence will be fix.
}
}
List<DataGridCell> cells = new List<DataGridCell>();
FindChildFrameworkElementsOfType<DataGridCell>(currentFe,cells);
// Requirement 2: Find Sibling Cell
foreach (DataGridCell cell in cells) {
// Here you have the desired cell of the other post
// Take the sibling you are interested in
// The sequence is as you expect it
DataGridTemplateColumn col=cell.Column as DataGridTemplateColumn;
DataTemplate template = col.CellTemplate;
// Through template.Resources you can access the CollectionViewSources
// if they are placed in the CellTemplate.
// Change this code if you will have an edit cell template or another
// another construction
}
}
}
void FindChildFrameworkElementsOfType<T>(DependencyObject parent,IList<T> list) where T: FrameworkElement{
DependencyObject child;
for(int i=0;i< VisualTreeHelper.GetChildrenCount(parent);i++){
child = VisualTreeHelper.GetChild(parent, i);
if (child is T) {
list.Add((T)child);
}
FindChildFrameworkElementsOfType<T>(child,list);
}
}
And this is the markup I used:
<DataGrid.Columns>
<DataGridTemplateColumn Header="Source" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="PART_CboSource" SelectionChanged="CboSource_SelectionChanged" ItemsSource="!!YOUR ITEMS SOURCE!!" SelectedItem="{Binding Category}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Destination">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="PART_CboDestination"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
Accessing the CollectionViewSource
To access the CollectionViewSource, put it into the resources section of the corresponding DataTemplate, not of the panel, then you will have direct access to them. IMO is this location anyway more appropriate than the resources-container of the grid.
If you dont't want to do this, check the state of the following post:
How to get logical tree of a DataTemplate

Resources