找出WPF中控件的真实(!)显示大小

时间:2013-02-25 19:43:59

标签: wpf layout

我在类似工具栏的控件上做了一些布局,并且当没有足够的空间时需要隐藏按钮文本。我已经在Windows Forms中成功完成了此操作,现在我已将此逻辑移植到WPF。但是这里存在一个很大的问题:为了使我的算法正常工作,我需要知道容器控件的所需宽度(知道如果一切都可见,将需要什么尺寸)和控件的实际宽度(知道如何它确实是宽的,是否有足够的空间用于所需的宽度)。第一个是可用的,虽然有时有点倒退。 (如果有更多可用空间,DesiredSize会增加以填满所有空间,尽管不会更好。)后者完全不可用!

我尝试过使用ActualWidth,但如果Grid比窗口宽,则ActualWidth会超出实际可见范围。所以这肯定是错的。然后我尝试了RenderSize,但它是一样的。在我的测量呼叫之后使用安排导致更多的怪异。

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

更新:好的,这里有一些代码。这个问题已经很长,但仍然不完整。这是来自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>

2 个答案:

答案 0 :(得分:1)

根据我的理解你使用网格,但你将列宽设置为自动,你如何使用*作为Grid的宽度.Column而不是Auto。如果Auto then 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;
          }
        }
    }

这个怎么样?我的伪代码能帮助你吗?

答案 1 :(得分:0)

事实证明,WPF不会给我可预测的网格大小,其中包含所有自动调整大小的列,其中包含按钮元素。但是这个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);
    // ...
}