簡體   English   中英

如何通知WPF DataGrid中的一行其單元格之一已通過編程方式修改?

[英]How do I notify a row in a WPF DataGrid that one of its cells was programmatically modified?

我正在為WPF項目創建一個DataGridRadioButtonColumn 看起來像這樣:

public class DataGridRadioButtonColumn : DataGridBoundColumn
{
    private Dictionary<DataGridCell, RadioButton> _buttons = new Dictionary<DataGridCell, RadioButton>();

    public string Group { get; set; }

    public static readonly DependencyProperty GroupProperty = RadioButton.GroupNameProperty.AddOwner(
        typeof(DataGridRadioButtonColumn), new FrameworkPropertyMetadata("DefaultGroup"));

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        // Only generate the buttons once, to preserve the Group.
        if (_buttons.ContainsKey(cell))
        {
            return (_buttons[cell]);
        }
        var radioButton = new RadioButton { GroupName = Group };
        BindingOperations.SetBinding(radioButton, ToggleButton.IsCheckedProperty, Binding);
        _buttons.Add(cell, radioButton);
        return radioButton;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
       // Use the same one we generated before.
       return _buttons[cell];
    }

    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
    {
        var radioButton = editingElement as RadioButton;
        if (radioButton == null) return null;
        return radioButton.IsChecked;
    }
}

這是其用法的一個示例:

<local:DataGridRadioButtonColumn
    Width="0.33*"
    Binding="{Binding PrimaryChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
    Group="Group1"
    Header="PRI" />

一切都按我的預期進行,包括單擊單選按鈕“取消選中”最初選擇的那個。 通過組依賴項屬性可以取消選中該選項。

我唯一的問題是未選中的單選按鈕不會在網格中注冊為行編輯。 只有我單擊的單選按鈕才進行行編輯,並且為了正確保存數據,必須保存兩行 (包含我單擊的單選按鈕的和包含未選中的單選按鈕的行)。

如何告訴未選中其單選按鈕的數據行已編輯,以便它正確更新DataGrid綁定集合中的匹配元組?

請注意,我在每行使用的模型中實現了IEditableObject接口,因此它不像僅依靠INotifyPropertyChanged那樣簡單。 確實需要在DataGrid行中觸發BeginEdit() 我不認為以編程方式清除單選按鈕仍然會觸發PropertyChanged ,因為更改不會反映在基礎Model對象中。


根據要求,這是一個MCVE(或者更好的一部分):

APP.XAML

<Application x:Class="WpfApp11.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp11"
             StartupUri="MainWindow.xaml">
    <Application.Resources>

    </Application.Resources>
</Application>

MainWindow.XAML

<Window
    x:Class="WpfApp11.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:local="clr-namespace:WpfApp11"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <DataGrid ItemsSource="{Binding Items, UpdateSourceTrigger=PropertyChanged}">
            <DataGrid.Columns>
                <DataGridTextColumn
                    Width="0.7*"
                    Binding="{Binding Path=Name, Mode=TwoWay}"
                    Header="Name" />
                <local:DataGridRadioButtonColumn
                    Width="0.3*"
                    Binding="{Binding PrimaryChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                    Group="Group1"
                    Header="PRI" />

            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

模型

using PropertyChanged; //Fody

namespace WpfApp11
{
    [AddINotifyPropertyChangedInterface]  // PropertyChanged.Fody
    public class Model
    {
        public string Name { get; set; }
        public bool? PrimaryChecked
        {
            get;
            set;
        }
    }
}

視圖模型

using System.Collections.ObjectModel;
using PropertyChanged; // Fody

namespace WpfApp11
{
    [AddINotifyPropertyChangedInterface] // PropertyChanged.Fody
    public class ViewModel
    {
        public ViewModel()
        {
            Items = new ObservableCollection<Model>
            {
                new Model {Name = "George"},
                new Model {Name = "Fred"},
                new Model {Name = "Tom"},
            };
        }
        public ObservableCollection<Model> Items { get; set; }
    }
}

如您所見,此代碼沒有什么變化。

這是有趣的地方。 讓我們在模型上放置一個IEditableObject實現。 IEditableObject被DataGrid識別; 它允許您為每個數據行提供諸如更改跟蹤和撤消功能之類的功能:

public class Model : EditableValidatableObject<Model>
{
    public string Name { get; set; }
    public bool? PrimaryChecked
    {
        get;
        set;
    }
}

EditableValidatableObject.cs

using PropertyChanged;
using System;
using System.ComponentModel;

namespace WpfApp11
{
    /// <summary>
    /// Provides an implementation of the IEditableObject and INotifyDataErrorInfo interfaces for data transfer objects.
    /// </summary><remarks>
    /// The IEditableObject interface is typically used to capture the BeginEdit, EndEdit, and CancelEdit semantics of a DataRowView.
    /// Making something an IEditableObject enables full editing and undo capabilities in a DataGrid.
    /// 
    /// The INotifyDataErrorInfo implementation uses Validation Attributes to validate the values of properties on the DTO.
    /// This information is used to indicate that a value entered by the user is invalid.
    /// 
    /// See T_Asset.cs and T_TestPoint.cs for usage examples.  
    /// </remarks>
    [AddINotifyPropertyChangedInterface]
    public abstract class EditableValidatableObject<T> : AnnotationValidationViewModel, IEditableObject
    {
        /// <summary>
        /// Constructor, sets up the INotifyDataErrorInfo implementation.
        /// </summary>
        private T Cache { get; set; }

        private object CurrentModel { get { return this; } }

        public RelayCommand CancelEditCommand
        {
            get { return new RelayCommand(CancelEdit); }
        }

        private bool IsDirty
        {
            get
            {
                if (Cache == null) return false;
                foreach (var info in CurrentModel.GetType().GetProperties())
                {
                    if (!info.CanRead || !info.CanWrite)
                        continue;

                    var oldValue = info.GetValue(Cache, null);
                    var currentValue = info.GetValue(CurrentModel, null);

                    if (oldValue == null && currentValue != null)
                        return true;

                    if (oldValue != null && !oldValue.Equals(currentValue))
                        return true;
                }
                return false;
            }
        }

        #region IEditableObject Implementation
        public bool Added { get; set; }
        public bool Edited { get; set; }
        public bool Deleted { get; set; }

        public void BeginEdit()
        {
            Cache = Activator.CreateInstance<T>();
            var type = CurrentModel.GetType();

            //Set Properties of Cache
            foreach (var info in type.GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(CurrentModel, null);
                Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
            }

            if (!Added && !Deleted && IsDirty)
            {
                Edited = true;
            }
        }

        public virtual void EndEdit()
        {
            if (!Added && !Deleted && IsDirty)
            {
                Edited = true;
            }
            Cache = default(T);
        }

        public void CancelEdit()
        {
            if (Cache == null) return;

            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(Cache, null);
                CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
            }
        }
        #endregion
    }
}

AnnotationValidationViewModel不變。 它只是INotifyDataErrorInfo的實現,它使用數據注釋進行驗證。

上面IEditableObject實現的關鍵部分是BeginEdit()方法,數據網格行使用該方法向基礎模型發出信號,表明已發生編輯。 單擊單選按鈕時將調用此方法,但自動取消選中另一個單選按鈕時則不會調用此方法

由於BeginEdit()永遠不會在未經檢查的行上被調用,因此Edited屬性永遠不會被設置。 我依靠Edited屬性知道需要將哪些記錄保存回數據庫。

經過深思熟慮,我決定對DataGridRadioButtonColumn實現進行一些更改。 現在看起來像這樣:

public class DataGridRadioButtonColumn : DataGridBoundColumn
{
    private Dictionary<DataGridCell, RadioButton> _buttons = new Dictionary<DataGridCell, RadioButton>();
    private Dictionary<RadioButton, dynamic> _models = new Dictionary<RadioButton, dynamic>();

    public string Group { get; set; }

    public static readonly DependencyProperty GroupProperty = RadioButton.GroupNameProperty.AddOwner(
        typeof(DataGridRadioButtonColumn), new FrameworkPropertyMetadata("Group1"));

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        if (_buttons.ContainsKey(cell))
        {
            return (_buttons[cell]);
        }
        var radioButton = new RadioButton { GroupName = Group };
        radioButton.Unchecked += RadioButton_Unchecked;

        BindingOperations.SetBinding(radioButton, ToggleButton.IsCheckedProperty, Binding);
        _buttons.Add(cell, radioButton);
        _models.Add(radioButton, dataItem);
        return radioButton;
    }

    private void RadioButton_Unchecked(object sender, RoutedEventArgs e)
    {
        var button = sender as RadioButton;
        dynamic model = _models[button];
        try
        {
            model.Edited = true;
        }
        catch { }
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
       return _buttons[cell];
    }

    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
    {
        var radioButton = editingElement as RadioButton;
        if (radioButton == null) return null;
        return radioButton.IsChecked;
    }
}

運作方式如下。 我添加了一個字典,用於捕獲每個單選按鈕的模型,創建單選按鈕時,將模型添加到新字典中,並將Unchecked事件掛在單選按鈕上:

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        if (_buttons.ContainsKey(cell))
        {
            return (_buttons[cell]);
        }
        var radioButton = new RadioButton { GroupName = Group };
        radioButton.Unchecked += RadioButton_Unchecked; // Added

        BindingOperations.SetBinding(radioButton, ToggleButton.IsCheckedProperty, Binding);
        _buttons.Add(cell, radioButton);
        _models.Add(radioButton, dataItem); // Added
        return radioButton;
    }

然后,在事件觸發時,我只是在Model中設置Edited屬性:

private void RadioButton_Unchecked(object sender, RoutedEventArgs e)
{
    var button = sender as RadioButton;
    dynamic model = _models[button];
    try
    {
        // Notify IEditableObject implementation, if it exists.
        model.Edited = true;
    }
    catch { }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM