Using WPF DataGridComboBoxColumn with MVVM - Binding to Property in ViewModel
Asked Answered
T

3

11

I'm using the excellent MVVM Light Toolkit. My ViewModel exposes:

public const string CourtCodesTypeCourtPropertyName = "CourtCodesTypeCourt";
private List<CourtType> _courtCodesTypes = new List<CourtType>();
public List<CourtType> CourtCodesTypeCourt
{
    get
    {
        return _courtCodesTypes;
    }

    set
    {
        if (_courtCodesTypes == value)
        {
            return;
        }

        var oldValue = _courtCodesTypes;
        _courtCodesTypes = value;

        // Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
        RaisePropertyChanged(CourtCodesTypeCourtPropertyName, oldValue, value, true);
    }
}

public const string CourtCodesPropertyName = "CourtCodes";
private List<Court> _courtCodes = null;
public List<Court> CourtCodes
{
    get
    {
        return _courtCodes;
    }

    set
    {
        if (_courtCodes == value)
        {
            return;
        }

        var oldValue = _courtCodes;
        _courtCodes = value;

        // Update bindings and broadcast change using GalaSoft.Utility.Messenging
        RaisePropertyChanged(CourtCodesPropertyName, oldValue, value, true);
    }
}

The View has a DataGrid:

<DataGrid
      ItemsSource="{Binding CourtCodes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
      AutoGenerateColumns="False"
      AlternatingRowBackground="{DynamicResource OffsetBrown}"
      AlternationCount="1" Margin="45,0">
   <DataGrid.Columns>
    <DataGridTextColumn Binding="{Binding Abbreviation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
         Header="Abbreviation"
         Width="25*" />
    <DataGridTextColumn Binding="{Binding FullName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
         Header="Court"
         Width="75*" />
    <DataGridComboBoxColumn Header="CourtType" 
         ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CourtCodesTypeCourt} TextBinding="{Binding CourtTypeDescription}""/>
   </DataGrid.Columns>
  </DataGrid>

The DataGrid has an ItemsSource, as you can see, of CourtCodes. I want the CourtType column to be a drop down of all enumerated CourtTypes that are contained within CourtCodesTypeCourt. For the life of me, I can't seem to populate the DataGridComboBoxColumn with anything. The current failed attempt is looking to use RelativeSource... what am I doing wrong?

In addition to not working, the two errors I see are:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.CourtCodesTypeCourt; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=38771709); target property is 'ItemsSource' (type 'IEnumerable')

and

System.Windows.Data Error: 40 : BindingExpression path error: 'CourtCodesTypeCourt' property not found on 'object' ''Court' (HashCode=38141773)'. BindingExpression:Path=CourtCodesTypeCourt.CourtTypeDescription; DataItem='Court' (HashCode=38141773); target element is 'ComboBox' (Name=''); target property is 'Text' (type 'String')

Thankful answered 25/8, 2010 at 4:51 Comment(0)
B
28

DataGrid column definitions don't participate in the logical tree in the way you would expect. It's ridiculous, but last I checked you have to do something like this:

<DataGridComboBoxColumn Header="CourtType" SelectedItemBinding="{Binding Type}">
    <DataGridComboBoxColumn.ElementStyle>
        <Style TargetType="ComboBox">
            <Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CourtCodesTypeCourt}"/>
            <Setter Property="IsReadOnly" Value="True"/>
        </Style>
    </DataGridComboBoxColumn.ElementStyle>
    <DataGridComboBoxColumn.EditingElementStyle>
        <Style TargetType="ComboBox">
            <Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CourtCodesTypeCourt}"/>
        </Style>
    </DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>

You'll notice I've also changed your TextBinding to a SelectedItemBinding. I'm not sure if you actually intended a TextBinding, but if you just want to allow the user to select between the list, then SelectedItemBinding is likely what you want.

Also, your VMs don't exactly follow best practices. You're using List<T> instead of ObservableCollection<T>, and you're exposing it as List<T> rather than something simpler such as ICollection<T>.

Baun answered 25/8, 2010 at 8:35 Comment(5)
Thanks for your help, Kent... this definitely gets me closer. I agree... it is ridiculous to have to resort to this kind of syntax. I'm struggling. I now have the CourtType objects populating the DataGridComboBoxColumn, but within this syntax, how do I control the displayed text? Currently it lists the type name, not it's CourtTypeDescription property. Secondly, I appreciate your feedback regarding best practices... I was, perhaps mistakenly, under the impression that the MVVM Light Toolkit made mvvminpc snippet-created properies into ObservableCollections... am I off on that too?Thankful
Ignore the ObservableCollections best practice question... I tested it a bit and see the value. The ability to hook into the CollectionChanged event seems well worth it on it's own right. I could use a nudge on the syntax for the DataGridComboBoxColumn... thanks again for your help!Thankful
@Mike: you should just be able to set DisplayMemberPath="CourtTypeDescription" on your DataGridComboBoxColumn.Baun
+1 The RelativeSource suggestion shown put me on the right track to solve a different issue with DataGrid Style bindings. Many thanks! :)Mandolin
be sure to remember to add 'DataContext' to Path (as shown here). I've made this mistake several timesHeal
G
4

Here I have found answer http://cinch.codeplex.com/discussions/239522

For the DataGridComboBoxColumn you have to create a StaticRecource of the ItemsSource like:

<CollectionViewSource Source="{Binding Element=theView, Path=DataContext.ViewModelCollection1}" x:Key="ViewModelCollection1" />

and bind it to the DataGridComboBoxColumn with following:

ItemsSource="{Binding Source={StaticResource ViewModelCollection1}}"

Thats because the DataGridColumns are not a part of the visual tree.

And if you want to bind on a collection of a item of the DataGrid you have to set the ItemsSource over the two styles:

<DataGridComboBoxColumn.ElementStyle>
    <Style TargetType="ComboBox">
        <Setter Property="ItemsSource" Value="{Binding Path=ModelCollection1}" />
    </Style> </DataGridComboBoxColumn.ElementStyle> <DataGridComboBoxColumn.EditingElementStyle>
    <Style TargetType="ComboBox">
        <Setter Property="ItemsSource" Value="{Binding Path=ModelCollection1}" />
    </Style> </DataGridComboBoxColumn.EditingElementStyle>
Gravel answered 6/6, 2013 at 8:54 Comment(0)
I
0

Check out this brilliant tutorial/example: https://code.msdn.microsoft.com/windowsdesktop/Best-ComboBox-Tutorial-5cc27f82

Imperious answered 30/7, 2015 at 2:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.