【问题标题】:Check box in Datagrid wpf c# applicationDatagrid wpf c#应用程序中的复选框
【发布时间】:2012-11-24 21:36:47
【问题描述】:

我正在使用数据网格为学生记录创建 C# wpf 应用程序。 如何在标题中创建复选框以选择/取消选择一行中的所有复选框? 如何一键选中行中的复选框,以便我们可以编辑/删除记录?以及我们如何选择多个复选框来删除?

【问题讨论】:

    标签: c# wpf datagrid


    【解决方案1】:

    我创建了一种行为,允许控件中的属性绑定到项目集合的属性,其方式如下:

    • 如果您更改控件中的属性,所有项目都会更新。
    • 如果更改项目中的属性,如果所有项目都具有相同的属性,则控件将反映它。如果没有,控件的属性将被赋予一个后备值(如 null)。

    通过这种行为,您可以向 DataGrid 标题添加一个复选框,并将其 IsChecked 属性绑定到 DataGrid 的 ItemSource,绑定到集合类型的属性。

    我们可以使用 MVVM 模式处理选择逻辑。例如,我们有以下集合实体的 ViewModel:

    public class ItemViewModel : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    
        #endregion
    
        private bool isSelected;
        public bool IsSelected {
            get { return this.isSelected; }
    
            set
            {
                if (this.isSelected == value)
                    return;
    
                this.isSelected = value;
                this.OnPropertyChanged("IsSelected");
            }
        }
    
        private string myProperty;
        public string MyProperty
        {
            get { return this.myProperty; }
            set
            {
                if (this.myProperty != value)
                {
                    this.myProperty = value;
                    this.OnPropertyChanged("MyProperty");
                }
            }
        }
    }
    

    然后我们有 MainViewModel,它控制着 MainWindow 的逻辑:

    public class MainViewModel: INotifyPropertyChanged
    {
        #region INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    
        #endregion
    
        #region Items (INotifyPropertyChanged Property)
        private ObservableCollection<ItemViewModel> items;
    
        public ObservableCollection<ItemViewModel> Items
        {
            get { return this.items; }
            set
            {
                if (this.items != value)
                {
                    this.items = value;
                    this.OnPropertyChanged("Items");
                }
            }
        }
        #endregion
    
        public MainViewModel()
        {
            this.Items = new ObservableCollection<ItemViewModel>();
            for (int i = 0; i < 10; i++)
                this.Items.Add(new ItemViewModel() { MyProperty = "Item" + i });
        }
    }
    

    在我们的窗口中,我们可以声明 DataGrid。为了实现你想要的,我们必须做到以下几点:

    • 将 MainViewModel 分配给 Window.DataContext
    • 将 DataGrid.ItemsSource 绑定到 MainViewModel 中的 Items 属性
    • 定义 DataGrid 的列。在示例中,我选择使用“IsSelected”列并将“SelectAll”复选框添加到其标题中,如您指定的那样,但您可以在任何地方使用 CheckBox 来控制选择。
    • 我们需要使行的选择更新我们项目的“IsSelected”属性,反之亦然。为此,我们修改 RowStyle,以便我们可以将行的“IsSelected”属性绑定到其项目的“IsSelected”。这样,选择逻辑现在可以完全由 ViewModel 驱动。
    • 最后要做的是让“SelectAll”复选框完成其工作。我们应用 CollectionPropertyBehavior 并对其进行配置,使其“SourcePropertyPath”指向我们要绑定的 CheckBox 中的属性(“IsChecked”),而 CollectionPropertyPath 指向项目中的属性(“IsSelected”)。然后我们只需要将其 ItemsSource 绑定到 DataGridItemsSource。注意默认值为“null”,这意味着当项目的属性值不同时,CheckBox 将接收“null”并处于未定义状态。

    最终的 xaml 将是这样的:

    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication2" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" x:Class="WpfApplication2.MainWindow"
        Title="MainWindow" mc:Ignorable="d" Height="350" Width="525">
    <Window.Resources>
        <Style x:Key="DataGridRowStyle" TargetType="{x:Type DataGridRow}">
            <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
        </Style>
    </Window.Resources>
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Items}" CanUserAddRows="False" RowStyle="{DynamicResource DataGridRowStyle}">
            <DataGrid.Columns>
                <DataGridCheckBoxColumn Binding="{Binding IsSelected}">
                    <DataGridCheckBoxColumn.Header>
                        <CheckBox>
                            <i:Interaction.Behaviors>
                                <local:CollectionPropertyBehavior CollectionPropertyPath="IsSelected" SourcePropertyPath="IsChecked" ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                            </i:Interaction.Behaviors>
                        </CheckBox>                                 
                    </DataGridCheckBoxColumn.Header>            
                </DataGridCheckBoxColumn>
                <DataGridTextColumn Width="*" Binding="{Binding MyProperty}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
    

    最后,行为:

    public class CollectionPropertyBehavior : Behavior<DependencyObject>
    {
        private IEnumerable<ValueProxy> proxies;
        private bool syncking;
    
        public string SourcePropertyPath
        {
            get { return (string)GetValue(SourcePropertyPathProperty); }
            set { SetValue(SourcePropertyPathProperty, value); }
        }
        public static readonly DependencyProperty SourcePropertyPathProperty =
            DependencyProperty.Register("SourcePropertyPath", typeof(string), typeof(CollectionPropertyBehavior), new PropertyMetadata(null));
    
        public string CollectionPropertyPath
        {
            get { return (string)GetValue(CollectionPropertyPathProperty); }
            set { SetValue(CollectionPropertyPathProperty, value); }
        }
        public static readonly DependencyProperty CollectionPropertyPathProperty =
            DependencyProperty.Register("CollectionPropertyPath", typeof(string), typeof(CollectionPropertyBehavior), new PropertyMetadata(null));
    
    
        private IEnumerable<object> Items { get { return  this.ItemsSource == null ? null : this.ItemsSource.OfType<object>(); } }
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CollectionPropertyBehavior), new PropertyMetadata(null, ItemsSourceChanged));
    
    
        private object Value
        {
            get { return (object)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        private static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(object), typeof(CollectionPropertyBehavior), new PropertyMetadata(null, ValueChanged));
    
    
        public object DefaultValue
        {
            get { return (object)GetValue(DefaultValueProperty); }
            set { SetValue(DefaultValueProperty, value); }
        }
        public static readonly DependencyProperty DefaultValueProperty =
            DependencyProperty.Register("DefaultValue", typeof(object), typeof(CollectionPropertyBehavior), new PropertyMetadata(null));
    
    
    
        private static void ValueChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            var element = sender as CollectionPropertyBehavior;
            if (element == null || element.ItemsSource == null) return;
    
            element.UpdateCollection();
        }
    
        private static void ItemsSourceChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            var element = sender as CollectionPropertyBehavior;
            if (element == null || element.ItemsSource == null) return;
            element.ItemsSourceChanged();
        }
    
        private void ItemsSourceChanged()
        {
            this.proxies = null;
    
            if (this.Items == null || !this.Items.Any() || this.CollectionPropertyPath == null) return;
    
            // Cria os proxies
            this.proxies = this.Items.Select(o =>
            {
                var proxy = new ValueProxy();
                proxy.Bind(o, this.CollectionPropertyPath);
                proxy.ValueChanged += (s, e) => this.UpdateSource();
                return proxy;
            }).ToArray();
    
            this.UpdateSource();
        }
    
        private void UpdateSource()
        {
            if (this.syncking) return;
    
            // Atualiza o valor 
            using (new SynckingScope(this))
            {
                object value = this.proxies.First().Value;
                foreach (var proxy in this.proxies.Skip(1))
                {
                    value = object.Equals(proxy.Value, value) ? value : this.DefaultValue;
                }
    
                this.Value = value;
            }
        }
    
        private void UpdateCollection()
        {
            // Se o valor estiver mudando em função da atualização de algum 
            // elemento da coleção, não faz nada
            if (this.syncking) return;
    
            using (new SynckingScope(this))
            {
                // Atualiza todos os elementos da coleção,
                // atrávés dos proxies
                if (this.proxies != null)
                    foreach (var proxy in this.proxies)
                        proxy.Value = this.Value;
            }
        }
    
        protected override void OnAttached()
        {
            base.OnAttached();
    
    
            // Bind da propriedade do objeto fonte para o behavior
            var binding = new Binding(this.SourcePropertyPath);
            binding.Source = this.AssociatedObject;
            binding.Mode = BindingMode.TwoWay;
            BindingOperations.SetBinding(this, ValueProperty, binding);
        }
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
    
            // Limpa o binding de value para a propriedade do objeto associado
            this.ClearValue(ValueProperty);
        }
    
        internal class SynckingScope : IDisposable
        {
            private readonly CollectionPropertyBehavior parent;
    
            public SynckingScope(CollectionPropertyBehavior parent)
            {
                this.parent = parent;
                this.parent.syncking = true;
            }
    
            public void Dispose()
            {
                this.parent.syncking = false;
            }
        }
    
        internal class ValueProxy : DependencyObject
        {
            public event EventHandler ValueChanged;
    
            public object Value
            {
                get { return (object)GetValue(ValueProperty); }
                set { SetValue(ValueProperty, value); }
            }
            public static readonly DependencyProperty ValueProperty =
                DependencyProperty.Register("Value", typeof(object), typeof(ValueProxy), new PropertyMetadata(null, OnValueChanged));
    
    
            private static void OnValueChanged(object sender, DependencyPropertyChangedEventArgs args)
            {
                var element = sender as ValueProxy;
                if (element == null || element.ValueChanged == null) return;
    
                element.ValueChanged(element, EventArgs.Empty);
            }
    
            public void Bind(object source, string path)
            {
                // Realiza o binding de value com o objeto desejado
                var binding = new Binding(path);
                binding.Source = source;
                binding.Mode = BindingMode.TwoWay;
    
                BindingOperations.SetBinding(this, ValueProperty, binding);
            }
        }
    }
    

    这种方法的优点是完全可重用。在此示例中,我们使用它来处理选择,但您可以将 ItemViewModel 中的枚举属性绑定到标题中的 ComboBox 等等。

    该行为是从我的 Silverlight 4 项目中移植的,但我进行了测试,它在 WPF 应用程序中运行良好。但是,我认为在 WPF 中,我们也许可以找到一种更好的方法,使行为适应 MarkupExtension。如果我有时间,我可能会看看那个。此外,还可以对其进行调整,使其可以绑定到 SelectedItems,这样,当有选定的项目时,它会更新它们,而当没有选定的项目时,它会全部更新。

    【讨论】:

      【解决方案2】:

      这样使用:

      DataGridCheckBoxColumn cbc = new DataGridCheckBoxColumn();
      dataGrid.Columns.Add(cbc);
      CheckBox cb = new CheckBox();
      cbc.Header = cb;
      

      并处理CheckedUnChecked 事件:

      cb.Checked+=new RoutedEventHandler(cb_Checked); 
      cb.Unchecked+=new RoutedEventHandler(cb_Unchecked);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-06-24
        • 1970-01-01
        • 1970-01-01
        • 2016-01-13
        • 2010-10-19
        • 2013-11-01
        • 2011-07-10
        相关资源
        最近更新 更多