【问题标题】:WPF DataGrid add separate border to every column setWPF DataGrid 为每个列集添加单独的边框
【发布时间】:2021-09-01 13:00:12
【问题描述】:

我正在尝试实现每列都有自己的边框的效果,但找不到完美的解决方案。

这种外观是理想的,但这是通过在 3 列网格中放置 3 个边框来实现的,这不灵活,因为 Grid 列和 DataGrid 列的大小是分开调整的

<Window x:Class="WpfApp3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp3" xmlns:usercontrols="clr-namespace:EECC.UserControls"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid Background="LightGray">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <Border Background="White" CornerRadius="5" BorderThickness="1" BorderBrush="Black" Margin="5"/>
    <Border Background="White" CornerRadius="5" BorderThickness="1" BorderBrush="Black" Margin="5" Grid.Column="1"/>
    <Border Background="White" CornerRadius="5" BorderThickness="1" BorderBrush="Black" Margin="5" Grid.Column="2"/>

    <DataGrid ItemsSource="{Binding Items}" ColumnWidth="*" AutoGenerateColumns="True" Padding="10" GridLinesVisibility="None" Background="Transparent" Grid.ColumnSpan="3">
        <DataGrid.Resources>
            <Style TargetType="{x:Type DataGridRow}">
                <Setter Property="Background" Value="Transparent"/>
            </Style>
            <Style TargetType="{x:Type DataGridColumnHeader}">
                <Setter Property="Background" Value="Transparent"/>
            </Style>
        </DataGrid.Resources>
    </DataGrid>
</Grid>

【问题讨论】:

    标签: wpf datagrid


    【解决方案1】:

    如果您想使用DataGrid,这并非易事。 “问题”是DataGrid 使用Grid 来托管单元格。使用Grid 的功能绘制单元格边框以显示网格线。您可以隐藏网格线,但仍然有 Grid 控制布局。
    创造你想要的差距并不容易。 Grid 的布局系统使其努力实现可扩展的解决方案。您可以扩展SelectiveScrollingGridDataGrid 的托管面板)并在布置项目时添加间隙。了解DataGrid 的内部结构,我可以说这是可能的,但不值得努力。

    替代解决方案是使用ListViewGridView 作为主机。这里的问题是GridView 旨在将行显示为单个项目。您没有机会修改基于边距的列。您只能调整内容。我没有尝试修改ListView 内部布局元素或覆盖布局算法,但在替代解决方案的上下文中,我也会使用GridView 排除ListView - 但这是可能的。这不值得。


    解决方案:自定义视图

    我可以建议的最简单的解决方案是调整数据结构以显示基于数据列的数据。这样您就可以使用水平的ListBox。每个项目构成一列。每列都实现为垂直ListBox。你基本上已经嵌套了ListBox 元素。

    您必须注意行映射,以便允许在垂直 ListBox 列中选择公共行的单元格。
    这可以通过将RowIndex 属性添加到CellItem 模型来轻松实现。

    这个想法是让水平的ListBox 显示ColumnItem 模型的集合。每个列项模型都公开了CellItem 模型的集合。不同列但同一行的CellItem项必须共享相同的CellItem.RowIndex
    作为奖励,这个解决方案很容易设计。与明显更复杂的DataGrid 或稍微复杂的GridView 相比,ListBox 模板几乎没有任何部分。

    为了让展示这个概念不那么混乱,我选择将网格布局实现为UserControl。为简单起见,初始化和托管模型和源集合的逻辑在此UserControl 中实现。我不推荐这个。项目的实例化和托管应该在控件之外,例如在视图模型内。您应该为控件添加一个DependencyProperty 作为ItemsSource 作为内部水平ListBox 的数据源。

    使用示例

    <Window>
      <ColumnsView />
    </Window>
    

    1. 首先创建数据结构以填充视图。
      该结构基于ColumnItem 类型,它承载了一个集合 CellItem 项目,其中每个 CellItem 都有一个 CellItem.RowIndex
      不同列的CellItem 项在逻辑上形成一行必须共享相同的CellItem.RowIndex

    ColumnItem.cs

    public class ColumnItem
    {
      public ColumnItem(string header, IEnumerable<CellItem> items)
      {
        Header = header;
        this.Items = new ObservableCollection<CellItem>(items);
      }
    
      public CellItem this[int rowIndex] 
        => this.Items.FirstOrDefault(cellItem => cellItem.RowIndex.Equals(rowIndex));
    
      public string Header { get; }
      public ObservableCollection<CellItem> Items { get; }
    }
    

    CellItem.cs

    public class CellItem : INotifyPropertyChanged
    {
      public CellItem(int rowIndex, object value)
      {
        this.RowIndex = rowIndex;
        this.Value = value;
      }
    
      protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") 
        => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
      public event PropertyChangedEventHandler PropertyChanged;
      public int RowIndex { get; }
    
      private object value;
      public object Value
      {
        get => this.value;
        set
        {
          this.value = value;
          OnPropertyChanged();
        }
      }
    
      private bool isSelected;
      public bool IsSelected
      {
        get => this.isSelected;
        set
        {
          this.isSelected = value;
          OnPropertyChanged();
        }
      }
    }
    
    1. 构建并初始化数据结构。
      在此示例中,这一切都在 UserControl 本身中实现,旨在使示例尽可能紧凑。

    ColumnsView.xaml.cs

    public partial class ColumnsView : UserControl
    {
      public ColumnsView()
      {
        InitializeComponent();
        this.DataContext = this;
    
        InitializeSourceData();
      }
    
      public InitializeSourceData()
      {
        this.Columns = new ObservableCollection<ColumnItem>();
    
        for (int columnIndex = 0; columnIndex < 3; columnIndex++)
        {
          var cellItems = new List<CellItem>();
          int asciiChar = 65;
    
          for (int rowIndex = 0; rowIndex < 10; rowIndex++)
          {
            var cellValue = $"CellItem.RowIndex:{rowIndex}, Value: {(char)asciiChar++}";
            var cellItem = new CellItem(rowIndex, cellValue);
            cellItems.Add(cellItem);
          }
    
          var columnHeader = $"Column {columnIndex + 1}";
          var columnItem = new ColumnItem(columnHeader, cellItems);
          this.Columns.Add(columnItem);
        }
      }
    
      private void CellsHostListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
      {
        var cellsHost = sender as Selector;
        var selectedCell = cellsHost.SelectedItem as CellItem;
        SelectCellsOfRow(selectedCell.RowIndex);
      }
    
      private void SelectCellsOfRow(int selectedRowIndex)
      {
        foreach (ColumnItem columnItem in this.Columns)
        {
          var cellOfRow = columnItem[selectedRowIndex];
          cellOfRow.IsSelected = true;
        }
      }
    
      private void ColumnGripper_DragStarted(object sender, DragStartedEventArgs e) 
        => this.DragStartX = Mouse.GetPosition(this).X;
    
      private void ColumnGripper_DragDelta(object sender, DragDeltaEventArgs e)
      {
        if ((sender as DependencyObject).TryFindVisualParentElement(out ListBoxItem listBoxItem))
        {
          double currentMousePositionX = Mouse.GetPosition(this).X;
          listBoxItem.Width = Math.Max(0 , listBoxItem.ActualWidth - (this.DragStartX - currentMousePositionX));
          this.DragStartX = currentMousePositionX;
        }
      }
    
      public static bool TryFindVisualParentElement<TParent>(DependencyObject child, out TParent resultElement)
        where TParent : DependencyObject
      {
        resultElement = null;
    
        DependencyObject parentElement = VisualTreeHelper.GetParent(child);
    
        if (parentElement is TParent parent)
        {
          resultElement = parent;
          return true;
        }
    
        return parentElement != null 
          ? TryFindVisualParentElement(parentElement, out resultElement) 
          : false;
      }
    
      public ObservableCollection<ColumnItem> Columns { get; }
      private double DragStartX { get; set; }
    }
    
    1. 使用水平的ListView 创建视图,将ColumnItem 源集合呈现为垂直的ListBox 元素列表。

    ColumnsView.xaml

    <UserControl x:Class="ColumnsView">
      <UserControl.Resources>
        <Style x:Key="ColumnGripperStyle"
             TargetType="{x:Type Thumb}">
          <Setter Property="Margin"
                Value="-2,8" />
          <Setter Property="Width"
                Value="4" />
          <Setter Property="Background"
                Value="Transparent" />
          <Setter Property="Cursor"
                Value="SizeWE" />
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="{x:Type Thumb}">
                <Border Background="{TemplateBinding Background}"
                      Padding="{TemplateBinding Padding}" />
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
      </UserControl.Resources>
    
      <!-- Column host. Displays cells of a column. -->
      <ListBox ItemsSource="{Binding Columns}">
        <ListBox.ItemsPanel>
          <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal" />
          </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    
        <ListBox.ItemTemplate>
          <DataTemplate>
            <Border Padding="4" 
                    BorderThickness="1" 
                    BorderBrush="Black" 
                    CornerRadius="8">
              <StackPanel>
                <TextBlock Text="{Binding Header}" />
    
                <!-- Cell host. Displays cells of a column. -->
                <ListBox ItemsSource="{Binding Items}" 
                         BorderThickness="0" 
                         Height="150"
                         Selector.SelectionChanged="CellsHostListBox_SelectionChanged">
                  <ListBox.ItemTemplate>
                    <DataTemplate>
                      <TextBlock Text="{Binding Value}" />
                    </DataTemplate>
                  </ListBox.ItemTemplate>
              
                  <ListBox.ItemContainerStyle>
                    <Style TargetType="ListBoxItem">
                  
                      <!-- Link item container selection to CellItem.IsSelected -->
                      <Setter Property="IsSelected" Value="{Binding IsSelected}" />
                    </Style>
                  </ListBox.ItemContainerStyle>
                </ListBox>
              </StackPanel>
            </Border>
          </DataTemplate>
        </ListBox.ItemTemplate>
    
        <ListBox.ItemContainerStyle>
          <Style TargetType="ListBoxItem">
            <Setter Property="Margin" Value="0,0,8,0" /> <!-- Define the column gap -->    
            <Setter Property="Template">
              <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                  <Grid>
                    <Grid.ColumnDefinitions>
                      <ColumnDefinition />
                      <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <ContentPresenter />
                    <Thumb Grid.Column="1" 
                           Style="{StaticResource ColumnGripperStyle}"
                           DragStarted="ColumnGripper_DragStarted" 
                           DragDelta="ColumnGripper_DragDelta" />
                  </Grid>
                </ControlTemplate>
              </Setter.Value>
            </Setter>
          </Style>
        </ListBox.ItemContainerStyle>
      </ListBox>
    </UserControl>
    

    改进说明

    ColumnsView.Columns 属性应该是 DependencyProperty 以允许将控件用作绑定目标。
    列间隙也可以是 DependencyPropertyColumnsView
    通过将显示列标题的TextBlock 替换为Button,您可以轻松添加排序。拥有触发排序的活动列,例如从词法上讲,您必须同步其他被动列并根据活动排序列的CellItem.RowIndex 顺序对它们进行排序。
    也许选择扩展Control而不是UserControl
    您可以实现CellItem 以使用泛型类型参数来声明Cellitem.Value 属性,如CellItem&lt;TValue&gt;
    您可以实现ColumnItem 以使用泛型类型参数来声明ColumnItem.Items 属性,如ColumnItem&lt;TColumn&gt;
    添加一个ColumnsView.SelectedRow 属性,该属性返回当前选定行的所有CellItem 项的集合

    【讨论】:

    • 非常感谢您的解决方案,但它并没有解决问题标题。它有这些问题: 1) 所有项目都立即加载,没有发生虚拟化。 2) DataGrid 是必需的,因为现在它的所有功能都丢失了。 3) 鼠标悬停在垂直ListBox上时出现垂直滚动问题。
    • 1) 这绝对不是真的。水平和垂直列表框都虚拟化了它们的项目。 2)不知道自己缺少哪些无法轻松实现的功能。但是,是的,它不是 DataGrid 3) 不确定你在这里的意思。我可以像往常一样滚动。你应该得到更具体的。但既然你不感兴趣,我猜这并不重要。
    • 我不想发布大量样式或代码。这就是为什么我保持简单,目的是展示您可以改进以满足您的要求的想法。关于垂直滚动,您可以轻松处理路由滚动事件并同步其他可见网格的滚动位置。垂直滚动位置和行索引是同义词。那么祝你的 DataGrid 好运。反正我是不会删帖的。
    • 我建议禁用垂直列表框的垂直滚动查看器,并使用水平列表框的顶级滚动查看器来滚动列。
    • 1) 虚拟化可用。当然,您必须为垂直的内部 ListBox 分配一个固定的高度。否则它将拉伸以使所有项目都适合 - 这意味着它将加载所有项目。但这是正常行为。为了演示,我在垂直单元格主机 ListBox 中添加了 150 的高度。如果这是我的控件,我将添加额外的属性以允许直接在 ColumnsView 上设置高度。
    【解决方案2】:

    使用大量的肘部油脂,您可以通过扩展原生 DataGrid 来获得所需的外观。

    自定义标题和单元格模板应注意间距,并使用适当的背景颜色。 AutoGeneratingColumn 行为需要比在 XAML 中轻松实现的更多控制,因此我选择在代码中创建模板以便能够传递列的 PropertyName

    细心的读者可能已经问过自己:“列表末尾的边框呢?”。没错,我们需要能够将最后一个项目与其他所有项目区分开来,以便能够以不同的方式对其边框进行模板化。

    这是通过以下合同完成的:

    public interface ICanBeLastItem
    {
        bool IsLastItem { get; set; }
    }
    

    为了正确绘制底部边框,需要实现哪些行对象。

    这在排序时也需要一些自定义逻辑,以更新IsLastItem 的值。黄色背景的图片显示了在ThirdNumber上的排序结果。

    原生DataGrid 提供了一个开箱即用的Sorting 事件,但没有Sorted 事件。模板内容与自定义事件的需求相结合,导致我从 DataGrid 子类化 ColumnView 而不是将其声明为 UserControl

    我在MainWindow 中添加了代码隐藏,用于切换背景颜色,但这只是为了说明目的(因为我不想实现Command 模式)并且与自定义控件无关。

    ColumnView 是通过绑定配置的。与往常一样,请随时扩展。当前的实现期望这些列是自动生成的。无论哪种情况,都提供了生成模板的代码。

    <local:ColumnView ItemsSource="{Binding Items}" Background="LightSteelBlue"/>
    

    演示代码

    列视图

    public class ColumnView : DataGrid
    {
        public ColumnView()
        {
            HeadersVisibility = DataGridHeadersVisibility.Column;
            HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
    
            // Hidden props from base DataGrid
            base.ColumnWidth = new DataGridLength(1, DataGridLengthUnitType.Star);
            base.AutoGenerateColumns = true;
            base.GridLinesVisibility = DataGridGridLinesVisibility.None;
    
            // Styling
            ColumnHeaderStyle = CreateColumnHeaderStyle();
            CellStyle = CreateCellStyle(this);
    
            // Event handling
            AutoGeneratingColumn += OnAutoGeneratingColumn;
            Sorting += OnSorting;
            Sorted += OnSorted;
        }
    
        #region Hidden props
    
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new DataGridLength ColumnWidth
        {
            get => base.ColumnWidth;
            set => new InvalidOperationException($"{nameof(ColumnView)} doesn't allow changing {nameof(ColumnWidth)}.");
        }
    
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new DataGridGridLinesVisibility GridLinesVisibility
        {
            get => base.GridLinesVisibility;
            set => new InvalidOperationException($"{nameof(ColumnView)} doesn't allow changing {nameof(GridLinesVisibility)}.");
        }
    
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new bool AutoGenerateColumns
        {
            get => base.AutoGenerateColumns;
            set => new InvalidOperationException($"{nameof(ColumnView)} doesn't allow changing {nameof(AutoGenerateColumns)}.");
        }
    
        #endregion Hidden props
    
        #region Styling
    
        private static Style CreateColumnHeaderStyle()
            => new Style(typeof(DataGridColumnHeader))
            {
                Setters =
                {
                    new Setter(BackgroundProperty, Brushes.Transparent),
                    new Setter(HorizontalAlignmentProperty, HorizontalAlignment.Stretch),
                    new Setter(HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch)
                }
            };
    
        private static Style CreateCellStyle(ColumnView columnView)
            => new Style(typeof(DataGridCell))
            {
                Setters =
                {
                    new Setter(BorderThicknessProperty, new Thickness(0.0)),
                    new Setter(BackgroundProperty, new Binding(nameof(Background)) { Source = columnView})
                }
            };
    
        #endregion Styling
    
        #region AutoGeneratingColumn
    
        // https://stackoverflow.com/questions/25643765/wpf-datagrid-databind-to-datatable-cell-in-celltemplates-datatemplate
        private static void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            if (sender is ColumnView columnView)
            {
                if (e.PropertyName == nameof(ICanBeLastItem.IsLastItem))
                {
                    e.Cancel = true;
                }
                else
                {
                    var column = new DataGridTemplateColumn
                    {
                        CellTemplate = CreateCustomCellTemplate(e.PropertyName),
                        Header = e.Column.Header,
                        HeaderTemplate = CreateCustomHeaderTemplate(columnView, e.PropertyName),
                        HeaderStringFormat = e.Column.HeaderStringFormat,
                        SortMemberPath = e.PropertyName
                    };
                    e.Column = column;
                }
            }
        }
    
        private static DataTemplate CreateCustomCellTemplate(string path)
        {
            // Create the data template
            var customTemplate = new DataTemplate();
    
            // Set up the wrapping border
            var border = new FrameworkElementFactory(typeof(Border));
            border.SetValue(BorderBrushProperty, Brushes.Black);
            border.SetValue(StyleProperty, new Style(typeof(Border))
            {
                Triggers =
                {
                    new DataTrigger
                    {
                        Binding = new Binding(nameof(DataGridCell.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridCell), 1) },
                        Value = false,
                        Setters =
                        {
                            new Setter(BackgroundProperty, Brushes.White),
                        }
                    },
                    new DataTrigger
                    {
                        Binding = new Binding(nameof(DataGridCell.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridCell), 1) },
                        Value = true,
                        Setters =
                        {
                            new Setter(BackgroundProperty, SystemColors.HighlightBrush),
                        }
                    },
                    new DataTrigger
                    {
                        Binding = new Binding(nameof(ICanBeLastItem.IsLastItem)),
                        Value = false,
                        Setters =
                        {
                            new Setter(MarginProperty, new Thickness(5.0, -1.0, 5.0, -1.0)),
                            new Setter(BorderThicknessProperty, new Thickness(1.0, 0.0, 1.0, 0.0)),
                        }
                    },
                    new DataTrigger
                    {
                        Binding = new Binding(nameof(ICanBeLastItem.IsLastItem)),
                        Value = true,
                        Setters =
                        {
                            new Setter(MarginProperty, new Thickness(5.0, -1.0, 5.0, 0.0)),
                            new Setter(BorderThicknessProperty, new Thickness(1.0, 0.0, 1.0, 1.0)),
                            new Setter(Border.CornerRadiusProperty, new CornerRadius(0.0, 0.0, 5.0, 5.0)),
                            new Setter(Border.PaddingProperty, new Thickness(0.0, 0.0, 0.0, 5.0)),
                        }
                    }
                }
            });
    
            // Set up the TextBlock
            var textBlock = new FrameworkElementFactory(typeof(TextBlock));
            textBlock.SetBinding(TextBlock.TextProperty, new Binding(path));
            textBlock.SetValue(MarginProperty, new Thickness(10.0, 0.0, 5.0, 0.0));
    
            // Set the visual tree of the data template
            border.AppendChild(textBlock);
            customTemplate.VisualTree = border;
    
            return customTemplate;
        }
    
        private static DataTemplate CreateCustomHeaderTemplate(ColumnView columnView, string propName)
        {
            // Create the data template
            var customTemplate = new DataTemplate();
    
            // Set up the wrapping border
            var border = new FrameworkElementFactory(typeof(Border));
            border.SetValue(MarginProperty, new Thickness(5.0, 0.0, 5.0, 0.0));
            border.SetValue(BackgroundProperty, Brushes.White);
            border.SetValue(BorderBrushProperty, Brushes.Black);
            border.SetValue(BorderThicknessProperty, new Thickness(1.0, 1.0, 1.0, 0.0));
            border.SetValue(Border.CornerRadiusProperty, new CornerRadius(5.0, 5.0, 0.0, 0.0));
    
            // Set up the TextBlock
            var textBlock = new FrameworkElementFactory(typeof(TextBlock));
            textBlock.SetValue(TextBlock.TextProperty, propName);
            textBlock.SetValue(MarginProperty, new Thickness(5.0));
    
            // Set the visual tree of the data template
            border.AppendChild(textBlock);
            customTemplate.VisualTree = border;
    
            return customTemplate;
        }
    
        #endregion AutoGeneratingColumn
    
        #region Sorting
    
        #region Custom Sorted Event
    
        // https://stackoverflow.com/questions/9571178/datagrid-is-there-no-sorted-event
    
        // Create a custom routed event by first registering a RoutedEventID
        // This event uses the bubbling routing strategy
        public static readonly RoutedEvent SortedEvent = EventManager.RegisterRoutedEvent(
            nameof(Sorted), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ColumnView));
    
        // Provide CLR accessors for the event
        public event RoutedEventHandler Sorted
        {
            add => AddHandler(SortedEvent, value);
            remove => RemoveHandler(SortedEvent, value);
        }
    
        // This method raises the Sorted event
        private void RaiseSortedEvent()
        {
            var newEventArgs = new RoutedEventArgs(ColumnView.SortedEvent);
            RaiseEvent(newEventArgs);
        }
    
        protected override void OnSorting(DataGridSortingEventArgs eventArgs)
        {
            base.OnSorting(eventArgs);
            RaiseSortedEvent();
        }
    
        #endregion Custom Sorted Event
    
        private static void OnSorting(object sender, DataGridSortingEventArgs e)
        {
            if (sender is DataGrid dataGrid && dataGrid.HasItems)
            {
                if (dataGrid.Items[dataGrid.Items.Count - 1] is ICanBeLastItem lastItem)
                {
                    lastItem.IsLastItem = false;
                }
            }
        }
    
        private static void OnSorted(object sender, RoutedEventArgs e)
        {
            if (sender is DataGrid dataGrid && dataGrid.HasItems)
            {
                if (dataGrid.Items[dataGrid.Items.Count - 1] is ICanBeLastItem lastItem)
                {
                    lastItem.IsLastItem = true;
                }
            }
        }
    
        #endregion Sorting
    }
    

    行项目

    public class RowItem : INotifyPropertyChanged, ICanBeLastItem
    {
        public RowItem(int firstNumber, string secondNumber, double thirdNumber)
        {
            FirstNumber = firstNumber;
            SecondNumber = secondNumber;
            ThirdNumber = thirdNumber;
        }
    
        public int FirstNumber { get; }
        public string SecondNumber { get; }
        public double ThirdNumber { get; }
    
        private bool _isLastItem;
        public bool IsLastItem
        {
            get => _isLastItem;
            set
            {
                _isLastItem = value;
                OnPropertyChanged();
            }
        }
    
        #region INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        #endregion INotifyPropertyChanged
    }
    
    public interface ICanBeLastItem
    {
        bool IsLastItem { get; set; }
    }
    

    MainWindow.xaml

    <Window x:Class="WpfApp.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Window.DataContext>
            <local:MainViewModel/>
        </Window.DataContext>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="30"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Button Content="Switch Background" Click="ButtonBase_OnClick" />
            <local:ColumnView x:Name="columnView" Grid.Row="1" Padding="10"
                              ItemsSource="{Binding Items}"
                              Background="LightSteelBlue"/>
        </Grid>
    </Window>
    

    MainWindow.xaml.cs

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            if (columnView.Background == Brushes.LightSteelBlue)
            {
                columnView.Background = Brushes.DarkRed;
            }
            else if (columnView.Background == Brushes.DarkRed)
            {
                columnView.Background = Brushes.Green;
            }
            else if (columnView.Background == Brushes.Green)
            {
                columnView.Background = Brushes.Blue;
            }
            else if (columnView.Background == Brushes.Blue)
            {
                columnView.Background = Brushes.Yellow;
            }
            else
            {
                columnView.Background = Brushes.LightSteelBlue;
            }
        }
    }
    

    主视图模型

    public class MainViewModel
    {
        public MainViewModel()
        {
            Items = InitializeItems(200);
        }
    
        private ObservableCollection<RowItem> InitializeItems(int numberOfItems)
        {
            var rowItems = new ObservableCollection<RowItem>();
            var random = new Random();
            for (var i = 0; i < numberOfItems; i++)
            {
                var firstNumber = Convert.ToInt32(1000 * random.NextDouble());
                var secondNumber = Convert.ToString(Math.Round(1000 * random.NextDouble()));
                var thirdNumber = Math.Round(1000 * random.NextDouble());
                var rowItem = new RowItem(firstNumber, secondNumber, thirdNumber);
                rowItems.Add(rowItem);
            }
    
            rowItems[numberOfItems - 1].IsLastItem = true;
    
            return rowItems;
        }
    
        public ObservableCollection<RowItem> Items { get; }
    }
    

    【讨论】:

    • 非常感谢您提供的出色解决方案。通过使用 DataGrid 来实现这一点非常棒,但它有两个关于我的案例的“问题”。 1) 添加像 DataGridTemplateColumn 这样的自定义列不遵循样式,因为目前它仅适用于自动生成的列。我想通过定义样式或其他东西很容易解决。 2)从我的屏幕截图中可以看出,最后一个可见行需要角落,在您的情况下,它是实际的最后一项。因此应始终显示底角。再次感谢您的出色解决方案。
    • 不客气。回答你的问题 1) 没错,我的解决方案是基于模板的,它很好地映射到自动生成列的方法。或者至少,当您使用 DataGridTemplateColumns 时。我基本上完成了大部分“框架”的工作,构建了一个控制,让一切都脱离了你的掌控。如果您需要更多控制权,可以将其拆开,使用零碎并单独设置样式。
    • 2) 我注意到了。这实际上是传统 UX / UI 的另一种方式,(本机)控件通常倾向于。它需要一种像你用于 OP 的覆盖。从本机 DataGrid 开始,要使其与动态列宽保持同步是很困难的。
    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 2018-11-07
    • 1970-01-01
    • 1970-01-01
    • 2018-11-16
    • 2019-03-30
    相关资源
    最近更新 更多