Home > Back-end >  How to bind dependency property to DataGridComboBoxColumn in UserControl?
How to bind dependency property to DataGridComboBoxColumn in UserControl?

Time:11-19

The XAML of my UserControl with name "Clinical_Protocol":

<Grid>
        <DataGrid Name="ClinicalProtocolDataGrid"
                  ItemsSource="{Binding DataGridItems, ElementName=Clinical_Protocol}"
                  AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridComboBoxColumn Header="Structure ID"
                                        ItemsSource="{Binding ComboBoxItems, ElementName=Clinical_Protocol, Mode=TwoWay}"
                                        SelectedItemBinding="{Binding SelectedStructureId, Mode=TwoWay}"
                                        />
                <DataGridTextColumn Header="RT ROI Type Code" 
                                    Binding="{Binding RtRoiInterpretedTypeCode}"/>
                <DataGridTextColumn Header="RT ROI Type Description"
                                    Binding="{Binding RtRoiInterpretedTypeDescription}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>

The dependency property for ItemsSource of the DataGrid in the code behind:

internal ObservableCollection<ClinicalProtocolDataGridItem> DataGridItems
{
    get { return (ObservableCollection<ClinicalProtocolDataGridItem>)GetValue(DataGridItemsProperty); }
    set { SetValue(DataGridItemsProperty, value); }
}

internal static readonly DependencyProperty DataGridItemsProperty =
            DependencyProperty.Register("DataGridItems", typeof(ObservableCollection<ClinicalProtocolDataGridItem>),
                typeof(ClinicalProtocolView), new PropertyMetadata(null, DataGridItemsPropertyChangedCallback));

private static void DataGridItemsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var control = (ClinicalProtocolView)d;
    control.DataGridItems = (ObservableCollection<ClinicalProtocolDataGridItem>)e.NewValue;
}

The according ClinicalProtocolDataGridItem class:

public class ClinicalProtocolDataGridItem
{
    public string RtRoiInterpretedTypeCode { get; }
    public string RtRoiInterpretedTypeDescription { get; }
    public object SelectedStructureId { get; set; }

    public ClinicalProtocolDataGridItem(string rtRoiInterpretedTypeCode, string rtRoiInterpretedTypeDescription)
    {
        RtRoiInterpretedTypeCode = rtRoiInterpretedTypeCode ??
                                       throw new ArgumentNullException(nameof(rtRoiInterpretedTypeCode));
        RtRoiInterpretedTypeDescription = rtRoiInterpretedTypeDescription ??
                                              throw new ArgumentNullException(nameof(rtRoiInterpretedTypeDescription));
    }
}

And finally the dependency property ComboBoxItems:

public ObservableCollection<ComboBoxItem> ComboBoxItems
{
    get { return (ObservableCollection<ComboBoxItem>)GetValue(ComboBoxItemsProperty); }
    set { SetValue(ComboBoxItemsProperty, value); }
}

public static readonly DependencyProperty ComboBoxItemsProperty =
    DependencyProperty.Register("ComboBoxItems", typeof(ObservableCollection<ComboBoxItem>),
        typeof(ClinicalProtocolView), new PropertyMetadata(new ObservableCollection<ComboBoxItem>(),
                    PropertyChangedCallback));

private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var control = (ClinicalProtocolView)d;
    control.ComboBoxItems = (ObservableCollection<ComboBoxItem>)e.NewValue;
}

I try to populate both dependency properties with the following:

private void AddSomeComboBoxItems()
{
    ComboBoxItems = new ObservableCollection<ComboBoxItem>
    {
        new ComboBoxItem(){Content = "S1",  HorizontalContentAlignment = HorizontalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center},
        new ComboBoxItem(){Content = "S2",  HorizontalContentAlignment = HorizontalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center},
        new ComboBoxItem(){Content = "S3",  HorizontalContentAlignment = HorizontalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center},
}

private void AddSomeRows()
{
    var items = new List<ClinicalProtocolDataGridItem>
    {
        new ClinicalProtocolDataGridItem("PTV", "Description of PTV, and it is a very long description!"),
        new ClinicalProtocolDataGridItem("OAR", "Description of OAR, and it is a very long description!"),
    };

    DataGridItems = new ObservableCollection<ClinicalProtocolDataGridItem>(items);
}

When I import it into a WPF application I get the following:

Combo Box Example picture

And the error message: DataGridComboBoxColumn.ItemsSource IEnumerable Cannot find governing FrameworkElement or FrameworkContentElement for target element. Error message

Microsoft documentation says that it is possible to bind a collection of ComboBoxItem. What am I missing? I don't want to have a static resource, because the combo box items might change during runtime.

CodePudding user response:

The DataGridColumn is a simple DependencyObject, not a UI element. It is not embedded in the visual tree and cannot get values from it. The ElementName and FindAncestor bindings will not work in it.

You need to get the collection into a static resource and only then get it from there for the DataGridColumn.

<DataGrid Name="ClinicalProtocolDataGrid"
          ItemsSource="{Binding DataGridItems, ElementName=Clinical_Protocol}"
          AutoGenerateColumns="False">
    <FrameworkElement.Resources>
        <!--Using a CollectionViewSource as a proxy to create a "static link".-->
        <CollectionViewSource
            x:Key="comboBoxItems"
            Source="{Binding ComboBoxItems, ElementName=Clinical_Protocol}"/>
    </FrameworkElement.Resources>
    <DataGrid.Columns>
        <DataGridComboBoxColumn
            Header="Structure ID"
            ItemsSource="{Binding Source={StaticResource comboBoxItems}}"
            SelectedItemBinding="{Binding SelectedStructureId, Mode=TwoWay}"/>

P.S. Please note my comment on your question. I explain there why even after a successful binding to such a source, the ComboBoxs may still work incorrectly.

CodePudding user response:

Remove dependency property ComboBoxItems you don't need it, but add to the code behind a data provider method for your combo boxes:

public ObservableCollection<string> GetCbxSource()
{
    return new ObservableCollection<string>
    {
        "S1",
        "S2",
        "S3",
    };
}

Then in XAML you can declare a data provider and later use it like:

<Grid>
    <Grid.Resources>
        <ObjectDataProvider x:Key="cbxData" MethodName="GetCbxSource" ObjectInstance="{x:Reference Clinical_Protocol}"/>
    </Grid.Resources>

    <DataGrid Name="ClinicalProtocolDataGrid"
                  ItemsSource="{Binding DataGridItems, ElementName=Clinical_Protocol}"
                  AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridComboBoxColumn Header="Structure ID"
                                        ItemsSource="{Binding Source= {StaticResource cbxData}}"
                                        SelectedItemBinding="{Binding SelectedStructureId, Mode=TwoWay}"
                                        />
            <DataGridTextColumn Header="RT ROI Type Code" 
                                    Binding="{Binding RtRoiInterpretedTypeCode}"/>
            <DataGridTextColumn Header="RT ROI Type Description"
                                    Binding="{Binding RtRoiInterpretedTypeDescription}"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>
  • Related