Files
29WxOrder/Main.Test/StyledDataGrid.cs
T
2026-10-21 17:11:24 +08:00

131 lines
6.6 KiB
C#

using System.Collections.Specialized;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace Main.Test;
/// <summary>带默认样式、行号和行选择勾选框的 DataGrid。</summary>
public class StyledDataGrid : DataGrid
{
public static readonly RoutedUICommand ToggleSelectAllCommand = new("全选或取消全选", nameof(ToggleSelectAllCommand), typeof(StyledDataGrid));
private static readonly DependencyPropertyKey AreAllRowsSelectedPropertyKey = DependencyProperty.RegisterReadOnly(
nameof(AreAllRowsSelected), typeof(bool?), typeof(StyledDataGrid), new PropertyMetadata(false));
public static readonly DependencyProperty AreAllRowsSelectedProperty = AreAllRowsSelectedPropertyKey.DependencyProperty;
/// <summary>全部选中为 true,部分选中为 null,未选中为 false。</summary>
public bool? AreAllRowsSelected => (bool?)GetValue(AreAllRowsSelectedProperty);
public static readonly DependencyProperty SelectedRowBackgroundProperty = DependencyProperty.Register(
nameof(SelectedRowBackground), typeof(Brush), typeof(StyledDataGrid),
new PropertyMetadata(new SolidColorBrush(Color.FromRgb(75, 178, 255))));
public static readonly DependencyProperty SelectedRowForegroundProperty = DependencyProperty.Register(
nameof(SelectedRowForeground), typeof(Brush), typeof(StyledDataGrid), new PropertyMetadata(Brushes.White));
public static readonly DependencyProperty ShowCheckBoxProperty = DependencyProperty.Register(
nameof(ShowCheckBox), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(false, HeaderOptionsChanged));
public static readonly DependencyProperty ShowRowNumberProperty = DependencyProperty.Register(
nameof(ShowRowNumber), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(false, HeaderOptionsChanged));
public Brush SelectedRowBackground { get => (Brush)GetValue(SelectedRowBackgroundProperty); set => SetValue(SelectedRowBackgroundProperty, value); }
public Brush SelectedRowForeground { get => (Brush)GetValue(SelectedRowForegroundProperty); set => SetValue(SelectedRowForegroundProperty, value); }
/// <summary>勾选状态与行 IsSelected 同步,可通过 SelectedItems 获取。</summary>
public bool ShowCheckBox { get => (bool)GetValue(ShowCheckBoxProperty); set => SetValue(ShowCheckBoxProperty, value); }
public bool ShowRowNumber { get => (bool)GetValue(ShowRowNumberProperty); set => SetValue(ShowRowNumberProperty, value); }
static StyledDataGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(StyledDataGrid), new FrameworkPropertyMetadata(typeof(StyledDataGrid)));
}
public StyledDataGrid()
{
CommandBindings.Add(new CommandBinding(ToggleSelectAllCommand, (_, e) =>
{
if (AreAllRowsSelected == true) UnselectAll();
else SelectAll();
e.Handled = true;
}, (_, e) =>
{
e.CanExecute = SelectionMode == DataGridSelectionMode.Extended &&
SelectionUnit != DataGridSelectionUnit.Cell && SelectableItemCount > 0;
e.Handled = true;
}));
LoadingRow += (_, e) => UpdateRowNumber(e.Row);
ItemContainerGenerator.ItemsChanged += (_, _) => QueueHeaderUpdate();
Loaded += (_, _) => QueueHeaderUpdate();
}
private static void HeaderOptionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> ((StyledDataGrid)d).QueueHeaderUpdate();
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
QueueHeaderUpdate();
UpdateSelectAllState();
}
private int SelectableItemCount => Items.Count - (Items.Contains(System.Windows.Data.CollectionView.NewItemPlaceholder) ? 1 : 0);
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
UpdateSelectAllState();
}
private void UpdateSelectAllState()
{
SetValue(AreAllRowsSelectedPropertyKey, SelectedItems.Count == 0 ? false :
SelectedItems.Count == SelectableItemCount ? true : (bool?)null);
CommandManager.InvalidateRequerySuggested();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == FontSizeProperty || e.Property == FontFamilyProperty ||
e.Property == FontStyleProperty || e.Property == FontWeightProperty || e.Property == FontStretchProperty)
QueueHeaderUpdate();
}
private bool headerUpdatePending;
private void QueueHeaderUpdate()
{
if (headerUpdatePending) return;
headerUpdatePending = true;
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
{
headerUpdatePending = false;
// 使用最宽数字估算所有行号,避免虚拟化滚动时行头宽度跳变。
var digits = Math.Max(1, Items.Count.ToString(CultureInfo.InvariantCulture).Length);
var numberWidth = 0d;
if (ShowRowNumber)
for (var digit = '0'; digit <= '9'; digit++)
{
var text = new FormattedText(new string(digit, digits), CultureInfo.CurrentCulture,
FlowDirection, new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
FontSize, Foreground ?? Brushes.Black, VisualTreeHelper.GetDpi(this).PixelsPerDip);
numberWidth = Math.Max(numberWidth, Math.Ceiling(text.WidthIncludingTrailingWhitespace) + 16);
}
SetCurrentValue(RowHeaderWidthProperty, numberWidth + (ShowCheckBox ? 32 : 0) +
(ShowRowNumber && ShowCheckBox ? 1 : 0) + (ShowRowNumber || ShowCheckBox ? 1 : 0));
foreach (var row in GetRealizedRows(this)) UpdateRowNumber(row);
}));
}
private void UpdateRowNumber(DataGridRow row)
=> row.SetCurrentValue(DataGridRow.HeaderProperty, ShowRowNumber ? (row.GetIndex() + 1).ToString(CultureInfo.CurrentCulture) : null);
private IEnumerable<DataGridRow> GetRealizedRows(DependencyObject parent)
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is DataGridRow row && ItemsControl.ItemsControlFromItemContainer(row) == this) yield return row;
else if (child is not DataGrid)
foreach (var descendant in GetRealizedRows(child)) yield return descendant;
}
}
}