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; /// 带默认样式、行号和行选择勾选框的 DataGrid。 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; /// 全部选中为 true,部分选中为 null,未选中为 false。 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); } /// 勾选状态与行 IsSelected 同步,可通过 SelectedItems 获取。 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 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; } } }