Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
516 views
in Technique[技术] by (71.8m points)

c# - MVVM; DataGridTextColumn with a ComboBox in the header, which determines the Binding of the Column

I needed a DataGrid with one or more DataGridTextColumn, which its Binding.Path property change when the selected item of a ComboBox in its header changes. The ItemsSource of this ComboBox should be the list of properties in the model.

However, the Path is not a DependencyProperty and therefore cannot be bound.

I encountered this problem while I was trying to answer a vague question. In fact, I was not sure if this is what he wants and therefore, I thought I send it as a separate Q&A.

Note that if the number of properties in the Model is small, such a column can be presented in a few number of columns. However, when the number of properties increase, this might be useful.

I couldn't find any similar question. This one seems to be similar, however, does not implement MVVM.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Assuming that the model has two properties Name and Desc:

public class Model : INotifyPropertyChanged
{
    string _desc;
    public string Desc { get { return _desc; } set { _desc = value; RaisePropertyChanged("Desc"); } }

    string _name;
    public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }

    public event PropertyChangedEventHandler PropertyChanged;
    internal void RaisePropertyChanged(string propname)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
    }
}

The view model should get a list of these properties (Items) and have a SelectedItem which indicates the current selected property. Other things are usual properties of a view model:

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        Models = new ObservableCollection<Model>() { new Model() {Name = "aaaa", Desc = "desc1" }, new Model() { Name = "bbbbb", Desc="desc2"  } };

        Items = new ObservableCollection<PropertyInfo>(typeof(Model).GetProperties());
        SelectedItem = Items[0];
    }
    ObservableCollection<PropertyInfo> _items;
    public ObservableCollection<PropertyInfo> Items { get { return _items; } set { _items = value; RaisePropertyChanged("Items"); } }

    PropertyInfo _selectedItem;
    public PropertyInfo SelectedItem { get { return _selectedItem; } set {
            _selectedItem = value; RaisePropertyChanged("SelectedItem"); 
        } }

    ObservableCollection<Model> _models;
    public ObservableCollection<Model> Models { get { return _models; }set { _models = value; RaisePropertyChanged("Models"); } }

  public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string propname)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
    }
}

The view needs an MultiBinding to get the selected properties value:

<DataGrid DataContext="{Binding}" ItemsSource="{Binding Models}" AutoGenerateColumns="False">
    <DataGrid.Resources>
        <local:conv1 x:Key="conv1" />
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridTextColumn>
            <DataGridTextColumn.Binding>
                <MultiBinding Converter="{StaticResource conv1}">
                    <Binding />
                    <Binding Path="DataContext.SelectedItem" RelativeSource="{RelativeSource AncestorType=DataGrid, Mode=FindAncestor}" />
                </MultiBinding>
            </DataGridTextColumn.Binding>
            <DataGridTextColumn.Header>
                <ComboBox DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" 
                          ItemsSource="{Binding Items}" SelectedItem ="{Binding SelectedItem, Mode=TwoWay}"/>
            </DataGridTextColumn.Header>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

in which

public class conv1 : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        Model m = values[0] as Model;
        PropertyInfo pi = values[1] as PropertyInfo;
        if (m != null && pi != null)
        {
            return pi.GetValue(m);
        }
        return Binding.DoNothing;
    } 
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    } 
}

and

public partial class MainWindow : Window
{ 
    public MainWindow()
    {  
        this.InitializeComponent();
        DataContext = new ViewModel(); 
    } 
}

which results in this:

enter image description here


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...