WPF: Star in ColumnDefinition not expanding columns - wpf

I have a user control that need the 1st and 3rd column to have the same width at all time.
My code is a follows:
<UserControl x:Class="UserControls.ListBoxSelector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox x:Name="ListBox_Source" Grid.Column="0" Grid.Row="0" />
<StackPanel Grid.Column="1" Grid.Row="0" Orientation="Vertical">
<Button Content=">" Click="Button_Add_Click"/>
<Button Content="<" Click="Button_Remove_Click" />
</StackPanel>
<ListBox x:Name="ListBox_Destination" Grid.Column="2" Grid.Row="0" />
</Grid>
</UserControl>
The result is not as expected as column 3 (ListBox_Destination) is not expanded at all.
Isn't the 5* in ColumnDefinition enough to force the 2 listbox to the same width??
UPDATED : Sorry that I forgot to mention that the problem only occurs when I put the control inside a RibbonGroup using Microsoft Ribbon for WPF

Sometimes, when you put your control in certian types of layout controls (like a StackPanel), it won't size as expected because the parent layout will only size the child to it's minimum desired size (just enough to show the content). This may be why you are seeing this when you put it in the RibbonGroup. Try giving your Grid a Width or MinWidth and see if that makes a difference.

yes it forces the columns 1 and 3 to be of the same size, but it doesnt gaurentee the content (listboxes) inside the colulms will be of the same size. You have to set the size of content to take up whole space

Related

WPF: Calculating the size of a Grid Column

I have a grid that contains three columns.
<Grid Background="AliceBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="2*" Name="ManualControlsSplit" />
</Grid.ColumnDefinitions>
The first column contains a Grid and a chart.
The second column contains a GridSplitter.
The third column contains a StackPanel which contains a number of TextBlocks, Buttons and Grids containing TextBlocks and Buttons. Text size is dynamic and based on translation resources.
I need to calculate the minimum width that the contents of the third column would ideally like to be able to be drawn into so that the contents are not clipped.
My knowledge of WPF is limited to what I can google so any help would be appreciated.
With layout in WPF, you can allow one if not more of the Grid.Columns to be <ColumnDefinition Width="Auto"/>. This essentially tells WPF to allow as much space as the contained controls want. The use of Auto cascades too, so in StackPanel you refer to, you can make that controls width Auto as well, likewise with the items it contains; if the stack panel merely contains a TextBlock (via some template or whatever), then you can also set this width to Auto and the width with set itself according to the contained text.
<Grid Background="AliceBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
...
<StackPanel Grid.Column=2
Width="Auto">
<TextBlock Width="Auto"
Text="This is TextBlock"/>
...
</StackPanel>
...
</Grid>
In this case the Text of the TextBlock sets the width of the StackPanel, which in turn sets the width of the 3rd grid column.
I hope this helps.
My WPF XAML setup is as follows (bits removed to keep this simple):
<UserControl ....>
<Grid Background="AliceBlue" Name="TopGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="12*" Name="Graph" />
<ColumnDefinition Width="5" Name="GridSplitter" />
<ColumnDefinition Width="2*" Name="ManualControlsSplit" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="AliceBlue"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<!-- A Graph and simple controls -->
</Grid>
<!-- GRID SPLITTER -->
<GridSplitter Grid.Column="1" Width="5"
HorizontalAlignment="Stretch" Name="Splitter" />
<Label Grid.Column="1" Content="⁞" Foreground="White"
Background="DarkGray"
VerticalAlignment="Center" FontSize="26" FontWeight="Bold"
IsHitTestVisible="False"/>
<!-- end GRID SPLITTER -->
<StackPanel Grid.Column="2" Grid.Row="0" Margin="5"
Name="TemperatureControls">
<!-- Load of Controls -->
</StackPanel>
</Grid>
</UserControl>
To calculate the desired width I use the following code:
// get my UserControl object
var manualControlView = userControl as HeatingController.Views.ManualControlView;
// Query the current width of THIRD column
double actualWidth = manualControlView.ManualControlsSplit.ActualWidth;
// Set up a BIG size (this has to be bigger size than the contents of the
// THIRD column would ever need)
Size size = new Size(400, manualControlView.TopGrid.ActualHeight);
// Ask WPF layout to calculate the Desired size.
manualControlView.TemperatureControls.Measure(size);
double width = manualControlView.TemperatureControls.DesiredSize.Width;
if (actualWidth <= width)
{
// Too small - do something
}
else
{
// big enough - do something else.
}
The variable 'width' now contains the value I wanted to calculate.

Preventing StackPanel items from overlapping

I am designing a Windows Phone application and I would like to stack a TextBlock and a Button next to each other horizontally. This is my pseudo-code on what I would like to achieve:
<StackPanel Name="titlePanel" Orientation="Horizontal" Margin="0,-6.5,0,26.5">
<TextBlock Name="titleBox" Text="{Binding Title}" Style="{ThemeResource HeaderTextBlockStyle}" CharacterSpacing="{ThemeResource PivotHeaderItemCharacterSpacing}"/>
<Button Content="press" Width="whatever space is left" Length="what the width is"/> <!--how do I do this?-->
</StackPanel>
I got an error when I tried to put Width="*", so I'm looking to do something like Width="titlePanel.Width - titleBox.Width" and Length="this.Width", only I can't seem to be able to reference other objects inside XAML. I don't want to put into the code-behind file to format the size of the Button every time that page comes up... how would this be achieved?
You can't do it with StackPanel because, the stack panel measures every child element with positive infinity as the constraint for the axis that it is stacking elements along. The child controls have to return how big they want to be (positive infinity is not a valid return from the MeasureOverride in either axis) so they return the smallest size where everything will fit. They have no way of knowing how much space they really have to fill.
So you should use grid to achieve desired behavior.
You can use grid control for this kind of purpose
try this one
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Name="titleBox" Grid.Column="0" Margin="5,0" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="Your text goes here." />
<Button Content="press" Grid.Column="1" Margin="5,0" HorizontalAlignment="Stretch"/>
</Grid>
Hope this helps.

How to bind Font Size to variable Grid Size

I have a grid with 2 columns and 2 rows. A single character (Unicode U+2699) was placed inside the bottom right grid field. It looks like this:
I´d like the character to automatically adjust its font size to fit the grid field it has been placed in (in this case it should be bound to the height of the second grid row, but since in some cases it could be unclear if the height or the width of the grid is smaller, it would be also nice to know how to bind to the lowest of those 2 values).
My implementation so far is something like the following (I simplified it a bit for this example):
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition x:Name="heightToBind" Height="40"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="14*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button FontSize="{Binding ElementName=heightToBind, Path=Height.Value, Mode=OneWay}" Content="⚙" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" />
</Grid>
The problem here is, that it only works if the height is a fixed value inside the RowDefinitions. I want it to work with the following definition:
<Grid.RowDefinitions>
<RowDefinition Height="4*"/>
<RowDefinition x:Name="heightToBind" Height="*"/>
</Grid.RowDefinitions>
As a bonus question I´d also be interested why it might be that the character is placed too low so it is getting cut off at the bottom (I tried VerticalAlignment="Center" for the button but with no effect).
You can try using a ViewBox as the button's content:
<Button Grid.Row="1" Grid.Column="1">
<Button.Content>
<Viewbox StretchDirection="Both" HorizontalAlignment="Stretch">
<TextBlock Text="⚙" />
</Viewbox>
</Button.Content>
</Button>
A ViewBox can stretch and scale his child to fill all the available space...
You could try binding to the ActualHeight instead of the Height:
<Button FontSize="{Binding ElementName=heightToBind, Path=ActualHeight.Value, Mode=OneWay}"
Content="⚙" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" />
This should work.
The * on the grid definition means take the available space as the height so it's only determined when the page layout has been prepared for layout. If the height is either unset or changed then the real height is returned in the ActualHeight property.

Recreating WinForms layout in WPF

I've just inherited an old WinForms app with a UI layout like this:
I'm tasked with updating several things about the software one of which is porting it to WPF which I've not previously worked with. I'm also told that the new WPF UI must look identical to the existing UI layout so I'm trying to figure out how to create that layout in WPF. I need a toolbar across the top of the window which stretches the entire width of the fixed-size window. Can I do that in the default grid or do I need a dockpanel to do that? Also, I'm assuming that I would use a grid with 2 columns and 3 rows to layout the six groupboxes?
Anything you can do with a DockPanel can also be done with a Grid -- the DockPanel is just a shortcut. So yes, you can do all this with the default Grid.
As for how to do the layout: it depends on how you want things to resize. Does everything stay proportional when you resize? If so, a single Grid with three columns (and percentage sizes for the ColumnDefinitions) would be fine. You would actually need four rows, though, not three -- the first RowDefinition would be for the ToolBar (using ColumnSpan="3") and would need Height="Auto" so it uses the ToolBar's default size; the remaining rows would be percentage-sized.
Try that, see if it works for you. If the resizing needs to be more complicated than just proportional, then post a second screenshot of the window at a different size, and we could try to help you further.
Personally I would use a DockPanel for the Menu/Content areas, then use a Grid in the Content Area to define the GroupBoxes
<DockPanel>
<Grid x:Name="MenuRegion" DockPanel.Dock="Top" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" />
<Grid Grid.Row="0" Grid.Column="2" />
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" />
<Grid Grid.Row="1" Grid.Column="2" />
<Grid Grid.Row="2" Grid.Column="0" />
<Grid Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" />
</DockPanel>
Of course, you could also make the Menu area part of the Grid and set it to span all rows, but I personally like keeping them separate.
Grid with 3 row (one for the menu). Since you have uneven spacing in the rows just a single column on the main grid. Then grid in the grid for the two column spacings.

WPF buttons same/recommended width

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?
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>
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.
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}"/>
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.
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.
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.

Resources