11

Suppose you have a window with multiple buttons such as Ok/Cancel or Yes/No/Cancel. All the buttons need to be the same width. Obviously this could be done by just guessing a number and hardwiring all of them to that number.

Is there a better way to do it, one that would take into account preferred/recommended sizes (just how wide should an Ok button be anyway? This is not a rhetorical question, I actually don't know the answer!), what's needed by the text of the longest caption, what happens if the font size is increased etc?

rwallace
  • 31,405
  • 40
  • 123
  • 242
  • Please try not to hardcode UI size and such. It's bad practice when taking into account internationalization and (to a lesser extent now that WPF uses 'resolution independent units') various size and DPI screens. We have layout panels available to us to use. They're good. – Benny Jobigan Apr 17 '10 at 14:25
  • Buttons are supposed to be 50dlu x 23dlu. WPF doesn't support dialog units; so you're pretty much stuck. – Ian Boyd Aug 21 '11 at 03:50

6 Answers6

12

Another, perhaps simpler, way to do this is to use the SharedSizeGroup property on the ColumnDefinition and RowDefinition classes.

Columns (and Rows) in a WPF Grid can automatically resize to fit their contents - when SharedSizeGroup is used, columns with the same group name share their resizing logic.

The Xaml would look something like this ...

<Grid Grid.IsSharedSizeScope="True">

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition SharedSizeGroup="Buttons" />
        <ColumnDefinition SharedSizeGroup="Buttons" />
        <ColumnDefinition SharedSizeGroup="Buttons" />
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Button Grid.Column="1"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Center"
            Content="Ok"
            Margin="4" />

    <Button Grid.Column="2"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Center"
            Content="Cancel"
            Margin="4" />

    <Button Grid.Column="3"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Center"
            Content="Long Button Caption"
            Margin="4" />
</Grid>
Bevan
  • 43,618
  • 10
  • 81
  • 133
9

There are several ways to do this:

1) Use a Grid for layout. Each Button gets its own Column, which is Star-sized. That way, all columns are the same size:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Button Grid.Column="0">Yes</Button>
    <Button Grid.Column="1">No</Button>
    <Button Grid.Column="2">Cancel</Button>
</Grid>

2) You can have one item as "master size" and bind the width of all others to this item's width.

<StackPanel Orientation="Horizontal">
    <Button Name="MasterButton" Width="100">Yes</Button>
    <Button>
        <Button.Width>
            <Binding ElementName="MasterButton" Path="Width"/>
        </Button.Width>
        No
    </Button>
</StackPanel>

EDIT: In actual code, you probably will have Width="Auto". Since the other widths are based on the "master width", the button with the widest width (widest text) should be chosen.

Daniel Rose
  • 17,233
  • 9
  • 65
  • 88
  • +1 for recommending using panels in your first suggestion, but I'm really against hardcoding UI sizes. – Benny Jobigan Apr 17 '10 at 14:27
  • In the second case this was for demonstration that the binding is working. – Daniel Rose Apr 17 '10 at 14:34
  • The button with the widest width text may be unpredictable across languages. Is there any way to get all buttons the same width, allowing for the longest text to dictate what that is, no matter which button? – Andrew Arnott Nov 15 '21 at 01:04
  • 1
    @AndrewArnott In that case, use a Grid layout, as in the first variant of my answer or Bevan's anwer. – Daniel Rose Nov 15 '21 at 11:23
  • Yes, I went with [Bevan's answer](https://stackoverflow.com/a/6727672/46926) as that allowed all buttons to be the same width without assuming full width across the dialog or the widest width button. – Andrew Arnott Nov 16 '21 at 13:47
5

Use a "master" control, like in Daniel's answer, but bind to the "ActualWidth" attribute instead of "Width":

<StackPanel Orientation="Horizontal">
    <Button Name="MasterButton">Yes</Button>
    <Button>
        <Button.Width>
            <Binding ElementName="MasterButton" Path="ActualWidth"/>
        </Button.Width>
        No
    </Button>
</StackPanel>

This way, the value is taken from the master control at run time, after minimum and maximum width and all other layout calculations have been taken into account. Binding to "Width" binds to whatever you happen to put into the attribute at compile time, which may not be the width that is really used.

Also, the binding can be written shorter like

<Button Width="{Binding ElementName=MasterButton, Path=ActualWidth}"/>
Alexander Rautenberg
  • 2,205
  • 2
  • 22
  • 20
2

According to the MS User Experience Interaction Guidelines for Windows 7 and Windows Vista (p61), standard dimensions for command buttons are 50x14 DLU actual size (75x23 pixels). The guidelines further suggest you "try to work with [these] default widths and heights." Obviously, if you need more width to fit a clear label, then take more width.

Michael Zuschlag
  • 4,702
  • 15
  • 14
  • Buttons are supposed to be 50x14 dialog units. Your conversion to pixels is wrong; at least on my computer where i use `Georgia 14pt`. That is why dialog units exist - so people stop using pixel sizes. – Ian Boyd Aug 21 '11 at 03:50
1

These answers are great if you have a fixed number or fixed layout for the buttons, but if like me there is a dynamic number of buttons coming from a binding and contained in a ItemsControl then this is not feasible. But there is a simple way and it still involves used the sharedsize property of Grid.

DataTemplate:

<DataTemplate x:Key="ODIF.Mapping">
    <Button HorizontalContentAlignment="Left" Background="#FFEEEEEE" BorderBrush="#FFBDBDBD">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="PluginButtonsWidth"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" SharedSizeGroup="PluginButtonsIconHeight"/>
                <RowDefinition Height="Auto" SharedSizeGroup="PluginButtonsNameHeight"/>
            </Grid.RowDefinitions>
            <Image Width="32" Height="32" Source="{Binding PluginIcon}" RenderOptions.BitmapScalingMode="HighQuality"/>
            <TextBlock Grid.Row="1" Text="{Binding PluginName}"/>
        </Grid>
    </Button>
</DataTemplate>

Parent container:

<ItemsControl ItemsSource="{Binding MappingPlugins, ElementName=page}" ItemTemplate="{StaticResource ODIF.Mapping}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Grid.IsSharedSizeScope="True"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

Essentially the button's content can itself be a Gird which then you can place your labels and icons as needed in, but even though the buttons do not reside in the same grid (they each are their own) the grid can still share it size so long as you set the root container's (ItemsControl) property of Grid.IsSharedSizeScope to True.

This will force the content grid of each button to be the same exact size based on the largest one while not having to have the Buttons themselves in a predefined grid.

Wobbles
  • 3,033
  • 1
  • 25
  • 51
0

In the most general case, you want to create a Style in your section, then apply this style as desired. Now when you change the style, all buttons change.

Or you can change the Content of the button so that it autosizes to the text.

Dave
  • 14,618
  • 13
  • 91
  • 145
  • Thanks. But, correct me if I'm wrong, but wouldn't making a button autosize to the text, break the requirement that all buttons be the same width? And, isn't the style solution, just a particular way of implementing 'guess a number and hardwire all the buttons to that number'? – rwallace Apr 17 '10 at 13:54
  • Ah... I understand your requirement now, sorry! Daniel has a much better answer, then. – Dave Apr 17 '10 at 14:16