diff --git a/Main.Test/App.xaml b/Main.Test/App.xaml index 25275cd..e9613f0 100644 --- a/Main.Test/App.xaml +++ b/Main.Test/App.xaml @@ -11,7 +11,7 @@ - + diff --git a/Main.Test/DataGridEx.cs b/Main.Test/DataGridEx.cs deleted file mode 100644 index 437effa..0000000 --- a/Main.Test/DataGridEx.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Controls; - -namespace Main.Test; - -public class DataGridEx : DataGrid -{ - -} diff --git a/Main.Test/DataGridHelper.cs b/Main.Test/DataGridHelper.cs index 88d55d2..ff1c76a 100644 --- a/Main.Test/DataGridHelper.cs +++ b/Main.Test/DataGridHelper.cs @@ -63,12 +63,15 @@ public class DataGridHelper public static DependencyProperty ShowRowNumberProperty = DependencyProperty.RegisterAttached("ShowRowNumber", - typeof(bool), typeof(DataGridAttach), + typeof(bool), typeof(DataGridHelper), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits, OnShowRowNumberChanged)); public static bool GetShowRowNumber(DependencyObject target) => (bool)target.GetValue(ShowRowNumberProperty); - public static void SetShowRowNumber(DependencyObject target, bool value) => target.SetValue(ShowRowNumberProperty, value); + public static void SetShowRowNumber(DependencyObject target, bool value) + { + target.SetValue(ShowRowNumberProperty, value); + } private static void OnShowRowNumberChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { @@ -118,7 +121,10 @@ public class DataGridHelper } } - private static void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e) => e.Row.Header = (e.Row.GetIndex() + 1).ToString(); + private static void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e) + { + e.Row.Header = (e.Row.GetIndex() + 1).ToString(); + } @@ -126,7 +132,6 @@ public class DataGridHelper public static readonly DependencyProperty ShowBorderProperty = DependencyProperty.RegisterAttached("ShowBorder", typeof(bool), typeof(DataGridHelper), new PropertyMetadata(false, OnShowBorderChanged)); - public static void SetShowBorder(DependencyObject element, bool value) => element.SetValue(ShowBorderProperty, value); @@ -166,24 +171,3 @@ public class BorderAdorner : Adorner new Point(header.ActualWidth - 0.5, header.ActualHeight)); } } -// 先创建自定义类(如果还没创建) -public class CustomDataGridColumnHeader : DataGridColumnHeader -{ - protected override void OnRender(DrawingContext drawingContext) - { - base.OnRender(drawingContext); - - // 在这里绘制边框,永远不消失 - var pen = new Pen(new SolidColorBrush(Colors.LightGray), 1); - - // 绘制右侧竖线 - drawingContext.DrawLine(pen, - new Point(ActualWidth - 0.5, 0), - new Point(ActualWidth - 0.5, ActualHeight)); - - // 绘制底部横线(如果需要) - // drawingContext.DrawLine(pen, - // new Point(0, ActualHeight - 0.5), - // new Point(ActualWidth, ActualHeight - 0.5)); - } -} \ No newline at end of file diff --git a/Main.Test/Dictionary1.xaml b/Main.Test/Dictionary1.xaml index 42078cb..1ca4940 100644 --- a/Main.Test/Dictionary1.xaml +++ b/Main.Test/Dictionary1.xaml @@ -3,16 +3,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ext="clr-namespace:Main.Test" xmlns:sys="clr-namespace:System;assembly=mscorlib"> - - - - + + + - - + SelectedRowForeground="White" + SelectionMode="Extended" + ShowCheckBox="True" + ShowRowNumber="True"> + - - + + diff --git a/Main.Test/StyledDataGrid.cs b/Main.Test/StyledDataGrid.cs new file mode 100644 index 0000000..61efc87 --- /dev/null +++ b/Main.Test/StyledDataGrid.cs @@ -0,0 +1,130 @@ +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; + } + } +} diff --git a/Main.Test/Themes/Generic.xaml b/Main.Test/Themes/Generic.xaml new file mode 100644 index 0000000..6ab3487 --- /dev/null +++ b/Main.Test/Themes/Generic.xaml @@ -0,0 +1,249 @@ + + + + + + + + + + + + diff --git a/Main.Test/ViewModels/MainViewModel.cs b/Main.Test/ViewModels/MainViewModel.cs index 820ad91..bb8446a 100644 --- a/Main.Test/ViewModels/MainViewModel.cs +++ b/Main.Test/ViewModels/MainViewModel.cs @@ -32,7 +32,7 @@ internal class MainViewModel var singleItem = itemFaker.Generate(); // 生成100个对象 - Data = itemFaker.Generate(100); + Data = itemFaker.Generate(1000); //Data.Clear(); } }