【问题标题】:Find out the real (!) displayed size of a control in WPF找出 WPF 中控件的实际 (!) 显示大小
【发布时间】:2013-02-27 03:00:06
【问题描述】:

我正在对类似工具栏的控件进行一些布局,并且在空间不足时需要隐藏按钮的文本。我已经在 Windows 窗体中成功完成了这项工作,现在我已将此逻辑移植到 WPF。但是这里有一个很大的问题:为了让我的算法正常工作,我需要知道容器控件的所需宽度(知道如果所有内容都可见则需要什么大小)和控件的实际宽度(知道如何确实很宽,以及是否有足够的空间容纳所需的宽度)。第一个是可用的,尽管有时有点倒退。 (如果可用空间多于所需空间,则 DesiredSize 会增加以将其全部填满,尽管较少也可以。)后一个完全不可用!

我已经尝试过 ActualWidth,但如果 Grid 比窗口宽,ActualWidth 就会超出实际可见的范围。所以这一定是错的。然后我尝试了 RenderSize,但它是一样的。在我的 Measure 调用之后使用 Arrange 会导致更多的怪异。

我需要知道控件的实际宽度,而不是它认为自己的宽度。如何确定该尺寸?

更新:好的,这里有一些代码。这个问题已经很长了,仍然不完整。这是来自 Window 的代码隐藏。

private void ToolGrid_LayoutUpdated(object sender, EventArgs e)
{
    AutoCollapseItems();
}

private void AutoCollapseItems()
{
    if (collapsingItems) return;
    if (ToolGrid.ActualWidth < 10) return;   // Something is wrong
    try
    {
        collapsingItems = true;

        // Collapse toolbar items in their specified priority to save space until all items
        // fit in the toolbar. When collapsing, the item's display style is reduced from
        // image and text to image-only. This is only applied to items with a specified
        // collapse priority.

        Dictionary<ICollapsableToolbarItem, int> collapsePriorities = new Dictionary<ICollapsableToolbarItem, int>();

        // Restore the display style of all items that have a collpase priority.
        var items = new List<ICollapsableToolbarItem>();
        EnumCollapsableItems(ToolGrid, items);
        foreach (var item in items)
        {
            if (item.CollapsePriority > 0)
            {
                item.ContentVisibility = Visibility.Visible;
                collapsePriorities[item] = item.CollapsePriority;
            }
        }

        // Group all items by their descending collapse priority and set their display style
        // to image-only as long as all items don't fit in the toolbar.
        var itemGroups = from kvp in collapsePriorities
                         where kvp.Value > 0
                         group kvp by kvp.Value into g
                         orderby g.Key descending
                         select g;
        foreach (var grp in itemGroups)
        {
            //ToolGrid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            //ToolGrid.Arrange(new Rect(ToolGrid.DesiredSize));
            //ToolGrid.UpdateLayout();
            System.Diagnostics.Debug.WriteLine("Desired=" + ToolGrid.DesiredSize.Width + ", Actual=" + ToolGrid.ActualWidth);
            if (ToolGrid.DesiredSize.Width <= ToolGrid.ActualWidth) break;
            foreach (var kvp in grp)
            {
                kvp.Key.ContentVisibility = Visibility.Collapsed;
            }
        }
        //ToolGrid.UpdateLayout();
    }
    finally
    {
        collapsingItems = false;
    }
}

更多代码:这是 Window XAML 的一部分:

<Window>
    <DockPanel>
        <Grid Name="ToolGrid" DockPanel.Dock="Top" LayoutUpdated="ToolGrid_LayoutUpdated">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                ...
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
        </Grid>

【问题讨论】:

  • 这是在安排/测量期间使用的吗?还是你通过绑定来做这个?
  • 在代码隐藏中的 LayoutUpdated 事件上调用(但不是递归)。我还发现所需的宽度总是小于实际宽度。如果没有足够的空间,则 Desired 是可见的大小,Actual 是所需的。如果有足够的空间,则 Desired 是所需的大小,Actual 是可见的。所以这两个属性的含义转了转,我不知道哪个是哪个。没用。
  • 一些代码会有所帮助,你是继承UserControl还是Control
  • 代码很多,几百行。我会避免把他们都撕到这个有限的阶段。只是普通的 Grid 容器,没有什么特别的项目。我目前正在窗口代码隐藏上进行此布局。当它更稳定时,我可能会将它放在单独的控件中。

标签: wpf layout


【解决方案1】:

据我了解,您使用的是 Grid,但您将列宽度设置为 Auto,您如何使用 * 作为 Grid.Column 的宽度而不是 Auto。如果 Auto 然后 Grid 拉伸其宽度和高度以适合其内容,因此您的 Grid.Width 大于窗口宽度。当您使用 * 时,该列不会关心内容,但它始终位于窗口边界内。

现在在实现 * 之后,您可以使用位于窗口边界内的 column.width/height 作为最终宽度/高度,并且在 Grid 内您可以测量嵌套内部控件的缩小尺寸。这就是您获得最终尺寸和缩小尺寸的控件的方式。

显示更多代码/xaml,我们将能够为您提供更多帮助。

已编辑:

<Window>
<DockPanel x:Name="dockyPanel>
    <Grid Name="ToolGrid" DockPanel.Dock="Top" LayoutUpdated="ToolGrid_LayoutUpdated">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            ...
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
    </Grid>


var itemGroups = from kvp in collapsePriorities
                     where kvp.Value > 0
                     group kvp by kvp.Value into g
                     orderby g.Key descending
                     select g;
    double x = 0.0;
    foreach (var grp in itemGroups)
    {
        // x will be increased by the sum of all widths of items
        x += grp.SumOfAllWidthOfGroup;

        // if x greater than available space then this group needs to collaps its items
        if(x > this.dockyPanel.ActualWidth)
        {
          foreach (var kvp in grp)
          {
            kvp.Key.ContentVisibility = Visibility.Collapsed;
          }
        }
    }

这个怎么样?我的伪代码会进一步帮助你吗?

【讨论】:

  • 网格使用自动宽度的列和行,正确。但必须这样,因为工具栏项之间不能有空格。自动是正确的设置,* 会导致错误的布局。
  • 好的,所以你有一个根网格和一个用于工具栏项目的网格。然后告诉你的根网格不要关心内容。
  • 在问题中添加了更多代码。 “不关心内容”是什么意思?没有根 Grid,它是一个 DockPanel。
  • 好吧,我说根网格指向顶部的面板。在您的情况下,它是 DockPanel。我会改变我的问题,请稍等:)
  • 好了,怎么样?
【解决方案2】:

事实证明,WPF 不会为我提供可预测的 Grid 大小,该 Grid 包含所有带有按钮元素的自动调整大小的列。但是这个 Grid 的父元素,无论它是什么以及它是如何布局的,都提供了可用的信息。所以我的解决方案基本上是在已经存在的内容和我的实际工具栏布局网格之间插入另一个级别的 Grid 容器,并比较两者的不同大小。确定网格是否适合给定空间的中心测试现在是这样的:

foreach (var grp in itemGroups)
{
    InnerToolGrid.UpdateLayout();
    if (InnerToolGrid.RenderSize.Width - extentWidth <= ToolGrid.ActualWidth) break;
    foreach (var kvp in grp)
    {
        kvp.Key.ContentVisibility = Visibility.Collapsed;
    }
}

它遍历所有可能被折叠在一起的优先级元素类(无论它们位于网格上的哪个位置),并折叠一个类(组)中的所有元素。然后更新内部网格的布局以反映按钮的变化,并将内部网格的 RenderSize 与外部网格的 ActualWidth 进行比较。如果它更小,它就可以放入并且不需要折叠更多的项目。

所有这些都是从内部网格的 LayoutUpdated 事件调用的,仍然通过锁定变量防止递归。这使我可以对工具栏上任何按钮的大小变化做出反应,例如当文本更新时。

由于 LayoutCompleted 事件似乎是异步触发的,锁定变量必须保持设置直到下一次布局运行完成,并且不能在 LayoutUpdated 处理程序结束时再次重置:

private bool collapsingItems;
private void InnerToolGrid_LayoutUpdated(object sender, EventArgs e)
{
    if (collapsingItems) return;
    // Prevent further calls before the layouting is completely finished
    collapsingItems = true;
    Dispatcher.BeginInvoke(
        (Action) (() => { collapsingItems = false; }),
        System.Windows.Threading.DispatcherPriority.Loaded);
    // ...
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-05
    • 2011-08-04
    • 1970-01-01
    相关资源
    最近更新 更多