首页 > 解决方案 > WPF DataGridCell ValidationErrors 的装饰器显示在错误的位置

问题描述

目前我正在尝试让 DataGrid 使用分层数据并允许同​​时进行多个编辑DataGridCells。到目前为止,它工作正常。

现在验证会引起一些麻烦。我想使用类似的接口在 ViewModel/DataContext 中进行验证IDataErrorInfo

如果我启动我的应用程序,每个ValidationError-Border 都会显示在其指定位置。但是,如果我稍微玩一下,ValidationError-Border 就不再是他们应该在的地方了。不知何故,如果绑定到的通知它,则Adorners每次都不会更新。ObservableCollectionItemSourceDataGridCollectionChangedEvent

你自己看:

在此处输入图像描述

我以前从未经历过这样的行为,因此我不知道如何解决它。我想出的唯一“解决方法”是调整整个Window. 这似乎会导致DataGrid/的完整渲染更新,Adorners并且ValidationError-Borders 会显示在应有的位置。然而,这根本不能令人满意。

我想知道这个错误的原因以及如何解决它。我将不胜感激任何提示和指示

重现所需的代码:

可编辑数据网格.cs:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace EditableTreeDataGridDemo
{
public class EditableDatagrid : DataGrid
{
    /// <summary>
    /// Overrides the default "paste" feature, so the clipboard content is written to every selected cell
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        if (e.Key == Key.V && Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
        {
            if (Clipboard.ContainsText())
            {
                this.SetSelectedCellsText(Clipboard.GetText().TrimEnd('\r', '\n'));
            }

            e.Handled = true;
        }
        else if (e.Key == Key.F5)
        {
            this.UpdateRendering(); //Workaraound
        }

        base.OnPreviewKeyDown(e);
    }

    /// <summary>
    /// Gets the upmost Parent
    /// </summary>
    /// <param name="element"></param>
    /// <returns></returns>
    private static FrameworkElement GetRoot(FrameworkElement element)
    {
        return element.Parent == null ? element : GetRoot((FrameworkElement)element.Parent);
    }

    /// <summary>
    /// Attempted Workaroud to Force redraw of the Datagrid (Press F5)
    /// </summary>
    /// <param name="o"></param>
    private void UpdateRendering()
    {
        // I Tried following to force die Datagrid to update the rendering of the Validation Error Borders, but nothing worked so far:

        // 1. Try works sometimes:
        //IEnumerable dataSource = d.ItemsSource;
        //d.ItemsSource = null;
        //d.ItemsSource = dataSource;

        // 2. Try works sometimes:
        //d.Items.Refresh();

        // 3. Try works sometimes:
        //CollectionViewSource.GetDefaultView(d.ItemsSource).Refresh();

        // 4. Try works: I noticed that if i resize the Window with the Mouse, the Datagrid updates the rendering correcty. So i tried to do this programmatically:
        Window window = GetRoot((FrameworkElement)this.Parent) as Window;
        window.Height++;
        window.Height--;
        window.Width++;
        window.Width--;

    }

    /// <summary>
    /// If a single cell leaves the edit mode, the contents of this cell is copied to every selected cell
    /// </summary>
    /// <param name="e"></param>
    protected override void OnCellEditEnding(DataGridCellEditEndingEventArgs e)
    {
        base.OnCellEditEnding(e);

        string typeName = e.EditingElement.GetType().Name;

        switch (typeName)
        {
            case nameof(TextBox):
                this.SetSelectedCellsText(((TextBox)e.EditingElement).Text);
                break;
            default:
                break;
        }
    }

    /// <summary>
    /// Helper method that writes a given text to each selected cell
    /// </summary>
    /// <param name="text"></param>
    private void SetSelectedCellsText(string text)
    {
        foreach (DataGridCellInfo ci in this.SelectedCells)
        {
            if (ci != null)
            {
                DataGridCell c = GetDataGridCell(ci);

                if (c != null)
                {
                    TextBlock t = c.Content as TextBlock;
                    if (t != null)
                    {
                        t.Text = text;
                    }
                }
            }
        }
    }

    /// <summary>
    /// Deactivates the BeginEdit checks of the base Datagrid, so that multiple cells can be edited at the same time
    /// </summary>
    /// <param name="e"></param>
    protected override void OnCanExecuteBeginEdit(CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
        e.Handled = true;
    }

    /// <summary>
    /// Gets a <see cref="DataGridCell" /> from <see cref="DataGridCellInfo" />
    /// </summary>
    /// <param name="cellInfo"></param>
    /// <returns></returns>
    private static DataGridCell GetDataGridCell(DataGridCellInfo cellInfo)
    {
        FrameworkElement cellContent = cellInfo.Column.GetCellContent(cellInfo.Item);

        if (cellContent != null)
        {
            return (DataGridCell)cellContent.Parent;
        }

        return null;
    }
}
}

分层数据.cs:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace EditableTreeDataGridDemo
{
    [Serializable]
    public class HierarchialData<T> : ObservableCollection<HierarchialDataModel<T>>
    {
        private List<HierarchialDataModel<T>> rootNodes;

        public List<HierarchialDataModel<T>> RootNodes
        {
            get
            {
                return this.rootNodes;
            }
            set
            {
                this.rootNodes = value;
            }
        }

        public List<HierarchialDataModel<T>> AllNodes
        {
            get
            {
                return this.rootNodes.SelectMany(n => n.Flatten()).ToList();
            }
        }

        public HierarchialData()
        {
            this.rootNodes = new List<HierarchialDataModel<T>>();
        }

        public void Initialize()
        {
            this.Clear();

            foreach (HierarchialDataModel<T> m in this.rootNodes.Where(c => c.IsVisible).SelectMany(x => new[] { x }.Concat(x.VisibleDescendants)))
            {
                this.Add(m);
            }
        }

        public void AddChildren(HierarchialDataModel<T> d)
        {
            if (!this.Contains(d))
            {
                return;
            }

            int parentIndex = this.IndexOf(d);

            foreach (HierarchialDataModel<T> c in d.Children)
            {
                parentIndex += 1;
                this.Insert(parentIndex, c);
            }
        }

        public void RemoveChildren(HierarchialDataModel<T> d)
        {
            foreach (HierarchialDataModel<T> c in d.Children)
            {
                if (this.Contains(c))
                {
                    this.Remove(c);
                }
            }
        }
    }
}

HierarchialDataModel.cs:

using System;
using System.Collections.Generic;
using System.Linq;

namespace EditableTreeDataGridDemo
{
    [Serializable]
    public class HierarchialDataModel<T>
    {
        private bool isExpanded = false;
        private bool isVisible = false;
        private HierarchialDataModel<T> parent;
        private HierarchialData<T> dataManager;
        private T data;
        private int level = -1;
        private List<HierarchialDataModel<T>> children;

        public HierarchialDataModel()
        {
            this.Children = new List<HierarchialDataModel<T>>();
        }

        public HierarchialDataModel(HierarchialData<T> dataManager, T data) : this()
        {
            this.dataManager = dataManager;
            this.data = data;
        }

        public bool HasChildren
        {
            get
            {
                return this.Children.Count > 0;
            }
        }

        public List<HierarchialDataModel<T>> Children
        {
            get
            {
                return this.children;
            }
            set
            {
                this.children = value;
            }
        }

        public T Data // the Data (Specify Binding as such {Binding Data.Field})
        {
            get
            {
                return this.data;
            }

            set
            {
                this.data = value;
            }
        }

        public HierarchialDataModel<T> Parent
        {
            get
            {
                return this.parent;
            }

            set
            {
                this.parent = value;
            }
        }

        public HierarchialData<T> DataManager
        {
            get
            {
                return this.dataManager;
            }

            set
            {
                this.dataManager = value;
            }
        }

        public void AddChild(HierarchialDataModel<T> t)
        {
            t.Parent = this;
            this.Children.Add(t);
        }

        public int Level
        {
            get
            {
                if (this.level == -1)
                {
                    this.level = (this.Parent != null) ? this.Parent.Level + 1 : 0;
                }

                return this.level;
            }
        }

        public bool IsExpanded
        {
            get
            {
                return this.isExpanded;
            }
            set
            {
                if (this.isExpanded != value)
                {
                    this.isExpanded = value;

                    if (this.isExpanded)
                    {
                        this.Expand();
                    }
                    else
                    {
                        this.Collapse();
                    }
                        
                }
            }
        }

        public bool IsVisible
        {
            get
            {
                return this.isVisible;
            }
            set
            {
                if (this.isVisible != value)
                {
                    this.isVisible = value;

                    if (this.isVisible)
                    {
                        this.ShowChildren();
                    }
                    else
                    {
                        this.HideChildren();
                    }
                }
            }
        }

        public IEnumerable<HierarchialDataModel<T>> Flatten()
        {
            return new[] { this }.Concat(this.children.SelectMany(x => x.Flatten()));
        }

        public IEnumerable<HierarchialDataModel<T>> VisibleDescendants
        {
            get
            {
                return this.Children
                    .Where(x => x.IsVisible)
                    .SelectMany(x => (new[] { x }).Concat(x.VisibleDescendants));
            }
        }

        private void Collapse()
        {
            this.DataManager.RemoveChildren(this);

            foreach (HierarchialDataModel<T> d in this.Children)
            {
                d.IsVisible = false;
            }
        }

        private void Expand()
        {
            this.DataManager.AddChildren(this);

            foreach (HierarchialDataModel<T> d in this.Children)
            {
                d.IsVisible = true;
            }
        }

        // Only if this is Expanded
        private void HideChildren()
        {
            if (this.IsExpanded)
            {
                // Following Order is Critical
                this.DataManager.RemoveChildren(this);

                foreach (HierarchialDataModel<T> d in this.Children)
                {
                    d.IsVisible = false;
                }
            }
        }

        private void ShowChildren()
        {
            if (this.IsExpanded)
            {
                // Following Order is Critical
                this.DataManager.AddChildren(this);

                foreach (HierarchialDataModel<T> d in this.Children)
                {
                    d.IsVisible = true;
                }
            }
        }
    }
}

MainWindowViewModel.cs:

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace EditableTreeDataGridDemo
{
    class MainWindowViewModel : INotifyPropertyChanged
    {
        private HierarchialData<Row> dataManager;

        public MainWindowViewModel()
        {
            this.Load();
        }

        private static void AddDummyData(List<Row> rowList)
        {
            rowList.Add(new Row() { Cell0 = "Cell0.01", Cell1 = "Cell1.01", Cell2 = "Cell2.01", Cell3 = "Cell3.01", Cell4 = "Cell4.01", Cell5 = "Cell5.01", Cell6 = "Cell6.01", Cell7 = "Cell7.01" });
            rowList.Add(new Row() { Cell0 = "Cell0.02", Cell1 = "Cell1.02", Cell2 = "Cell2.02", Cell3 = "Cell3.02", Cell4 = "Cell4.02", Cell5 = "Cell5.02", Cell6 = "Cell6.02", Cell7 = "Cell7.02" });
            rowList.Add(new Row() { Cell0 = "Cell0.03", Cell1 = "Cell1.03", Cell2 = "Cell2.03", Cell3 = "Cell3.03", Cell4 = "Cell4.03", Cell5 = "Cell5.03", Cell6 = "Cell6.03", Cell7 = "Cell7.03" });
            rowList.Add(new Row() { Cell0 = "Cell0.04", Cell1 = "Cell1.04", Cell2 = "Cell2.04", Cell3 = "Cell3.04", Cell4 = "Cell4.04", Cell5 = "Cell5.04", Cell6 = "Cell6.04", Cell7 = "Cell7.04" });
            rowList.Add(new Row() { Cell0 = "Cell0.05", Cell1 = "Cell1.05", Cell2 = "Cell2.05", Cell3 = "Cell3.05", Cell4 = "Cell4.05", Cell5 = "Cell5.05", Cell6 = "Cell6.05", Cell7 = "Cell7.05" });
            rowList.Add(new Row() { Cell0 = "Cell0.06", Cell1 = "Cell1.06", Cell2 = "Cell2.06", Cell3 = "Cell3.06", Cell4 = "Cell4.06", Cell5 = "Cell5.06", Cell6 = "Cell6.06", Cell7 = "Cell7.06" });
            rowList.Add(new Row() { Cell0 = "Cell0.07", Cell1 = "Cell1.07", Cell2 = "Cell2.07", Cell3 = "Cell3.07", Cell4 = "Cell4.07", Cell5 = "Cell5.07", Cell6 = "Cell6.07", Cell7 = "Cell7.07" });
            rowList.Add(new Row() { Cell0 = "Cell0.08", Cell1 = "Cell1.08", Cell2 = "Cell2.08", Cell3 = "Cell3.08", Cell4 = "Cell4.08", Cell5 = "Cell5.08", Cell6 = "Cell6.08", Cell7 = "Cell7.08" });
            rowList.Add(new Row() { Cell0 = "Cell0.09", Cell1 = "Cell1.09", Cell2 = "Cell2.09", Cell3 = "Cell3.09", Cell4 = "Cell4.09", Cell5 = "Cell5.09", Cell6 = "Cell6.09", Cell7 = "Cell7.09" });
            rowList.Add(new Row() { Cell0 = "Cell0.10", Cell1 = "Cell1.10", Cell2 = "Cell2.10", Cell3 = "Cell3.10", Cell4 = "Cell4.10", Cell5 = "Cell5.10", Cell6 = "Cell6.10", Cell7 = "Cell7.10" });
            rowList.Add(new Row() { Cell0 = "Cell0.11", Cell1 = "Cell1.11", Cell2 = "Cell2.11", Cell3 = "Cell3.11", Cell4 = "Cell4.11", Cell5 = "Cell5.11", Cell6 = "Cell6.11", Cell7 = "Cell7.11" });
            rowList.Add(new Row() { Cell0 = "Cell0.12", Cell1 = "Cell1.12", Cell2 = "Cell2.12", Cell3 = "Cell3.12", Cell4 = "Cell4.12", Cell5 = "Cell5.12", Cell6 = "Cell6.12", Cell7 = "Cell7.12" });
            rowList.Add(new Row() { Cell0 = "Cell0.13", Cell1 = "Cell1.13", Cell2 = "Cell2.13", Cell3 = "Cell3.13", Cell4 = "Cell4.13", Cell5 = "Cell5.13", Cell6 = "Cell6.13", Cell7 = "Cell7.13" });
            rowList.Add(new Row() { Cell0 = "Cell0.14", Cell1 = "Cell1.14", Cell2 = "Cell2.14", Cell3 = "Cell3.14", Cell4 = "Cell4.14", Cell5 = "Cell5.14", Cell6 = "Cell6.14", Cell7 = "Cell7.14" });
        }

        /// <summary>
        /// Load Dummy Data
        /// </summary>
        private void Load()
        {
            this.DataManager = new HierarchialData<Row>();
            List<Row> rowList = new List<Row>();
            AddDummyData(rowList);

            while (rowList.Count > 1)
            {
                Row parent = rowList[0];
                Row child = rowList[1];
                rowList.RemoveAt(1);
                rowList.RemoveAt(0);

                HierarchialDataModel<Row> hdmParent = new HierarchialDataModel<Row>(this.dataManager, parent);
                HierarchialDataModel<Row> hdmChild = new HierarchialDataModel<Row>(this.dataManager, child);
                hdmParent.IsVisible = true;

                hdmParent.AddChild(hdmChild);
                this.dataManager.RootNodes.Add(hdmParent);
            }

            if (rowList.Count > 0)
            {
                HierarchialDataModel<Row> hdm = new HierarchialDataModel<Row>(this.dataManager, rowList[0]);
                hdm.IsVisible = true;
                this.dataManager.RootNodes.Add(hdm);
                rowList.RemoveAt(0);
            }

            this.dataManager.Initialize();
        }



        /// <summary>
        /// Hierarchical ObservableCollection as Itemsource for Datagrid
        /// </summary>
        public HierarchialData<Row> DataManager
        {
            get
            {
                return this.dataManager;
            }

            set
            {
                if (this.dataManager != value)
                {
                    this.dataManager = value;
                    this.RaisePropertyChanged(nameof(this.DataManager));
                }
            }
        }

        #region event

        public event PropertyChangedEventHandler PropertyChanged;

        // Create the OnPropertyChanged method to raise the event
        // The calling member's name will be used as the parameter.
        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}

行.cs:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace EditableTreeDataGridDemo
{
    [Serializable]
    public class Row : INotifyPropertyChanged, IDataErrorInfo
    {
        private string cell0;
        private string cell1;
        private string cell2;
        private string cell3;
        private string cell4;
        private string cell5;
        private string cell6;
        private string cell7;


        public Row()
        {
        }

        public string Cell0
        {
            get
            {
                return this.cell0;
            }

            set
            {
                if (this.cell0 != value)
                {
                    this.cell0 = value;
                    this.RaisePropertyChanged(nameof(this.Cell0));
                }
            }
        }

        public string Cell1
        {
            get
            {
                return this.cell1;
            }

            set
            {
                if (this.cell1 != value)
                {
                    this.cell1 = value;
                    this.RaisePropertyChanged(nameof(this.Cell1));
                }
            }
        }

        public string Cell2
        {
            get
            {
                return this.cell2;
            }

            set
            {
                if (this.cell2 != value)
                {
                    this.cell2 = value;
                    this.RaisePropertyChanged(nameof(this.Cell2));
                }
            }
        }

        public string Cell3
        {
            get
            {
                return this.cell3;
            }

            set
            {
                if (this.cell3 != value)
                {
                    this.cell3 = value;
                    this.RaisePropertyChanged(nameof(this.Cell3));
                }
            }
        }

        public string Cell4
        {
            get
            {
                return this.cell4;
            }

            set
            {
                if (this.cell4 != value)
                {
                    this.cell4 = value;
                    this.RaisePropertyChanged(nameof(this.Cell4));
                }
            }
        }

        public string Cell5
        {
            get
            {
                return this.cell5;
            }

            set
            {
                if (this.cell5 != value)
                {
                    this.cell5 = value;
                    this.RaisePropertyChanged(nameof(this.Cell5));
                }
            }
        }

        public string Cell6
        {
            get
            {
                return this.cell6;
            }

            set
            {
                if (this.cell6 != value)
                {
                    this.cell6 = value;
                    this.RaisePropertyChanged(nameof(this.Cell6));
                }
            }
        }

        public string Cell7
        {
            get
            {
                return this.cell7;
            }

            set
            {
                if (this.cell7 != value)
                {
                    this.cell7 = value;
                    this.RaisePropertyChanged(nameof(this.Cell7));
                }
            }
        }

        #region Validation

        public string Error
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// Returns an Validation-Error-String for each property (for testing purposses)
        /// </summary>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public string this[string propertyName]
        {
            get
            {
                return "TEST-ERROR";
            }
        }

        #endregion

        #region event

        public event PropertyChangedEventHandler PropertyChanged;

        // Create the OnPropertyChanged method to raise the event
        // The calling member's name will be used as the parameter.
        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}

MainWindow.xaml:

由于 StackOverflow 限制,我必须将 MainWindow.xaml 共享为链接(问题中只允许 30000 个字符):https ://wtools.io/paste-code/b51y

标签: c#wpfvalidationmvvmdatagrid

解决方案


推荐阅读