WPF工具栏换行

时间:2015-06-26 14:44:58

标签: wpf toolbar wrapping

我有一个带有ToolBarTray的WPF应用程序,该ToolBarTray由一些带有大量按钮的ToolBar组成。我唯一想要的是让它们始终可见,无论窗口大小如何。

<ToolBarTray>
    <ToolBar OverflowMode="Never">
        <userControls:SearchUserControl x:Name="SearchControl" />
    </ToolBar>
    <ToolBar ItemsSource="{Binding CommonCommands}" OverflowMode="Never"/>
    <ToolBar ItemsSource="{Binding Type1Commands}" OverflowMode="Never"/>
    <ToolBar ItemsSource="{Binding Type2Commands}" OverflowMode="Never" />
    <ToolBar ItemsSource="{Binding Type3Commands}" OverflowMode="Never" />
</ToolBarTray>

我设置了OverflowMode =&#34;从不&#34;当没有足够的空间因为我总是希望所有按钮都可见时,摆脱按钮所在的右边的小箭头。我的命令&#34; list是带有DataTemplate的RoutedCommand,显示为一个按钮,但无论我使用什么,它都会做出同样的反应。

如果我将ToolBarTray放在StackPanel中,他们的按钮/工具栏只会继续超过窗口大小。如果我将ToolBarTray放在WrapPanel中,而不是将其包裹起来,则完全隐藏按钮(仅保留工具栏夹点)。

我希望实现的功能是工具栏动态更改其Band属性,以便在没有可用空间的情况下,工具栏切换到第二个band(行)而不是将按钮隐藏在其溢出面板中

2 个答案:

答案 0 :(得分:3)

包装ToolBar的内容并不容易,但当然有可能。关键是标准ToolBar控件需要一个名为“PART_ToolBarPanel”的TemplatePart,其类型必须为ToolBarPanel。 ToolBarPanel继承自StackPanel,但为了实现目标,您应该使用WrapPanel。 因此,由于ToolBarPanel继承自StackPanel,因此无法从WrapPanel同时继承。

我的想法是扩展ToolBarPanel并伪装它,使它看起来像一个WrapPanel。首先让我们看一下ToolBarPanel的代码(我使用ILSpy来检索WrapPanel代码行为):

public class ToolBarPanel : System.Windows.Controls.Primitives.ToolBarPanel
{
    private struct UVSize
    {
        internal double U;
        internal double V;
        private Orientation _orientation;
        internal double Width
        {
            get
            {
                if (this._orientation != Orientation.Horizontal)
                {
                    return this.V;
                }
                return this.U;
            }
            set
            {
                if (this._orientation == Orientation.Horizontal)
                {
                    this.U = value;
                    return;
                }
                this.V = value;
            }
        }
        internal double Height
        {
            get
            {
                if (this._orientation != Orientation.Horizontal)
                {
                    return this.U;
                }
                return this.V;
            }
            set
            {
                if (this._orientation == Orientation.Horizontal)
                {
                    this.V = value;
                    return;
                }
                this.U = value;
            }
        }
        internal UVSize(Orientation orientation, double width, double height)
        {
            this.U = (this.V = 0.0);
            this._orientation = orientation;
            this.Width = width;
            this.Height = height;
        }
        internal UVSize(Orientation orientation)
        {
            this.U = (this.V = 0.0);
            this._orientation = orientation;
        }
    }

    private static int _itemWidth = 80;
    private static int _itemHeight = 30;

    private static bool IsWidthHeightValid(object value)
    {
        double num = (double)value;
        return DoubleUtil.IsNaN(num) || (num >= 0.0 && !double.IsPositiveInfinity(num));
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        int num = 0;
        double itemWidth = _itemWidth;
        double itemHeight = _itemHeight;
        double num2 = 0.0;
        double itemU = (this.Orientation == Orientation.Horizontal) ? itemWidth : itemHeight;
        ToolBarPanel.UVSize uVSize = new ToolBarPanel.UVSize(this.Orientation);
        ToolBarPanel.UVSize uVSize2 = new ToolBarPanel.UVSize(this.Orientation, finalSize.Width, finalSize.Height);
        bool flag = !DoubleUtil.IsNaN(itemWidth);
        bool flag2 = !DoubleUtil.IsNaN(itemHeight);
        bool useItemU = (this.Orientation == Orientation.Horizontal) ? flag : flag2;
        UIElementCollection internalChildren = base.InternalChildren;
        int i = 0;
        int count = internalChildren.Count;
        while (i < count)
        {
            UIElement uIElement = internalChildren[i];
            if (uIElement != null)
            {
                ToolBarPanel.UVSize uVSize3 = new ToolBarPanel.UVSize(this.Orientation, flag ? itemWidth : uIElement.DesiredSize.Width, flag2 ? itemHeight : uIElement.DesiredSize.Height);
                if (DoubleUtil.GreaterThan(uVSize.U + uVSize3.U, uVSize2.U))
                {
                    this.ArrangeLine(num2, uVSize.V, num, i, useItemU, itemU);
                    num2 += uVSize.V;
                    uVSize = uVSize3;
                    if (DoubleUtil.GreaterThan(uVSize3.U, uVSize2.U))
                    {
                        this.ArrangeLine(num2, uVSize3.V, i, ++i, useItemU, itemU);
                        num2 += uVSize3.V;
                        uVSize = new ToolBarPanel.UVSize(this.Orientation);
                    }
                    num = i;
                }
                else
                {
                    uVSize.U += uVSize3.U;
                    uVSize.V = Math.Max(uVSize3.V, uVSize.V);
                }
            }
            i++;
        }
        if (num < internalChildren.Count)
        {
            this.ArrangeLine(num2, uVSize.V, num, internalChildren.Count, useItemU, itemU);
        }
        return finalSize;
    }

    private void ArrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU)
    {
        double num = 0.0;
        bool flag = this.Orientation == Orientation.Horizontal;
        UIElementCollection internalChildren = base.InternalChildren;
        for (int i = start; i < end; i++)
        {
            UIElement uIElement = internalChildren[i];
            if (uIElement != null)
            {
                ToolBarPanel.UVSize uVSize = new ToolBarPanel.UVSize(this.Orientation, uIElement.DesiredSize.Width, uIElement.DesiredSize.Height);
                double num2 = useItemU ? itemU : uVSize.U;
                uIElement.Arrange(new Rect(flag ? num : v, flag ? v : num, flag ? num2 : lineV, flag ? lineV : num2));
                num += num2;
            }
        }
    }

    protected override Size MeasureOverride(Size constraint)
    {
        ToolBarPanel.UVSize uVSize = new ToolBarPanel.UVSize(this.Orientation);
        ToolBarPanel.UVSize uVSize2 = new ToolBarPanel.UVSize(this.Orientation);
        ToolBarPanel.UVSize uVSize3 = new ToolBarPanel.UVSize(this.Orientation, constraint.Width, constraint.Height);
        double itemWidth = _itemWidth;
        double itemHeight = _itemHeight;
        bool flag = !DoubleUtil.IsNaN(itemWidth);
        bool flag2 = !DoubleUtil.IsNaN(itemHeight);
        Size availableSize = new Size(flag ? itemWidth : constraint.Width, flag2 ? itemHeight : constraint.Height);
        UIElementCollection internalChildren = base.InternalChildren;
        int i = 0;
        int count = internalChildren.Count;
        while (i < count)
        {
            UIElement uIElement = internalChildren[i];
            if (uIElement != null)
            {
                uIElement.Measure(availableSize);
                ToolBarPanel.UVSize uVSize4 = new ToolBarPanel.UVSize(this.Orientation, flag ? itemWidth : uIElement.DesiredSize.Width, flag2 ? itemHeight : uIElement.DesiredSize.Height);
                if (DoubleUtil.GreaterThan(uVSize.U + uVSize4.U, uVSize3.U))
                {
                    uVSize2.U = Math.Max(uVSize.U, uVSize2.U);
                    uVSize2.V += uVSize.V;
                    uVSize = uVSize4;
                    if (DoubleUtil.GreaterThan(uVSize4.U, uVSize3.U))
                    {
                        uVSize2.U = Math.Max(uVSize4.U, uVSize2.U);
                        uVSize2.V += uVSize4.V;
                        uVSize = new ToolBarPanel.UVSize(this.Orientation);
                    }
                }
                else
                {
                    uVSize.U += uVSize4.U;
                    uVSize.V = Math.Max(uVSize4.V, uVSize.V);
                }
            }
            i++;
        }
        uVSize2.U = Math.Max(uVSize.U, uVSize2.U);
        uVSize2.V += uVSize.V;
        return new Size(uVSize2.Width, uVSize2.Height);
    }
}

为了这个示例的目的,我使用了两个静态变量(_itemWidth_itemHeight)来定义WrapPanel项目大小。无论如何,他们应该被两个依赖属性替换。此外,扩展的ToolBar不处理Orientation更改(只需使用ILSpy进行修改)。

为了使这段代码有效,我也添加了DoubleUtil类:

public static class DoubleUtil
{
    [StructLayout(LayoutKind.Explicit)]
    private struct NanUnion
    {
        [FieldOffset(0)]
        internal double DoubleValue;
        [FieldOffset(0)]
        internal ulong UintValue;
    }
    internal const double DBL_EPSILON = 2.2204460492503131E-16;
    internal const float FLT_MIN = 1.17549435E-38f;
    public static bool AreClose(double value1, double value2)
    {
        if (value1 == value2)
        {
            return true;
        }
        double num = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * 2.2204460492503131E-16;
        double num2 = value1 - value2;
        return -num < num2 && num > num2;
    }
    public static bool LessThan(double value1, double value2)
    {
        return value1 < value2 && !DoubleUtil.AreClose(value1, value2);
    }
    public static bool GreaterThan(double value1, double value2)
    {
        return value1 > value2 && !DoubleUtil.AreClose(value1, value2);
    }
    public static bool LessThanOrClose(double value1, double value2)
    {
        return value1 < value2 || DoubleUtil.AreClose(value1, value2);
    }
    public static bool GreaterThanOrClose(double value1, double value2)
    {
        return value1 > value2 || DoubleUtil.AreClose(value1, value2);
    }
    public static bool IsOne(double value)
    {
        return Math.Abs(value - 1.0) < 2.2204460492503131E-15;
    }
    public static bool IsZero(double value)
    {
        return Math.Abs(value) < 2.2204460492503131E-15;
    }
    public static bool AreClose(Point point1, Point point2)
    {
        return DoubleUtil.AreClose(point1.X, point2.X) && DoubleUtil.AreClose(point1.Y, point2.Y);
    }
    public static bool AreClose(Size size1, Size size2)
    {
        return DoubleUtil.AreClose(size1.Width, size2.Width) && DoubleUtil.AreClose(size1.Height, size2.Height);
    }
    public static bool AreClose(Vector vector1, Vector vector2)
    {
        return DoubleUtil.AreClose(vector1.X, vector2.X) && DoubleUtil.AreClose(vector1.Y, vector2.Y);
    }
    public static bool AreClose(Rect rect1, Rect rect2)
    {
        if (rect1.IsEmpty)
        {
            return rect2.IsEmpty;
        }
        return !rect2.IsEmpty && DoubleUtil.AreClose(rect1.X, rect2.X) && DoubleUtil.AreClose(rect1.Y, rect2.Y) && DoubleUtil.AreClose(rect1.Height, rect2.Height) && DoubleUtil.AreClose(rect1.Width, rect2.Width);
    }
    public static bool IsBetweenZeroAndOne(double val)
    {
        return DoubleUtil.GreaterThanOrClose(val, 0.0) && DoubleUtil.LessThanOrClose(val, 1.0);
    }
    public static int DoubleToInt(double val)
    {
        if (0.0 >= val)
        {
            return (int)(val - 0.5);
        }
        return (int)(val + 0.5);
    }
    public static bool RectHasNaN(Rect r)
    {
        return DoubleUtil.IsNaN(r.X) || DoubleUtil.IsNaN(r.Y) || DoubleUtil.IsNaN(r.Height) || DoubleUtil.IsNaN(r.Width);
    }
    public static bool IsNaN(double value)
    {
        DoubleUtil.NanUnion nanUnion = default(DoubleUtil.NanUnion);
        nanUnion.DoubleValue = value;
        ulong num = nanUnion.UintValue & 18442240474082181120uL;
        ulong num2 = nanUnion.UintValue & 4503599627370495uL;
        return (num == 9218868437227405312uL || num == 18442240474082181120uL) && num2 != 0uL;
    }
}

现在我们只需要在XAML中定义一些资源(再次使用ILSpy):

<Style x:Key="ToolbarThumb" TargetType="{x:Type Thumb}">
    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Thumb}">
                <Border Padding="{TemplateBinding Control.Padding}" Background="#00FFFFFF" SnapsToDevicePixels="True">
                    <Rectangle>
                        <Rectangle.Fill>
                            <DrawingBrush Viewbox="0,0,4,4" Viewport="0,0,4,4" TileMode="Tile" ViewportUnits="Absolute" ViewboxUnits="Absolute">
                                <DrawingBrush.Drawing>
                                    <DrawingGroup>
                                        <DrawingGroup.Children>
                                            <GeometryDrawing Brush="#FFFFFFFF" Geometry="M1,1L1,3 3,3 3,1z" />
                                            <GeometryDrawing Brush="#A0A0A0" Geometry="M0,0L0,2 2,2 2,0z" />
                                        </DrawingGroup.Children>
                                    </DrawingGroup>
                                </DrawingBrush.Drawing>
                            </DrawingBrush>
                        </Rectangle.Fill>
                    </Rectangle>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="UIElement.IsMouseOver" Value="True">
                        <Setter Property="FrameworkElement.Cursor" Value="SizeAll" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style x:Key="NoOverflowItems" TargetType="ToolBar" BasedOn="{StaticResource {x:Type ToolBar}}">
    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToolBar}">
                <Grid Name="Grid" Margin="3,1,1,1" SnapsToDevicePixels="True">
                    <Border Name="MainPanelBorder" Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="{TemplateBinding Control.BorderThickness}" Padding="{TemplateBinding Control.Padding}">
                        <DockPanel KeyboardNavigation.TabIndex="1" KeyboardNavigation.TabNavigation="Local">
                            <Thumb Name="ToolBarThumb" Style="{StaticResource ToolbarThumb}" Margin="-3,-1,0,0" Width="10" Padding="6,5,1,6" />
                            <ContentPresenter Name="ToolBarHeader" ContentSource="Header" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="4,0,4,0" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                            <local:ToolBarPanel x:Name="PART_ToolBarPanel" IsItemsHost="True" Margin="0,1,2,2" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                        </DockPanel>
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Value="{x:Null}" Property="HeaderedItemsControl.Header">
                        <Setter TargetName="ToolBarHeader" Property="UIElement.Visibility" Value="Collapsed" />
                    </Trigger>
                    <Trigger Property="ToolBarTray.IsLocked" Value="True">
                        <Setter TargetName="ToolBarThumb" Property="UIElement.Visibility" Value="Collapsed" />
                    </Trigger>
                    <Trigger Property="ToolBar.Orientation" Value="Vertical">
                        <Setter TargetName="Grid" Property="FrameworkElement.Margin" Value="1,3,1,1" />
                        <Setter TargetName="ToolBarThumb" Property="FrameworkElement.Height" Value="10" />
                        <Setter TargetName="ToolBarThumb" Property="FrameworkElement.Width" Value="Auto" />
                        <Setter TargetName="ToolBarThumb" Property="FrameworkElement.Margin" Value="-1,-3,0,0" />
                        <Setter TargetName="ToolBarThumb" Property="Control.Padding" Value="5,6,6,1" />
                        <Setter TargetName="ToolBarHeader" Property="FrameworkElement.Margin" Value="0,0,0,4" />
                        <Setter TargetName="PART_ToolBarPanel" Property="FrameworkElement.Margin" Value="1,0,2,2" />
                        <Setter TargetName="ToolBarThumb" Property="DockPanel.Dock" Value="Top" />
                        <Setter TargetName="ToolBarHeader" Property="DockPanel.Dock" Value="Top" />

                        <Setter TargetName="MainPanelBorder" Property="FrameworkElement.Margin" Value="0,0,0,11" />
                    </Trigger>
                    <Trigger Property="UIElement.IsEnabled" Value="False">
                        <Setter Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" Property="Control.Foreground" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

现在让我们创建一个小窗口(300x300)并添加这个XAML:

<ToolBarTray>
    <ToolBar OverflowMode="Never" Band="1">
        <Button Content="Button1" Width="76" Height="26" Margin="2" />
        <Button Content="Button2" Width="76" Height="26" Margin="2" />
        <Button Content="Button3" Width="76" Height="26" Margin="2" />
        <Button Content="Button4" Width="76" Height="26" Margin="2" />
        <Button Content="Button5" Width="76" Height="26" Margin="2" />
        <Button Content="Button6" Width="76" Height="26" Margin="2" />
    </ToolBar>
    <ToolBar Style="{StaticResource NoOverflowItems}" Band="2">
        <Button Content="ButtonA" Width="76" Height="26" Margin="2" />
        <Button Content="ButtonB" Width="76" Height="26" Margin="2" />
        <Button Content="ButtonC" Width="76" Height="26" Margin="2" />
        <Button Content="ButtonD" Width="76" Height="26" Margin="2" />
        <Button Content="ButtonF" Width="76" Height="26" Margin="2" />
        <Button Content="ButtonG" Width="76" Height="26" Margin="2" />
    </ToolBar>
</ToolBarTray>

我希望我的代码能为您提供帮助。

答案 1 :(得分:0)

最初提出问题的四年之后,但是如果我正确理解了问题,可以通过在WrapPanel中而不是ToolBarTray中添加ToolBar控件来实现。缺点是失去了ToolBarTray的功能,例如在托盘内移动ToolBar的功能,但是如果这不是问题,则可以很好地发挥作用。