c# - WPF DataGridCell ValidationErrors 的装饰器显示在错误的位置
问题描述
目前我正在尝试让 DataGrid 使用分层数据并允许同时进行多个编辑DataGridCells
。到目前为止,它工作正常。
现在验证会引起一些麻烦。我想使用类似的接口在 ViewModel/DataContext 中进行验证IDataErrorInfo
如果我启动我的应用程序,每个ValidationError
-Border 都会显示在其指定位置。但是,如果我稍微玩一下,ValidationError
-Border 就不再是他们应该在的地方了。不知何故,如果绑定到的通知它,则Adorners
每次都不会更新。ObservableCollection
ItemSource
DataGrid
CollectionChangedEvent
你自己看:
我以前从未经历过这样的行为,因此我不知道如何解决它。我想出的唯一“解决方法”是调整整个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
解决方案
推荐阅读
- mysql - 带有 BOOLEAN 的 SELECT 语句
- ios - SecItemAdd() 以 kSecAttrAccessibleWhenUnlocked 成功,但以 kSecAttrAccessibleWhenUnlockedThisDeviceOnly 失败
- javascript - 为什么我的 Google PageSpeed Insights 得分降低了这么多?
- python - 在 Python 中的每个执行时间创建不同的日志名称
- html - 在另一个 flexbox 容器旁边的 Flex 方向行对齐
- python - Pandas 根据 ID 组合 2 个数据帧
- android - Android:不使用 Google Places 或 Maps SDK 按名称搜索位置
- apache-kafka - Kafka 流式读写到单独的集群
- sql - 如何计算 SQL 文本中关键字的实例
- c# - ASP.NET NuGet 包依赖项不会更新