如何拥有宽度可调的固定列的自定义 Datagrid 控件?

How to have a Custom Datagrid control with fixed columns whose width is adjustable?

我想做的是制作一个已经有 2 个固定列的 CustomDatagrid。然后我可以重新使用此 CustomDatagrid 并添加额外的列以满足我的最佳目的。但是当我添加额外的列时,我希望能够调整 2 个固定列的大小。我尝试使用依赖属性作为下面的示例来解决它,但无济于事。而且我没有收到任何绑定错误,这让我不知道出了什么问题。

=> 这个小例子将阐明我正在尝试做的事情

CustomDataGrid.Xaml

<DataGrid x:Class="DataGridWidthTestControl.CustomDataGrid"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:DataGridWidthTestControl"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<DataGrid.Columns>
    <DataGridTextColumn Width="{Binding Column1Width}" Header="Column1"/>
    <DataGridTextColumn Width="{Binding Column2Width}" Header="Column2"/>
</DataGrid.Columns>

CustomDataGrid.Xaml.CS - 代码隐藏

namespace DataGridWidthTestControl
{
    /// <summary>
    /// Interaction logic for CustomDataGrid.xaml
    /// </summary>
    public partial class CustomDataGrid : DataGrid
    {
        public CustomDataGrid()
        {
            InitializeComponent();
            DataContext = this;
        }

        public static readonly DependencyProperty Column1WidthProperty = DependencyProperty.Register( "Column1Width", typeof(DataGridLength), typeof(CustomDataGrid), new FrameworkPropertyMetadata(new DataGridLength(25, DataGridLengthUnitType.Star)));

        public DataGridLength Column1Width
        {
            get { return (DataGridLength)GetValue(Column1WidthProperty); }
            set { SetValue(Column1WidthProperty, value); }
        }

        public static readonly DependencyProperty Column2WidthProperty = DependencyProperty.Register("Column2Width", typeof(DataGridLength), typeof(CustomDataGrid), new FrameworkPropertyMetadata(new DataGridLength(15, DataGridLengthUnitType.Star)));

        public DataGridLength Column2Width
        {
            get { return (DataGridLength)GetValue(Column2WidthProperty); }
            set { SetValue(Column2WidthProperty, value); }
        }

    }
}

Mainwindow.xaml(除默认初始化调用外代码隐藏为空)

<Window x:Class="DataGridWidthTestControl.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:DataGridWidthTestControl"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:CustomDataGrid Column1Width="1*" Column2Width="1*" >
            <DataGrid.Columns>
                <DataGridTextColumn Width="10*" Header="column3"/>
            </DataGrid.Columns>
        </local:CustomDataGrid>
    </Grid>
</Window>

您需要以编程方式将固定列插入 CustomDataGrid class,而不是在模板中指定它们。

类似于:

public override void OnApplyTemplate()
{
    if (!this.Columns.Any(c => c.Header.ToString() == "Column1"))
    {
        this.Columns.Insert(0,
                            new DataGridTextColumn
                            {
                                Width = this.Column1Width,
                                Header = "Column1"
                            });
    }

    if (!this.Columns.Any(c => c.Header.ToString() == "Column2"))
    {
        this.Columns.Insert(1,
                            new DataGridTextColumn
                            {
                                Width = this.Column2Width,
                                Header = "Column2"
                            });
    }

    base.OnApplyTemplate();
}

我不确定 OnApplyTemplate() 是否是执行此操作的正确时间,可能有更好的方法来覆盖,但这是我要完成这项工作的概念。

首先你需要知道没有指定任何来源的绑定(就像你的情况),使用 Binding.RelativeSource 的绑定和使用 Binding.ElementName 的绑定将不适用于 DataGridColumn,因为它直接派生自 DependencyObject,这是不够的。基本上,为了使这些工作,目标对象需要是从 FrameworkElementFrameworkContentElement 派生的类型,并且它应该是视觉树或逻辑树的一部分(DataGridColumns不是)。请注意,该要求几乎没有例外(例如 Freezable 在资源字典中定义),并且即将推出的框架版本会带来更多例外。

所以很遗憾,您需要明确指定 Binding.Source(这应该是您的 CustomDataGrid class 的一个实例)。我想不出在 XAML 中做这件事的任何方法(Source={x:Reference (...)} 不适用,因为你不能从它的定义中引用一个对象),所以我认为您需要回退到代码隐藏(这在设计自定义控件时并不罕见)。

最简单的方法是命名您的列:

<DataGrid.Columns>
    <DataGridTextColumn x:Name="Column1" x:FieldModifier="private" Header="Column1" />
    <DataGridTextColumn x:Name="Column2" x:FieldModifier="private" Header="Column2" />
</DataGrid.Columns>

x:FieldModifier="private" 是可选的)然后在控件初始化时设置绑定:

public CustomDataGrid()
{
    InitializeComponent();
    BindingOperations.SetBinding(Column1, DataGridColumn.WidthProperty, new Binding
    {
        Path = new PropertyPath(Column1WidthProperty),
        Source = this,
    });
    BindingOperations.SetBinding(Column2, DataGridColumn.WidthProperty, new Binding
    {
        Path = new PropertyPath(Column2WidthProperty),
        Source = this,
    });
}

您可能还想将 Mode = BindingMode.TwoWay 添加到绑定中,以便在用户手动调整列大小时控件上的值保持同步。

请注意,我故意删除了 DataContext = this 行,因为 a) 没有必要,而 b) 它会给出您在使用控件时遇到很多麻烦(例如,将 DataGrid.ItemsSource 绑定到视图模型 属性 将无法正常工作)。