如何对齐 GridView(ListView)列中的文本或内容

How to align text or content in GridView (ListView) columns

我想在 ListView 中这样声明 GridViewColumn

<ListView ItemsSource="{Binding Sells}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="No. of order" Width="80"
                    DisplayMemberBinding="{Binding NoOfOrder, StringFormat=N0}"/>
            <GridViewColumn Header="Quantity" Width="100"
                    DisplayMemberBinding="{Binding Quantity, StringFormat=N0}"/>
            <GridViewColumn Header="Price" Width="100"
                    DisplayMemberBinding="{Binding Price, StringFormat=N2}"/>
        </GridView>
    </ListView.View>
</ListView>

但问题在于列 headers 以及单元格未按我希望的方式对齐,我的应用程序中有几个 ListView,我想要他们的一些专栏 headers 以及要居中对齐的单元格和其他要对齐的单元格,我在这个网站上找到的解决方案是做这样的事情:

<ListView ItemsSource="{Binding Buys}">
    <ListView.Resources>
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>
    </ListView.Resources>
    <ListView.View>
        <GridView>
            <GridViewColumn Width="80">
                <GridViewColumn.Header>
                    <GridViewColumnHeader Content="No. of order" HorizontalContentAlignment="Center"/>
                </GridViewColumn.Header>
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding NoOfOrder}" TextAlignment="Center"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>

            <GridViewColumn Width="100">
                <GridViewColumn.Header>
                    <GridViewColumnHeader Content="Quantity" HorizontalContentAlignment="Right"/>
                </GridViewColumn.Header>
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Quantity, StringFormat=N0}" TextAlignment="Right"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>

            <GridViewColumn Width="100">
                <GridViewColumn.Header>
                    <GridViewColumnHeader Content="Price" HorizontalContentAlignment="Right"/>
                </GridViewColumn.Header>
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Price, StringFormat=N2}" TextAlignment="Right"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

3 列 ListView 太多了!我还有其他一些有 7/8 列的东西,所以像这样重写它们肯定会很痛苦!有没有一种方法可以按照我在第一个示例中为 Sells 定义的方式声明这些 ListView 并具有一些模板和绑定机制,以便我可以这样编写这些列:

<ListView ItemsSource="{Binding Sells}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="No. of order" Align="Center" Width="80"
                    DisplayMemberBinding="{Binding NoOfOrder, StringFormat=N0}"/>
            <GridViewColumn Header="Quantity" Align="Right" Width="100"
                    DisplayMemberBinding="{Binding Quantity, StringFormat=N0}"/>
            <GridViewColumn Header="Price" Align="Right" Width="100"
                    DisplayMemberBinding="{Binding Price, StringFormat=N2}"/>
        </GridView>
    </ListView.View>
</ListView>

或类似的东西?

一个简单的解决方案是编写附加行为。此实现使用两个属性。 ListView.IsColumnContentAlignmentEnabled 必须在对应的 GridViewColumn 类型 TextAlignmentListViewListView.ColumnContentAlignment 上设置(如果此 属性 未设置,则该值将默认为 TextAlignment.Left)。

这是因为没有简单的方法来获取 GridViewColumn 的父级 ListView

以下附加行为允许对齐任何 FrameworkElement(不仅是 TextBlock 元素):

ListView.cs

// Attached Behavior
public class ListView : DependencyObject
{
  #region IsColumnContentAlignmentEnabled attached property

  public static readonly DependencyProperty IsColumnContentAlignmentEnabledProperty = DependencyProperty.RegisterAttached(
    "IsColumnContentAlignmentEnabled", typeof(bool), typeof(ListView), new PropertyMetadata(false, ListView.OnIsColumnContentAlignmentEnabledChanged));

  public static void SetIsColumnContentAlignmentEnabled(DependencyObject attachingElement, bool value) => attachingElement.SetValue(ListView.IsColumnContentAlignmentEnabledProperty, value);

  public static bool GetIsColumnContentAlignmentEnabled(DependencyObject attachingElement) => (bool) attachingElement.GetValue(ListView.IsColumnContentAlignmentEnabledProperty);

  #endregion

  #region ColumnContentAlignment attached property

  public static readonly DependencyProperty ColumnContentAlignmentProperty = DependencyProperty.RegisterAttached(
    "ColumnContentAlignment", typeof(TextAlignment), typeof(ListView), new PropertyMetadata(TextAlignment.Left, ListView.OnColumnContentAlignmentChanged));

  public static void SetColumnContentAlignment(DependencyObject attachingElement, TextAlignment value) => attachingElement.SetValue(ListView.ColumnContentAlignmentProperty, value);

  public static TextAlignment GetColumnContentAlignment(DependencyObject attachingElement) => (TextAlignment) attachingElement.GetValue(ListView.ColumnContentAlignmentProperty);

  #endregion

  private static Dictionary<GridViewColumn, System.Windows.Controls.ListView> ColumnListViewTable { get; }

  static ListView()
  {
    ListView.ColumnListViewTable = new Dictionary<GridViewColumn, System.Windows.Controls.ListView>();
  }

  private static void OnColumnContentAlignmentChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
  {
    // Get the parent ListView of the current column
    if (attachingElement is GridViewColumn gridViewColumn 
      && ListView.ColumnListViewTable.TryGetValue(gridViewColumn, out System.Windows.Controls.ListView listView))
    {
      ListView.AlignColumns(listView);
    }
  }

  private static void OnIsColumnContentAlignmentEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
  {
    if (!(attachingElement is System.Windows.Controls.ListView listView))
    {
      return;
    }

    bool isEnabled = (bool) e.NewValue;
    if (isEnabled)
    {
      if (!listView.IsLoaded)
      {
        listView.Loaded += ListView.InitializeColumnsOnListViewLoaded;
        return;
      }
      ListView.InitializeColumns(listView);
    }
    else
    {
      ListView.UnregisterColumns(listView);
    }
  }

  private static void InitializeColumnsOnListViewLoaded(object sender, RoutedEventArgs e)
  {
    var listView = sender as System.Windows.Controls.ListView;
    listView.Loaded -= ListView.InitializeColumnsOnListViewLoaded;
    ListView.InitializeColumns(listView);
  }

  private static void InitializeColumns(System.Windows.Controls.ListView listView)
  {
    if (ListView.TryRegisterColumns(listView))
    {
      ListView.AlignColumns(listView);
    }
  }

  private static void AlignColumns(System.Windows.Controls.ListView listView)
  {
    IEnumerable<ListBoxItem> listViewItemContainers = listView.Items
      .OfType<object>()
      .Select(item => listView.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem);

    foreach (ListBoxItem listViewItemContainer in listViewItemContainers)
    {
      if (!listViewItemContainer.TryFindVisualChildElement(out GridViewRowPresenter rowPresenter))
      {
        continue;
      }

      List<object> rowElements = LogicalTreeHelper.GetChildren(rowPresenter)
        .Cast<object>()
        .ToList();

      for (int columnIndex = 0; columnIndex < rowPresenter.Columns.Count; columnIndex++)
      {
        TextAlignment columnContentAlignment = ListView.GetColumnContentAlignment(rowPresenter.Columns.ElementAt(columnIndex));
        if (rowElements.ElementAt(columnIndex) is TextBlock textBlock)
        {
          textBlock.TextAlignment = columnContentAlignment;
        }

        // Because the TextBlock's width is set to Auto
        // the aligned text will always appear to be left aligned.
        // Therefore set equivalent HorizontalAlignment to compensate.
        // Also setting the HorizontalAlignment enables alignment of all FrameworkElements
        if (rowElements.ElementAt(columnIndex) is FrameworkElement frameworkElement)
        {
          frameworkElement.HorizontalAlignment = ListView.GetHorizontalAlignment(columnContentAlignment);
        }
      }
    }
  }

  private static HorizontalAlignment GetHorizontalAlignment(TextAlignment textBlockTextAlignment)
  {
    switch (textBlockTextAlignment)
    {
      case TextAlignment.Center: return HorizontalAlignment.Center;
      case TextAlignment.Right: return HorizontalAlignment.Right;
      case TextAlignment.Justify: return HorizontalAlignment.Stretch;
      default: return HorizontalAlignment.Left;
    }
  }

  private static void UnregisterColumns(System.Windows.Controls.ListView listView)
  {
    if (!(listView.View is System.Windows.Controls.GridView gridView))
    {
      return;
    }
    gridView.Columns
      .ToList()
      .ForEach(column => ListView.ColumnListViewTable.Remove(column));
  }

  private static bool TryRegisterColumns(System.Windows.Controls.ListView listView)
  {
    if (!(listView.View is System.Windows.Controls.GridView gridView))
    {
      return false;
    }
    gridView.Columns
      .ToList()
      .ForEach(column => ListView.ColumnListViewTable.Add(column, listView));
    return true;
  }
}

用法示例

<ListView ListView.IsColumnContentAlignmentEnabled="True"
          ItemsSource="{Binding Persons}">
  <ListView.View>
    <GridView>
      <GridViewColumn DisplayMemberBinding="{Binding FirstName}"
                      ListView.ColumnContentAlignment="Right"
                      Header="First Name" />
      </GridViewColumn>
      <GridViewColumn DisplayMemberBinding="{Binding LastName}"
                      ListView.ColumnContentAlignment="Center"
                      Header="Last Name" />
      </GridViewColumn>
    </GridView>
  </ListView.View>
</ListView>