在 WPF DataGrid 中,是否可以将列的大小调整为最后一列填充空白空间的内容,同时仍然允许水平滚动条?

问题描述 投票:0回答:1

我不知道如何在有限的标题中正确地表达我的问题,所以我会尽力解释它。我制作了用于展示的 gif,但在我没有足够的声誉点后注意到。

编辑:我现在对 gif 有足够的声誉!

我有一个 WPF 应用程序,我在其中使用 DataGrid 来显示 ViewModel 列表。此 DataGrid 填充了文件夹中的数据,您可以使用附加到按钮的FolderBrowserDialog 打开该文件夹。当您之前设置路径时,应用程序会记住它,并在启动时自动填充 DataGrid。

这意味着有两种状态:打开应用程序填充DataGrid,以及打开应用程序填充DataGrid。稍后请注意这一点。

我试图解决的问题是,每当 DataGrid 填充数据时,我需要所有列自动调整大小以适应其内容的宽度。但是,最后一列的宽度需要填充所有剩余的空白空间,因此 DataGrid 内部的行可以在 DataGrid 的整个宽度内选择。同时,当您将应用程序的大小调整到 DataGrid 的宽度变得比列的总宽度窄时,需要出现水平滚动条,以便您可以滚动列,其宽度仍与其内容相同.

这不仅需要在通过按钮填充数据时发生,而且还需要在应用程序启动时自动填充数据时发生。

我的困难在于找到适用于任何情况的解决方案。

我尝试了很多方法,但我将列出最成功的方法:

方法一:将所有列的宽度设置为自动,最后一列填充(*)

代码:

<Grid Grid.Column="0"
      Grid.ColumnSpan="2"
      Grid.Row="3" 
      Background="#f7f5f2"
      Margin="10">
    <DataGrid x:Name="ModList" 
              ItemsSource="{Binding Mods}"
              Style="{DynamicResource DataGridStyle1}" 
              CellStyle="{DynamicResource DataGridCellStyle1}" 
              ColumnHeaderStyle="{DynamicResource DataGridColumnHeaderStyle1}" 
              RowStyle="{DynamicResource DataGridRowStyle1}"
              RowDetailsTemplate="{DynamicResource DataGridRowDetailsTemplate1}"
              Margin="10"
              dd:DragDrop.IsDragSource="True" 
              dd:DragDrop.IsDropTarget="True" 
              dd:DragDrop.SelectDroppedItems="True" 
              dd:DragDrop.DropHandler="{Binding}" 
              dd:DragDrop.DropTargetAdornerBrush="#484D54">
     
        <DataGrid.Columns>
            <DataGridTemplateColumn x:Name="DataGridColumnEnabled"
                                    Width="Auto">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <CheckBox x:Name="CheckBoxIsEnabled"
                                  IsChecked="{Binding IsEnabled, UpdateSourceTrigger=PropertyChanged}"
                                  Command="{Binding DataContext.ToggleCheckBoxCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn x:Name="DataGridColumnLoadorder"
                                Header="Loadorder" 
                                IsReadOnly="True" 
                                Width="Auto" 
                                Binding="{Binding LoadOrder, UpdateSourceTrigger=PropertyChanged}" 
                                CanUserSort="False"/>
            <DataGridTextColumn x:Name="DataGridColumnMod"
                                Header="Mod" 
                                IsReadOnly="True"
                                Width="Auto"
                                Binding="{Binding DisplayName}" 
                                CanUserSort="False"
                                VirtualizingPanel.VirtualizationMode="Standard"/>
            <DataGridTemplateColumn x:Name="DataGridColumnNotification"
                                    IsReadOnly="True"
                                    Width="Auto"
                                    CanUserSort="False">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Image Source="/WarningIcon.png"
                               Height="16"
                               Width="16"
                               HorizontalAlignment="Stretch"
                               VerticalAlignment="Stretch"
                               Cursor="Help"
                               Visibility="{Binding HasConflicts}">
                            <ToolTipService.ToolTip>
                                <ToolTip Content="Mod(s) detected altering the same asset(s). &#x0a;This may be intentional, please check before committing loadorder."/>
                            </ToolTipService.ToolTip>
                        </Image>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn x:Name="DataGridColumnAuthor"
                                Header="Author" 
                                IsReadOnly="True" 
                                Width="Auto" 
                                Binding="{Binding Author}" 
                                CanUserSort="False"/>
            <DataGridTextColumn x:Name="DataGridColumnVersion"
                                Header="Version" 
                                IsReadOnly="True" 
                                Width="Auto"
                                Binding="{Binding Version}" 
                                CanUserSort="False"/>
            <DataGridTextColumn x:Name="DataGridColumnSource"
                                Header="Source"
                                IsReadOnly="True"
                                Width="*"
                                Binding="{Binding Source}"
                                CanUserSort="False"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

专业人士:

无论宽度如何,整个 DataGrid 中的行都可以选择。

Full Selectable Row

缺点:

调整大小时列会混在一起,并且没有水平滚动条(调整大小时它会闪烁,但我不知道为什么会这样)。

Mashed columns

用数据填充空 DataGrid 会导致列无法根据内容自动调整大小。

Columns not auto resizing

方法 2:与上面相同,但在代码隐藏中处理列的 MinWidth

代码:

在我的 XAML 中向 DataGrid 添加了事件:

SizeChanged="ModList_SizeChanged"

后台代码:

public partial class MainWindow : Window
{
    
    // Other code

    private void ModList_SizeChanged(object sender, EventArgs e)
    {
        DataGridColumnEnabled.MinWidth = DataGridColumnEnabled.ActualWidth;
        DataGridColumnLoadorder.MinWidth = DataGridColumnLoadorder.ActualWidth;
        DataGridColumnMod.MinWidth = DataGridColumnMod.ActualWidth;
        DataGridColumnNotification.MinWidth = DataGridColumnNotification.ActualWidth;
        DataGridColumnAuthor.MinWidth = DataGridColumnAuthor.ActualWidth;
        DataGridColumnVersion.MinWidth = DataGridColumnVersion.ActualWidth;
        DataGridColumnSource.MinWidth = 200;
    }
}

亲:

调整窗口大小时现在会出现水平滚动条

Horizontal scrollbar

Con

用数据填充空 DataGrid 仍然会导致列无法根据内容自动调整大小。

结论

最后一种方法是我现在一直在使用的方法。我需要找到一个解决方案,该解决方案基本上可以实现相同的优点,但在填充 DataGrid 时也会自动调整所有列的大小。

我开始觉得我的想法是错误的,这就是为什么我决定通过这里寻求帮助。

我真的希望有人能为我提供一个合适的解决方案,让 DataGrid 自动调整大小,在窗口太小时使用水平滚动条。

感谢您阅读这篇文章,我希望我已经以合适的方式构建了它。

期待您的建议!

c# wpf datagrid autosize datagridcolumn
1个回答
0
投票

您不得将列的宽度设置为

*
宽度,因为这会改变排列行为,因为它将强制最大化
*
列的空间。因此,自动列将会缩小(基于
DataGrid
的布局算法。

相反,您必须显式计算最后一列的宽度以使其填充。要保留默认的列大小(例如单元格大小或标题大小),您必须允许

DataGid
完成原始布局算法。然后最后调整最后一列。

对于一个优雅的解决方案,您可能需要考虑扩展

DataGrid
,以便您可以覆盖
ArrangeOverride
以在最后一列前面添加自定义计算。

以下示例扩展

DataGrid
以在内部实现逻辑。或者,您可以将调整逻辑移至附加行为或
DataGrid.SizeChanged
事件的事件处理程序。从设计角度来看,扩展
DataGrid
是最简洁的解决方案,因为布局逻辑已由元素本身正确封装。

<!-- 
     Setting the DataGrid.ColumnWidth is not required. 
     This example does this because one requirement was 
     to size the columns based on the cell content.
-->
<ExtendedDataGrid LastChildFill="True"
                  ColumnWidth="{x:Static DataGridLength.SizeToCells}" />
public class ExtendedDataGrid : DataGrid
{
  public bool LastChildFill
  {
    get => (bool)GetValue(LastChildFillProperty);
    set => SetValue(LastChildFillProperty, value);
  }

  public static readonly DependencyProperty LastChildFillProperty = DependencyProperty.Register(
    "LastChildFill",
    typeof(bool),
    typeof(ExtendedDataGrid),
    new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.AffectsArrange, OnLastChildFillChanged));

  private static void OnLastChildFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    => ((ExtendedDataGrid)d).OnLastChildFillChanged((bool)e.OldValue, (bool)e.NewValue);

  private const string VerticalScrollBarPartName = "PART_VerticalScrollBar";
  private ScrollBar PART_VerticalScrollBar { get; set; }
  private ScrollBarVisibility HorizontalScrollBarVisibilityInternal { get; set; }

  public ExtendedDataGrid()
  {
    this.HorizontalScrollBarVisibilityInternal = this.HorizontalScrollBarVisibility;
    this.Loaded += OnLoaded;
  }

  private void OnLoaded(object sender, RoutedEventArgs e)
  {
    this.Loaded -= OnLoaded;

    if (TryFindVisualChildElementByName(this, VerticalScrollBarPartName, out ScrollBar scrollBar))
    {
      this.PART_VerticalScrollBar = scrollBar;
    }
  }

  protected override Size ArrangeOverride(Size arrangeBounds)
  {
    arrangeBounds = base.ArrangeOverride(arrangeBounds);
    HandleLastColumnFill(arrangeBounds.Width);

    return arrangeBounds;
  }

  protected virtual void OnLastChildFillChanged(bool oldValue, bool newValue)
  {
    if (newValue)
    {
      // Backup original horizontalScrollBarVisibility
      this.HorizontalScrollBarVisibilityInternal = this.HorizontalScrollBarVisibility;
    }
    else
    {
      // Restore original horizontalScrollBarVisibility
      SetCurrentValue(HorizontalScrollBarVisibilityProperty, this.HorizontalScrollBarVisibilityInternal);
    }
  }

  private void HandleLastColumnFill(double availableWidth)
  {
    if (!this.LastChildFill
      || !this.IsLoaded
      || !this.HasItems)
    {
      return;
    }

    ScrollBarVisibility horizontalScrollBarVisibility = TryMakeLastColumnFill(availableWidth)
      ? ScrollBarVisibility.Disabled
      : ScrollBarVisibility.Visible;
    SetCurrentValue(HorizontalScrollBarVisibilityProperty, horizontalScrollBarVisibility);
  }

  private bool TryMakeLastColumnFill(double availableWidth)
  {
    double totalPrecedingColumnWidth = this.Columns.SkipLast(1).Sum(column => column.ActualWidth);
    double verticalScrollBarWidth = this.PART_VerticalScrollBar?.Width ?? 0;
    DataGridColumn lastColumn = this.Columns.Last();
    double maxCellContentWidth = this.Items
      .Cast<object>()
      .Max(item => lastColumn.GetCellContent(item).DesiredSize.Width);
    double desiredLastColumnWidth = maxCellContentWidth + verticalScrollBarWidth;
    double lastColumnAvailableWidth = availableWidth - (totalPrecedingColumnWidth);
    bool canLastColumnFill = lastColumnAvailableWidth > desiredLastColumnWidth;
    DataGridLength defaultColumnWidth = this.ColumnWidth;
    if (canLastColumnFill)
    {
      lastColumn.Width = new DataGridLength(lastColumnAvailableWidth, DataGridLengthUnitType.Pixel);
    }
    else
    {
      lastColumn.Width = defaultColumnWidth;
    }

    return canLastColumnFill;
  }

  private static bool TryFindVisualChildElementByName<TChild>(DependencyObject parent,
  string childElementName,
  out TChild resultElement) where TChild : FrameworkElement
  {
    resultElement = null;
    if (parent is Popup popup)
    {
      parent = popup.Child;
      if (parent == null)
      {
        return false;
      }
    }

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
      DependencyObject child = VisualTreeHelper.GetChild(parent, i);
      if (child is FrameworkElement frameworkElement
        && frameworkElement.Name.Equals(childElementName, StringComparison.OrdinalIgnoreCase))
      {
        resultElement = frameworkElement as TChild;
        return true;
      }

      if (child.TryFindVisualChildElementByName<TChild>(childElementName, out resultElement))
      {
        return true;
      }
    }

    return false;
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.