WPF DataGrid:调整列大小

发布于 2024-12-05 04:34:08 字数 163 浏览 1 评论 0原文

我有一个 System.Windows.Controls.DataGrid,其属性 CanUserResizeColumns 分配为 True。现在我可以通过在两个列标题之间单击鼠标左键来调整列的宽度。

但我还希望能够更改 dataGrid 任何行中的列宽度,而不仅仅是列标题中的宽度。是否可以?

I have a System.Windows.Controls.DataGrid with property CanUserResizeColumns assigned to True. Now I can adjust the width of the columns by using the mouse left button click between 2 column headers.

But I also want to be able to change the width of the columns in any row of the dataGrid, not only in the column headers. Is it possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

云雾 2024-12-12 04:34:08

在您的 dataGrid 中,您可以使用 DataGridTemplate 列与 GridSplitter 来实现此目的。

 <toolkit:DataGridTemplateColumn Header="Text" >
     <toolkit:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
           <Grid>
              <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="*"/>
                 <ColumnDefinition  Width="Auto"/>
              </Grid.ColumnDefinitions>
              <TextBlock Text="{Binding Text}"/>
              <GridSplitter Grid.Column="1" Width="3"
                            DragIncrement="1"
                            DragDelta="GridSplitter_DragDelta"
                            Tag="{Binding BindsDirectlyToSource=True,
                                    RelativeSource={RelativeSource
                                      AncestorType={x:Type toolkit:DataGridCell}}}"/>
           </Grid>
        </DataTemplate>
     </toolkit:DataGridTemplateColumn.CellTemplate>
 </toolkit:DataGridTemplateColumn>

然后在您的代码后面...执行此操作...

    private void GridSplitter_DragDelta(
         object sender,
         System.Windows.Controls.Primitives.DragDeltaEventArgs e)
    {
        var gridSplitter = sender as GridSplitter;

        if (gridSplitter != null)
        {
            ((DataGridCell) gridSplitter.Tag).Column.Width
                = ((DataGridCell) gridSplitter.Tag).Column.ActualWidth +
                  e.HorizontalChange;
        }
    }

这样 GridSplitter 在单个单元格级别可以调整其整个列的大小。

如果您使用 MVVM,则应将上述事件处理程序放入附加行为中

In your dataGrid you can use a DataGridTemplate column alogn with a GridSplitter to achieve this..

 <toolkit:DataGridTemplateColumn Header="Text" >
     <toolkit:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
           <Grid>
              <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="*"/>
                 <ColumnDefinition  Width="Auto"/>
              </Grid.ColumnDefinitions>
              <TextBlock Text="{Binding Text}"/>
              <GridSplitter Grid.Column="1" Width="3"
                            DragIncrement="1"
                            DragDelta="GridSplitter_DragDelta"
                            Tag="{Binding BindsDirectlyToSource=True,
                                    RelativeSource={RelativeSource
                                      AncestorType={x:Type toolkit:DataGridCell}}}"/>
           </Grid>
        </DataTemplate>
     </toolkit:DataGridTemplateColumn.CellTemplate>
 </toolkit:DataGridTemplateColumn>

Then in your code behind... do this...

    private void GridSplitter_DragDelta(
         object sender,
         System.Windows.Controls.Primitives.DragDeltaEventArgs e)
    {
        var gridSplitter = sender as GridSplitter;

        if (gridSplitter != null)
        {
            ((DataGridCell) gridSplitter.Tag).Column.Width
                = ((DataGridCell) gridSplitter.Tag).Column.ActualWidth +
                  e.HorizontalChange;
        }
    }

This way a GridSplitter at individual cell level can resize its entire column.

If you are using MVVM then the above event handler should be put in an Attached Behavior

狼亦尘 2024-12-12 04:34:08

继 WPF-其出色的答案之后,以下是如何通过附加行为实现相同的结果:

public static class SplitterOnGridCellBehaviour
{
    public static readonly DependencyProperty ChangeGridCellSizeOnDragProperty =
        DependencyProperty.RegisterAttached("ChangeGridCellSizeOnDrag", typeof (bool),
                                            typeof (SplitterOnGridCellBehaviour),
                                            new PropertyMetadata(false, OnChangeGridCellSizeOnDrag));

private static void OnChangeGridCellSizeOnDrag(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
    GridSplitter splitter = dependencyObject as GridSplitter;

    if(splitter == null)
    {
        throw new NotSupportedException("SplitterOnGridCellBehaviour can only be on a GridSplitter");
    }

    if((bool)args.NewValue)
    {
        splitter.DragDelta += SplitterOnDragDelta;
    }
    else
    {
        splitter.DragDelta -= SplitterOnDragDelta;
    }
}

private static void SplitterOnDragDelta(object sender, DragDeltaEventArgs args)
{
    GridSplitter splitter = (GridSplitter)sender;
    var containerCell = splitter.FindParent<DataGridCell>();
    containerCell.Column.Width = containerCell.Column.ActualWidth + args.HorizontalChange;
}


public static void SetChangeGridCellSizeOnDrag(UIElement element, bool value)
{
    element.SetValue(ChangeGridCellSizeOnDragProperty, value);
}

public static bool GetChangeGridCellSizeOnDrag(UIElement element)
{
    return (bool) element.GetValue(ChangeGridCellSizeOnDragProperty);
}

public static T FindParent<T>(this DependencyObject child)
   where T : DependencyObject
{
    DependencyObject parentObject = VisualTreeHelper.GetParent(child);

    if (parentObject == null) return null;

    var parent = parentObject as T;
    if (parent != null)
    {
        return parent;
    }
    return FindParent<T>(parentObject);
    }
}

为了使所有网格拆分器在 DataGrid 中显示为一个,我将 DataGridCell 的 BorderThickness 调整为 0,否则所有网格分隔符显示为破折号(至少在 Windows 8 上)。

窗口的 XAML 如下所示:

<Window x:Class="DataGridWithSplitter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DataGridWithSplitter" Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="CellWithSplitterTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition  Width="Auto"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Column1}"/>
                <GridSplitter Grid.Column="1" Width="3" Background="Black" local:SplitterOnGridCellBehaviour.ChangeGridCellSizeOnDrag="True" />
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <DataGrid ItemsSource="{Binding SampleData}" GridLinesVisibility="None" HeadersVisibility="None" AutoGenerateColumns="False">
            <DataGrid.Resources>
                <!-- Makes the GridSplitters Solid -->
                <Style TargetType="DataGridCell">
                    <Setter Property="BorderThickness" Value="0" />
                </Style>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="First Column" CellTemplate="{StaticResource CellWithSplitterTemplate}"  />
                <DataGridTextColumn Header="Other column" Binding="{Binding Column2}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

其余部分的计算相当明显,但为了完整起见,Windows DataContext 设置为以下 ViewModel 代码的实例:

public class SampleData
{
    public string Column1 { get; set; }

    public string Column2 { get; set; }
}

public class MainWindowViewModel
{
    public IEnumerable<SampleData> SampleData
    {
        get
        {
            return new List<SampleData>()
                       {
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                       };
        }
    }
}

Following on from WPF-its excellent answer, here's how to achieve the same result with at attached behavior:

public static class SplitterOnGridCellBehaviour
{
    public static readonly DependencyProperty ChangeGridCellSizeOnDragProperty =
        DependencyProperty.RegisterAttached("ChangeGridCellSizeOnDrag", typeof (bool),
                                            typeof (SplitterOnGridCellBehaviour),
                                            new PropertyMetadata(false, OnChangeGridCellSizeOnDrag));

private static void OnChangeGridCellSizeOnDrag(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
    GridSplitter splitter = dependencyObject as GridSplitter;

    if(splitter == null)
    {
        throw new NotSupportedException("SplitterOnGridCellBehaviour can only be on a GridSplitter");
    }

    if((bool)args.NewValue)
    {
        splitter.DragDelta += SplitterOnDragDelta;
    }
    else
    {
        splitter.DragDelta -= SplitterOnDragDelta;
    }
}

private static void SplitterOnDragDelta(object sender, DragDeltaEventArgs args)
{
    GridSplitter splitter = (GridSplitter)sender;
    var containerCell = splitter.FindParent<DataGridCell>();
    containerCell.Column.Width = containerCell.Column.ActualWidth + args.HorizontalChange;
}


public static void SetChangeGridCellSizeOnDrag(UIElement element, bool value)
{
    element.SetValue(ChangeGridCellSizeOnDragProperty, value);
}

public static bool GetChangeGridCellSizeOnDrag(UIElement element)
{
    return (bool) element.GetValue(ChangeGridCellSizeOnDragProperty);
}

public static T FindParent<T>(this DependencyObject child)
   where T : DependencyObject
{
    DependencyObject parentObject = VisualTreeHelper.GetParent(child);

    if (parentObject == null) return null;

    var parent = parentObject as T;
    if (parent != null)
    {
        return parent;
    }
    return FindParent<T>(parentObject);
    }
}

To make all of the grid splitters appear as one in the DataGrid, I adjusted the BorderThickness of the DataGridCell to 0, otherwise all of the grid splitters appeared as dashes (on Windows 8 at least).

The XAML for the Window looks like this:

<Window x:Class="DataGridWithSplitter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DataGridWithSplitter" Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="CellWithSplitterTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition  Width="Auto"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Column1}"/>
                <GridSplitter Grid.Column="1" Width="3" Background="Black" local:SplitterOnGridCellBehaviour.ChangeGridCellSizeOnDrag="True" />
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <DataGrid ItemsSource="{Binding SampleData}" GridLinesVisibility="None" HeadersVisibility="None" AutoGenerateColumns="False">
            <DataGrid.Resources>
                <!-- Makes the GridSplitters Solid -->
                <Style TargetType="DataGridCell">
                    <Setter Property="BorderThickness" Value="0" />
                </Style>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="First Column" CellTemplate="{StaticResource CellWithSplitterTemplate}"  />
                <DataGridTextColumn Header="Other column" Binding="{Binding Column2}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

The rest of it is fairly obvious to work out, but for completeness the Windows DataContext was set to an instance of the following ViewModel code:

public class SampleData
{
    public string Column1 { get; set; }

    public string Column2 { get; set; }
}

public class MainWindowViewModel
{
    public IEnumerable<SampleData> SampleData
    {
        get
        {
            return new List<SampleData>()
                       {
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                            new SampleData() {Column1 = "Hello", Column2 = "World"},
                       };
        }
    }
}
从此见与不见 2024-12-12 04:34:08

这是一种不会污染数据网格内容的替代解决方案。在 DataGrid 顶部放置一个 Canvas,并且在该 Canvas 内有一条可以左右拖动的线。拖动时,它会更新所需的列宽。

XAML:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <DataGrid x:Name="grid" Grid.Row="0" /> <!-- This is your data grid -->
    <Canvas Grid.Row="0"> <!-- Canvas layerd over data grid -->
        <Line StrokeThickness="4" Stroke="Transparent" Cursor="SizeWE"
              X1="{Binding Columns[0].ActualWidth, ElementName=grid}"
              X2="{Binding X1, RelativeSource={RelativeSource Self}}"
              Y2="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Canvas}}}"
              MouseLeftButtonDown="OnSplitLineMouseLeftButtonDown"
              MouseLeftButtonUp="OnSplitLineMouseLeftButtonUp"
              MouseMove="OnSplitLineMouseMove"/>
    </Canvas>
</Grid>

C# 代码隐藏:

#region SplitBarHandling
bool splitBarDragging = false;
double splitBarMouseLastX = 0;
private void OnSplitLineMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    e.Handled = true;
    splitBarDragging = true;
    splitBarMouseLastX = e.GetPosition(null).X;
    ((UIElement)sender).CaptureMouse();
}

private void OnSplitLineMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    e.Handled = true;
    splitBarDragging = false;
    ((UIElement)sender).ReleaseMouseCapture();
}

private void OnSplitLineMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
    if (splitBarDragging)
    {
        e.Handled = true;
        double newX = e.GetPosition(null).X;
        grid.Columns[0].Width = grid.Columns[0].ActualWidth + (newX - splitBarMouseLastX);
        splitBarMouseLastX = newX;
    }
}
#endregion

注意我选择使该行透明,这样最终用户实际上不会看到它。这是因为我已经依赖数据网格本身来显示列之间的垂直网格线。
此外,您可以选择任何您认为用户友好的线条粗细,而不会影响网格单元的布局。我选择 4 是因为即使数据网格将垂直网格线渲染为 1 像素宽,它也可以轻松拾取。

示例代码来自我的自定义 PropertyGrid 代码库,它只有两列,因此是硬编码列 0。为了更通用,我会将其转换为附加行为,支持所需的尽可能多的列,或者子-类 DataGrid 本身。

与上一个解决方案相比,无论您有多少数据网格行,此解决方案都只添加了一些 WPF 元素来支持该行为,因此它在大型数据集上可能更高效且可扩展。

Here is an alternative solution that does not pollute your data grid contents. Layer a Canvas on top of the DataGrid, and within that Canvas have a Line that can be dragged left and right. When dragged, it updates the desired column width.

XAML:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <DataGrid x:Name="grid" Grid.Row="0" /> <!-- This is your data grid -->
    <Canvas Grid.Row="0"> <!-- Canvas layerd over data grid -->
        <Line StrokeThickness="4" Stroke="Transparent" Cursor="SizeWE"
              X1="{Binding Columns[0].ActualWidth, ElementName=grid}"
              X2="{Binding X1, RelativeSource={RelativeSource Self}}"
              Y2="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Canvas}}}"
              MouseLeftButtonDown="OnSplitLineMouseLeftButtonDown"
              MouseLeftButtonUp="OnSplitLineMouseLeftButtonUp"
              MouseMove="OnSplitLineMouseMove"/>
    </Canvas>
</Grid>

C# code-behind:

#region SplitBarHandling
bool splitBarDragging = false;
double splitBarMouseLastX = 0;
private void OnSplitLineMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    e.Handled = true;
    splitBarDragging = true;
    splitBarMouseLastX = e.GetPosition(null).X;
    ((UIElement)sender).CaptureMouse();
}

private void OnSplitLineMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    e.Handled = true;
    splitBarDragging = false;
    ((UIElement)sender).ReleaseMouseCapture();
}

private void OnSplitLineMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
    if (splitBarDragging)
    {
        e.Handled = true;
        double newX = e.GetPosition(null).X;
        grid.Columns[0].Width = grid.Columns[0].ActualWidth + (newX - splitBarMouseLastX);
        splitBarMouseLastX = newX;
    }
}
#endregion

Note I chose to make the line transparent so the final user will not actually see it. This is because I already rely on the data grid itself to show the vertical grid lines between columns.
Also, you may choose the line thickness to whatever you find to be user-friendly without affecting the layout of the grid cells. I chose 4 because it makes it easy to pickup even though the datagrid renders the vertical grid line as 1-pixel wide.

The example code comes from my custom PropertyGrid code-base, which has only two columns, hence the hard-coded column 0. For more generalization, I'd turn this into an attached behavior with support for as many columns needed, or sub-class DataGrid itself.

Compared to the previous solution, this one only adds a few WPF elements to support the behavior regardless of how many data grid rows you have, so it might be more efficient and scalable on large data sets.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文