WPF DataGrid: как привязать объект для отражения элемента, строка которого проверяется
У меня есть datagrid, заполненная элементами, и флажок для каждого элемента.
Я ищу способ, чтобы объект в моем ViewModel был тем элементом, у которого в данный момент установлен флажок.
Вот мой XAML до сих пор:
<Window x:Class="fun_with_DataGridCheckBoxColumns.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:fun_with_DataGridCheckBoxColumns"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Label Content="Chosen One : " />
<Label Content="{Binding ChosenOne.Name, Mode=OneWay}" />
</StackPanel>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID, Mode=OneWay}" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name, Mode=OneWay}" IsReadOnly="True"/>
<DataGridCheckBoxColumn Header="Is Chosen"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
И мой CS:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace fun_with_DataGridCheckBoxColumns
{
public partial class MainWindow : Window
{
public Person ChosenOne { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = new Viewmodel();
}
}
public class Viewmodel : INotifyPropertyChanged
{
public ObservableCollection<Person> People { get; private set; }
private Person _chosenOne = null;
public Person ChosenOne
{
get
{
if (_chosenOne == null) { return new Person { Name = "Does Not Exist" }; }
else return _chosenOne;
}
set
{
_chosenOne = value;
NotifyPropertyChanged("ChosenOne");
}
}
public Viewmodel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "John" },
new Person { Name = "Marie" },
new Person { Name = "Bob" },
new Person { Name = "Sarah" }
};
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Person
{
private static int person_quantity = 0;
private int _id = ++person_quantity;
public int ID { get { return _id; } }
public string Name { get; set; }
}
}
Вот поведение, которое я ищу:
- ChosenOne в ViewModel становится тем человеком, у которого установлен флажок
- когда флажок установлен, все остальные unchecked
- если флажки не установлены, устанавливает ChosenOne в null
В основном это то же самое поведение, как если бы я поместил это в DataGrid (XAML):
Но в моем случае ChosenOne не может быть SelectedItem datagrid, так как мне нужно SelectedItem для чего-то другого, и я должен использовать флажки по причинам компании.SelectedItem="{Связывание ChosenOne, Mode=TwoWay} "
Я не нашел, как имитировать эту логику" SelectedItem". с флажками.
Я знаю, что могу поместить свойство "bool IsChosen" в свой класс Person и привязать к нему флажок, но я бы предпочел этого избежать. Это будет моим решением, если все остальное потерпит неудачу.
Спасибо.
2 ответа:
Альтернативой было бы обернуть ваш объект чем-то, что поддерживает проверку. Источник
using System.ComponentModel; namespace Jarloo { public class CheckedListItem<T> : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private bool isChecked; private T item; public CheckedListItem() {} public CheckedListItem(T item, bool isChecked=false) { this.item = item; this.isChecked = isChecked; } public T Item { get { return item; } set { item = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item")); } } public bool IsChecked { get { return isChecked; } set { isChecked = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked")); } } } }
Добавьте свойство IsChecked в класс
Person
и реализуйте интерфейсINotifyPropertyChanged
:public class Person : INotifyPropertyChanged { private static int person_quantity = 0; private int _id = ++person_quantity; public int ID { get { return _id; } } public string Name { get; set; } private bool _isChecked; public bool IsChecked { get { return _isChecked; } set { _isChecked = value; NotifyPropertyChanged("IsChecked"); } } public event PropertyChangedEventHandler PropertyChanged; public void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
Свяжите столбец DataGridCheckBoxColumn с этим свойством:
<DataGridCheckBoxColumn Header="Is Chosen" Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
Затем можно обработать логику в классе модели представления. Это должно гарантировать, что только один
Person
выбран одновременно:public class Viewmodel : INotifyPropertyChanged { public ObservableCollection<Person> People { get; private set; } private Person _chosenOne = null; public Person ChosenOne { get { if (_chosenOne == null) { return new Person { Name = "Does Not Exist" }; } else return _chosenOne; } set { _chosenOne = value; NotifyPropertyChanged("ChosenOne"); } } public Viewmodel() { People = new ObservableCollection<Person> { new Person { Name = "John" }, new Person { Name = "Marie" }, new Person { Name = "Bob" }, new Person { Name = "Sarah" } }; foreach(Person p in People) p.PropertyChanged += P_PropertyChanged; } private bool handle = true; private void P_PropertyChanged(object sender, PropertyChangedEventArgs e) { if(handle && e.PropertyName == "IsChecked") { handle = false; //uncheck all other persons foreach (Person p in People) if(p != sender) p.IsChecked = false; ChosenOne = sender as Person; handle = true; } } public event PropertyChangedEventHandler PropertyChanged; public void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
Если вы планируете добавлять новые объекты
Person
в коллекцию People динамически во время выполнения, вы также должны убедиться, что вы обрабатываете событиеPropertyChanged
для них:public Viewmodel() { People = new ObservableCollection<Person> { new Person { Name = "John" }, new Person { Name = "Marie" }, new Person { Name = "Bob" }, new Person { Name = "Sarah" } }; foreach (Person p in People) p.PropertyChanged += P_PropertyChanged; People.CollectionChanged += (s, e) => { if (e.NewItems != null) { foreach (object person in e.NewItems) { (person as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(P_PropertyChanged); } } if (e.OldItems != null) { foreach (object person in e.OldItems) { (person as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(P_PropertyChanged); } } }; People.Add(new Person() { Name = "New..." }); }