如何拥有宽度可调的固定列的自定义 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
,这是不够的。基本上,为了使这些工作,目标对象需要是从 FrameworkElement
或 FrameworkContentElement
派生的类型,并且它应该是视觉树或逻辑树的一部分(DataGridColumn
s不是)。请注意,该要求几乎没有例外(例如 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
绑定到视图模型 属性 将无法正常工作)。
我想做的是制作一个已经有 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
,这是不够的。基本上,为了使这些工作,目标对象需要是从 FrameworkElement
或 FrameworkContentElement
派生的类型,并且它应该是视觉树或逻辑树的一部分(DataGridColumn
s不是)。请注意,该要求几乎没有例外(例如 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
绑定到视图模型 属性 将无法正常工作)。