WPF DataGrid 为每个列集添加单独的边框

mis*_*iga 6 wpf datagrid

我试图实现每列都有自己的边框的效果,但还找不到完美的工作解决方案。 在此输入图像描述

这种外观是理想的,但这是通过在 3 列网格中放置 3 个边框来实现的,这并不灵活,因为网格列和 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>
Run Code Online (Sandbox Code Playgroud)

Fun*_*unk 1

通过相当多的苦劳,您可以通过扩展原生 来获得所需的外观DataGrid

第一张图片

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

第二张图

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

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

public interface ICanBeLastItem
{
    bool IsLastItem { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

行对象需要实现它才能正确绘制底部边框。

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

本机DataGrid提供了Sorting开箱即用的事件,但没有Sorted事件。模板与自定义事件的需求相结合,使我从子类化ColumnViewDataGrid不是将其声明为UserControl.

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

ColumnView通过绑定进行配置。一如既往,请随意扩展。当前的实现期望自动生成列。无论哪种情况,都会提供用于生成模板的代码。

<local:ColumnView ItemsSource="{Binding Items}" Background="LightSteelBlue"/>
Run Code Online (Sandbox Code Playgroud)

演示代码

列视图

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
}
Run Code Online (Sandbox Code Playgroud)

行项

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; }
}
Run Code Online (Sandbox Code Playgroud)

主窗口.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>
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

主视图模型

/// <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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)