WPF:确保该按钮具有相同的宽度

时间:2015-09-15 13:58:15

标签: c# wpf xaml

我目前在StackPanel内有多个ButtonButton的内容是动态的,所以我不知道它们的大小。

每个Button都有不同的边距。

我想让所有按钮具有相同的宽度(不计算其中的边距)。

以下是一个例子:

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
            <Button Margin="3" Content="{Binding PreviousButtonText, ElementName=CurrentControl}" Command="{Binding GoToPreviousPageCommand}"/>
            <Button Margin="3,3,8,3"  Content="{Binding NextButtonText, ElementName=CurrentControl}" Command="{Binding GoToNextPageCommand}" Visibility="{Binding IsLastPage, Converter={StaticResource BooleanToVisibleConverter}, ConverterParameter=true, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Margin="3,3,8,3"  Content="{Binding FinishButtonText, ElementName=CurrentControl}" Command="{Binding FinishCommand}" Visibility="{Binding IsLastPage, Converter={StaticResource BooleanToVisibleConverter}, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Margin="8,3,3,3" Content="{Binding CancelButtonText, ElementName=CurrentControl}" Command="{Binding CancelCommand}"/>
        </StackPanel>

由于我需要具有相同的宽度,但不计算其中的边距,我不能使用UniformGrid(或者SharedSizeGroup的网格。)

目标是让所有按钮占据其中一个按钮所需的“最大宽度”。

这必须在XAML中完成,没有代码。 另外,按钮内的文本可以在运行时更改。

知道怎么做吗?

编辑似乎很多人都不明白我需要一个宽度相同的按钮,而不是按钮+他的边距相同的宽度。

你所有的解决方案都是这样的:你可以清楚地看到按钮的大小不同(我增加了“8”边距值以更好地显示问题)。 enter image description here

这是我想要实现的目标(我因为设置了一些固定值而作弊): enter image description here

5 个答案:

答案 0 :(得分:3)

这是一个继承标准WPF Button控件的小类。它确保当任何按钮改变其大小并成为具有最大宽度的按钮时,同一容器内的控件共享相同的宽度。 但是,这只是一个概念的原则,您应该根据容器(可能不仅仅是StackPanels)来完成它,也许可以用异常处理错误,而不是return;语句等等。

稍后编辑:您指定不应该在此处使用代码隐藏解决方案。您的要求是否也禁止新的控制类?如果是,那么这不适用。

internal class WidthButton : Button
{
    private static Dictionary<Panel, List<Button>> _containers = new Dictionary<Panel, List<Button>>();

    public WidthButton()
    {
        this.Initialized += WidthButton_Initialized;
        this.SizeChanged += WidthButton_SizeChanged;
    }

    void WidthButton_Initialized(object sender, EventArgs e)
    {
        var parent = VisualTreeHelper.GetParent(this) as Panel;
        if (parent == null) return;

        var thisButton = sender as Button;
        if (thisButton == null) return;

        if (!_containers.ContainsKey(parent))
        {
           _containers.Add(parent, new List<Button>()); 
        }

        if (!_containers[parent].Contains(thisButton))
        {
            _containers[parent].Add(thisButton);
        }
    }

    void WidthButton_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
    {
        var thisButton = sender as Button;
        if (thisButton == null) return;

        var containerPair = _containers.FirstOrDefault(pair => pair.Value.Contains(thisButton));
        if (containerPair.Value == null) return;

        var maxWidth = containerPair.Value.Max(btn => btn.ActualWidth);
        containerPair.Value.ForEach(btn => btn.Width = maxWidth);
    }
}

稍后编辑:这是一个使用附加行为的示例(潜在的内存泄漏,因为没有取消订阅SizeChanged事件)

internal class WidthBehavior
{
    private static Dictionary<string, List<FrameworkElement>> _scopes = new Dictionary<string, List<FrameworkElement>>();

    public static readonly DependencyProperty WidthShareScopeProperty = DependencyProperty.RegisterAttached(
        "WidthShareScope", typeof (string), typeof (WidthBehavior), new PropertyMetadata(default(string), PropertyChangedCallback));

    private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        var elem = dependencyObject as FrameworkElement;
        if (elem == null) return;

        var scope = dependencyPropertyChangedEventArgs.NewValue as string;
        if (scope == null) return;

        if (!_scopes.ContainsKey(scope))
        {
            _scopes.Add(scope, new List<FrameworkElement>());
        }

        if (!_scopes[scope].Contains(elem))
        {
            _scopes[scope].Add(elem);
            elem.SizeChanged += elem_SizeChanged;
        }
    }

    static void elem_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        var elem = sender as FrameworkElement;
        if (elem == null) return;

        var scope = GetWidthShareScope(elem);
        ArrangeScope(scope);
    }

    private static void ArrangeScope(string scope)
    {
        if (!_scopes.ContainsKey(scope)) return;

        var list = _scopes[scope];

        var maxWidth = list.Max(elem => elem.ActualWidth);
        list.ForEach(elem => elem.Width = maxWidth);
    }

    public static void SetWidthShareScope(DependencyObject element, string value)
    {
        element.SetValue(WidthShareScopeProperty, value);
    }

    public static string GetWidthShareScope(DependencyObject element)
    {
        return (string) element.GetValue(WidthShareScopeProperty);
    }
}

你可以像这样使用它:

<StackPanel Orientation="Horizontal">

        <Button Margin="10 15 13 12"
                behavior:WidthBehavior.WidthShareScope="Scope1"
                Content="Text"/>
        <Button Margin="7 2 14 11"
                behavior:WidthBehavior.WidthShareScope="Scope1"
                Content="Longer Text"/>

    </StackPanel>

答案 1 :(得分:1)

或者,您也可以使用UniformGrid。只需将UniformGrid.Rows设置为1即可实现Orientation="Horizontal"的相同行为:

<UniformGrid Rows="1" HorizontalAlignment="Right">
    <Button Margin="3" Content="{Binding PreviousButtonText, ElementName=CurrentControl}" Command="{Binding GoToPreviousPageCommand}"/>
    <Button Margin="3,3,8,3"  Content="{Binding NextButtonText, ElementName=CurrentControl}" Command="{Binding GoToNextPageCommand}" Visibility="{Binding IsLastPage, Converter={StaticResource BooleanToVisibleConverter}, ConverterParameter=true, UpdateSourceTrigger=PropertyChanged}"/>
    <Button Margin="3,3,8,3"  Content="{Binding FinishButtonText, ElementName=CurrentControl}" Command="{Binding FinishCommand}" Visibility="{Binding IsLastPage, Converter={StaticResource BooleanToVisibleConverter}, UpdateSourceTrigger=PropertyChanged}"/>
    <Button Margin="8,3,3,3" Content="{Binding CancelButtonText, ElementName=CurrentControl}" Command="{Binding CancelCommand}"/>
</UniformGrid>

答案 2 :(得分:1)

在这种情况下,您想要为某些属性width属性共享相同的值吗?为它创造风格:

<Style x:Key="DialogButton" BaseOn="{StaticResource YourDefaultButtonStyle}">
   <Setter Property="Width" Value="100" />
</Style>

或者使用一些布局面板,例如Grid:

<Grid IsSharedSizeScope="True">
   <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" SharedColumnSize="Button"/>
        <ColumnDefinition Width="10"/>
        <ColumnDefinition Width="Auto" SharedColumnSize="Button"/>
        <ColumnDefinition Width="50"/>
        <ColumnDefinition Width="Auto" SharedColumnSize="Button"/>
    </Grid.ColumnDefinitions>

    <Button Grid.Column="0" Margin="0" />
    <Button Grid.Column="2" Margin="0" />
    <Button Grid.Column="4" Margin="0" />

</Grid>

编辑:还有一个建议: 如果你的按钮在整个应用程序中具有相同的宽度,如果可能的话,这不仅仅是单个视图,这很好。创建指定MinWidth的显式样式。

<Style x:Key="DialogButton" BaseOn="{StaticResource YourDefaultButtonStyle}">
   <Setter Property="MinWidth" Value="100" />
</Style>

具有此样式的所有按钮将具有相同的宽度,但文本超出最小宽度。这就是大多数对话框中按钮的工作方式(例如,在Visual Studio选项对话框,保存对话框等中。)

答案 3 :(得分:0)

使用隐式风格?

但是你需要知道哪个Button有最大宽度并将其绑定到样式。

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
        <StackPanel.Resources>
            <Style TargetType="{x:Type Button}">
                <Setter Property="Width" Value="{Binding ButtonMaxWidth}"/>
            </Style>
        </StackPanel.Resources>
        <Button Margin="3" Content="11" />
        <Button Margin="3,3,8,3" Content="2222" />
        <Button Margin="3,3,8,3" Content="333333" />
        <Button Margin="8,3,3,3" Content="44444444" />
    </StackPanel>

答案 4 :(得分:0)

当我遇到Margin / Padding问题时,我更喜欢使用一个封闭的控制元素来独立于原始控件来处理它。一种解决方案是模仿MarginBorder没有厚度,没有画笔,Padding具有所需的保证金。

然后,如果以下解决方案,您必须事先知道哪个Button将具有最大宽度。因为我们需要将此ActualWidth的{​​{1}}复制到所有其他Button

Width