WPF Toolbar Items HorizontalAligment="Right" - wpf

Is it possible to make the elements within a WPF toolbar have a HorizontalAlignment of Right?
<ToolBar Height="38" VerticalAlignment="Top" Grid.Row="1">
<Button HorizontalAlignment="Left" Width="50" VerticalAlignment="Stretch"/>
<Button HorizontalAlignment="Left" Width="50" VerticalAlignment="Stretch"/>
<ComboBox Width="120" HorizontalAlignment="Right"/>
</ToolBar>
I've tried adding the elements inside into a Grid and assigning the ColumnDefinitions to Left/Right as well. I have also tried a StackPanel. No matter what I try I can't seem to get the ComboBox to be "anchored" on the right side of the Toolbar.
UPDATE:
<DockPanel LastChildFill="True">
Doesn't work, It will not fill the ToolBar element like it would a normal element.

Further investigation showed that in order to do this I need to set the width of a Grid within the ToolBar, or as Chris Nicol said, a DockPanel within the ToolBar dynamically to that of the width of the Toolbar using RelativeSource.
However, that did not feel like a clean solution. It is quite complicated to get the Toolbar to update correctly on resizing. So instead I found somewhat of a hack that looks, and operates cleaner by adding an external Grid.
<Grid>
<ToolBar Height="38" VerticalAlignment="Top" Grid.Row="1">
<Button HorizontalAlignment="Left" Width="50" VerticalAlignment="Stretch"/>
<Button HorizontalAlignment="Left" Width="50" VerticalAlignment="Stretch"/>
</ToolBar>
<ComboBox Margin="0,0,15,0" Width="120" HorizontalAlignment="Right" Grid.Row="1"/>
</Grid>
Since all of my elements are on a Grid, I can place my ComboBox on top of the ToolBar by assigning it's Grid.Row to the same row as the toolbar. After setting my Margins to pull the ComboBox over slightly as not to interfere with looks, it operates as needed with no bugs. Since the only other way I found to do this was setting a DockPanel/Grid's Width property dynamically, I actually feel like this is the cleaner more efficient way to do it.

I realize this is an old topic, but it still pops up when asking the question. This is how I handle this question:
<Grid>
<Grid.RowDefinitions>
<RowDefinition x:Name="MenuRow" Height="25"/>
<RowDefinition x:Name="ToolbarRow" Height="25"/>
<RowDefinition x:Name="CatalogRow" Height="*"/>
<RowDefinition x:Name="RecipeRow" Height="0.4*"/>
</Grid.RowDefinitions>
<ToolBar Grid.Row="1">
<Button x:Name="tbFileOpen" Margin="0,0,0,0" Click="MenuItem_Click"><Image Source="Icons/Main/File/Load.png"/></Button>
<Button x:Name="tbFileSave" Margin="0,0,0,0" Click="MenuItem_Click"><Image Source="Icons/Main/File/Save.png"/></Button>
<Button x:Name="tbFileClear" Margin="0,0,0,0" Click="MenuItem_Click"><Image Source="Icons/Main/File/New document.png"/></Button>
</ToolBar>
<ToolBar Grid.Row="1" HorizontalAlignment="Right">
<Button x:Name="tbFileExit" Margin="0,0,0,0" Click="MenuItem_Click"><Image Source="Icons/Main/File/Exit.png"/></Button>
</ToolBar>
</Grid>
Effectively: I create two toolbar objects and have them on the same Grid.row. The first one has default (left) alignment, the second one is right aligned. It seems to do the trick for me.

For anyone else looking for a solution, the following worked for me:
<ToolBar HorizontalAlignment="Stretch" ToolBarTray.IsLocked="True">
<ToolBar.Resources>
<Style TargetType="{x:Type DockPanel}">
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
</ToolBar.Resources>
I'm using .NET 4.6 and VS2015, but I believe this would work in previous versions too.

Have you tried using a DockPanel that fills the toolbar, then you can dock the ComboBox to the right.
Remember that with a dockpanel the sequence you put the items in is very important.
HTH

<ToolBar Width="100" VerticalAlignment="Top" >
<ToolBar.Resources>
<Style TargetType="{x:Type ToolBarPanel}">
<Setter Property="Orientation" Value="Vertical"/>
</Style>
</ToolBar.Resources>
<DockPanel>
<ToolBarPanel Orientation="Horizontal" >
<Button>A</Button>
<Button>B</Button>
</ToolBarPanel>
<Button DockPanel.Dock="Right" HorizontalAlignment="Right">C</Button>
</DockPanel>
</ToolBar>

My solution to this was to create a label control with a "spring" like ability, so that it would fill the empty void with between the buttons on the toolbar, thus "right aligning" the toolbar's combobox (or any other control that needs "right-aligned).
To do this, I created a WidthConverter, that would take the Actual Width of the ToolBar Control, and then subtract the the space needed needed to right align the combobox.:
public class WidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Math.Max(System.Convert.ToDouble(value) - System.Convert.ToDouble(parameter), 0.0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then, I added a label control to the toolbar, placed to the left of the combobox you need right aligned. Bind the label's Width to the toolbar's ActualWidth and apply the WidthConverter:
<Label Width="{Binding Converter={StaticResource WidthConverter}, ElementName=toolBar1, Path=ActualWidth, ConverterParameter=50}" />
You will need to adjust the ConverterParameter to your specific needs until you get the desired "right align". A higher number provides more space for the combobox, whereas a lower number provides less space.
Using this solution, the label will automatically resize whenever your toolbar resizes, making it appear that you have right aligned your combobox.
There are two great benefit to this solution compared to adding a grid to the toolbar. The first is that if you need to use buttons on the toolbar, you won't lose the toolbar button styling. The second is that the overflow will work as expected if the toolbar length is reduced through window resizing. Individual buttons will go into the overflow as required. If the buttons are put into a a grid then the grid is put into the overflow taking all buttons with it.
XAML of it in use:
<ToolBarPanel>
<ToolBar Name="toolBar1">
<Button>
<Image Source="save.png"/>
</Button>
<Label Width="{Binding Converter={StaticResource Converters.WidthConverter},
ElementName=toolBar1,
Path=ActualWidth,
ConverterParameter=231}" HorizontalAlignment="Stretch" ToolBar.OverflowMode="Never"/>
<Button>
<Image Source="open.png"/>
</Button>
</ToolBar>
If you desire to always keep the last button on the toolbar, say a help button that you always want visible, add the attribute ToolBar.OverflowMode="Never" to its element.

This is how I did it:
I created a style for the toolbar
<Style x:Key="{x:Type ToolBar}" TargetType="{x:Type ToolBar}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolBar}">
<Grid Background="{StaticResource ToolGridBackground}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Style="{StaticResource LogoImage}"/>
<ToolBarPanel Grid.Column="2" x:Name="PART_ToolBarPanel" IsItemsHost="true" Margin="0,1,2,2" Orientation="Horizontal"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The important part is :
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
And
<ToolBarPanel Grid.Column="2"/>
With this, your buttons will be right aligned

I'm not very satisfied with the "WidthConverter" solution because I got some dynamic elements at end. Further search led me to here, which seems to be working perfect for me. Here is my code sample in case you are interested:
<ToolBar Name="toolBar">
<DockPanel Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ToolBarPanel}}}">
<DockPanel.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"></Style>
</DockPanel.Resources>
<Button x:Name="btnRefresh" ToolTip="Refresh" Click="btnRefresh_Click">
<Image Margin="2 0" Source="/Resources/refresh.ico" Height="16" Width="16"/>
</Button>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Image Margin="2 0" Source="/Resources/Help.ico" Height="16" Width="16"/>
<TextBlock Text="Help" Margin="2 0" VerticalAlignment="Center"/>
</StackPanel>
</DockPanel>
</ToolBar>

Related

How to align buttons horizontally and spread equally with xaml windows phone

I have an ObservableCollection which contains ViewModel which in turns defines my buttons definitions.
I've been at it for hours, reading articles after articles but to no avail. I've tried using a Listbox and this is the closest I've got to. My buttons are getting build horizontally but assuming I'm displaying 3 buttons, I want one displayed on the left, one displayed in the middle and one displayed on the right.
<ListBox Grid.Row="2" ItemsSource="{Binding Links}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<StackPanel Background="Beige" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Grid.Column="{Binding Column}"
Grid.Row="0"
Width="90"
Height="90">
<ContentControl>
<StackPanel Orientation="Vertical">
<Image Source="{Binding Image}" Width="36" Height="36"
Margin="5" Stretch="UniformToFill"
HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Description}"
Foreground="#0F558E"
FontSize="18"
HorizontalAlignment="Center"/>
</StackPanel>
</ContentControl>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
As you can see, I set the column dynamically using a property from my ViewModel but I no longer have a grid as I couldn't get it to work, but ideally I'd like to use a grid so that I can specify in which Column to set the button.
When I use a StackPanel, it works but the buttons are right next to each other rather than being split evenly through the entire width of the screen.
I've done something similar to the above using ItemsControl and using a grid, and I can see each button getting added but they overlap each other in row 0, col 0. If I bind the row to the Column property for testing purposes, it build it correctly but my buttons are displayed on different rows which is not what I want. I want each button to be aligned horizontally.
How can I achieve this? I know I could just hard code 3 buttons and just change their icons and text and handle the relevant code by passing the relevant button as binded parameter, but ideally I'd like to build the buttons dynamically in a grid and position them using the column.
Note that the number of column would be fixed i.e. 3, as I'll never have more than this.
Thanks.
but ideally I'd like to use a grid so that I can specify in which
Column to set the button.
In any Xaml variant, why not just use that Grid, such as shown below, where the Grid is set to consumes all the horizontal space.
Then with the grid's center column to be star sized and to have the rest of the remaining space be consumed after button 1 and button 3, which auto size into their own spaces:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"/>
<Button Grid.Column="1" HorizontalAlignment="Center"/>
<Button Grid.Column="2"/>
</Grid>
If that fails, set button one's HorizontalAlignment to be Left and button three's as Right.
As an aside with the list box, it may not be stretching to the whole horizontal size of the screen. Check out my answer to a WP8 sizing issue:
ListBoxItem HorizontalContentAlignment.
I eventually found the answer to my problem in an article I found on the web.
You can check it out here: Using Grids with ItemsControl in XAML
In short, you need to subclass the itemsControl and you need overwrite the GetContainerForItemOverride method which will take care of copying the "data" of the ItemTemplate to the ContentPresenter. In this instance, the row and column, but for my requirement, it is just the Column, since my row will always be 0.
Here is core part of the code if you don't want to check the full article which resolve the problem of setting controls horizontally in a grid using ItemsControl but note the article takes care of creating rows & columns dynamically as well, which I'm not interested in for my project.
public class GridAwareItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
ContentPresenter container = (ContentPresenter)base.GetContainerForItemOverride();
if (ItemTemplate == null)
{
return container;
}
FrameworkElement content = (FrameworkElement)ItemTemplate.LoadContent();
BindingExpression rowBinding = content.GetBindingExpression(Grid.RowProperty);
BindingExpression columnBinding = content.GetBindingExpression(Grid.ColumnProperty);
if (rowBinding != null)
{
container.SetBinding(Grid.RowProperty, rowBinding.ParentBinding);
}
if (columnBinding != null)
{
container.SetBinding(Grid.ColumnProperty, columnBinding.ParentBinding);
}
return container;
}
}
The final xaml looks like this:
<controls:GridAwareItemsControl ItemsSource="{Binding Links}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Background="Pink">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Grid.Column="{Binding Column}"
Grid.Row="0"
Width="120" Height="120">
<ContentControl>
<StackPanel Orientation="Vertical">
<Image Source="{Binding Image}" Width="36" Height="36" Margin="5"
Stretch="UniformToFill" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Description}" Foreground="#0F558E"
FontSize="18" HorizontalAlignment="Center"/>
</StackPanel>
</ContentControl>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</controls:GridAwareItemsControl>
Once I used the new control, my buttons were correctly placed inside the grid, and therefore were evenly spaced out as the grid took care of that wit the ColumnDefinitions.
If anyone knows how to achieve the same without having to create a new control and overriding the method (in other words, pure XAML), please post it as I'd be very interested to see how this can be done.
Thanks and thank you to Robert Garfoot for sharing this great code!
PS: Note that I've simplified my xaml in order to create a test sample without any style on the buttons, so these are rather large if you try based on this sample.
UPDATE:
Small typo correction but my grid column definition was defined as
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
but as #OmegaMan suggested, to be evenly spread, it should have been defined as
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
I was able to accomplish this with a stackpanel inside of a grid, avoiding columns altogether. If you set the stackepanel's HorizontalAlignment to "center", it will center itself inside the grid and grow as buttons are added, still staying centered inside of the grid. Then it's just a matter of margins to have the buttons equally spaced:
<Grid>
<StackPanel
VerticalAlignment="Stretch"
HorizontalAlignment="Center"
Orientation="Horizontal"
>
<Button HorizontalAlignment="Center" Content="Add" Width="104" Height="32" Margin="24,0"/>
<Button HorizontalAlignment="Center" Content="Edit" Width="104" Height="32" Margin="24,0"/>
<Button HorizontalAlignment="Center" Content="Remove" Width="104" Height="32" Margin="24,0"/>
</StackPanel></Grid>

Listbox content not dynamically re-sizing when Listbox size changes

I've seen quite a few people asking this question and i feel like my question is slightly different. I have a listbox that holds a series of identical custom made user controls. These controls are added to the listbox at runtime. Now i currently have my listbox items resizing themselves properly upon first creation and insertion into the control.
Here is the strange part. If I resize the listbox the controls that have been visible previously are not resized to the new listbox width. In other words if i add 12 controls and the box only shows 4 (the rest are hidden by a scrollbar) then if i resize the box the first 4 controls will still be the original width and if i scroll then the other 8 will be the correct size. Also if i manipulate the list items in any way they resize themselves to the proper width automatically. SEE EDIT2
I've tried attaching to the sizeChanged event and issuing the following on both the listbox and the items but it has had no effect. I think i need to find some way of resetting the layout information for the listbox items but i can't find the command.
item.InvalidateArrange();
item.InvalidateMeasure();
Layers.UpdateLayout();
item.UpdateLayout();
I think this has something to do with the items i'm adding because even if i detach the items from the lisbox and then attach them they remain the wrong width.
Here is my listbox code:
<ListBox x:Name="Layers" VerticalContentAlignment="Top" Margin="0,17,0,0" BorderThickness="0,1,0,0" HorizontalContentAlignment="Stretch" SizeChanged="Layers_SizeChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Here is the code for my items
<UserControl x:Class="ListOverlayItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:Data"
HorizontalContentAlignment="Stretch">
<UserControl.Resources>
<data:Translator x:Key="translatorString" />
</UserControl.Resources>
<Border BorderBrush="Silver" BorderThickness="0,0,0,0" Name="border1" HorizontalAlignment="Stretch">
<Grid x:Name="layout" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="19"/>
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<!-- Column 0 -->
<ScrollBar Name="arrangeIcon" FlowDirection="LeftToRight" Maximum="10" SmallChange="1" Value="5" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"/>
<!-- Column 1 -->
<Slider Name="OverlayOpacity" Height="25" Grid.Column="1" Margin="6,0,0,0" Grid.Row="1" Maximum="1" LargeChange="0.1" ValueChanged="OverlayOpacity_ValueChanged"/>
<TextBlock x:Name="OverlayName" Text="{Binding Path=LocationName}" Foreground="#FFF08D2D" Margin="10,2,0,0" Grid.Column="1" FontSize="12" Height="18" VerticalAlignment="Top" />
<!-- Column 3 -->
<Button Name="SettingsButton" Grid.Column="3" Content="{Binding TRK_OV_Settings, Source={StaticResource translatorString}}" VerticalAlignment="Center" Click="SettingsButton_Click" />
<CheckBox x:Name="OverlayEnabled" FlowDirection="LeftToRight" Grid.Column="2" DataContext="{Binding}" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.RowSpan="2" Checked="OverlayEnabled_Checked" Unchecked="OverlayEnabled_Unchecked" />
<TextBlock Name="percentage" Text="100%" FontSize="9" TextAlignment="Right" Grid.Column="2" Grid.Row="1" VerticalAlignment="Center" Margin="2,6,26,6" MinWidth="30"/>
</Grid>
</Border>
Again it seems like my UserControl is correctly able to scale itself its just not getting the command to do so when the parent container is resized.
EDIT: Whoops, removed the WPF tag because it was incorrectly added. I think i've got it down to slider being the culprit. If i set the slider to a fixed size instead of 'stretch' then the item correctly scales just fine. So the question now is how do i force the slider to resize itself.
EDIT2: I know what is causing the issue but i don't know how to solve it. What is happening is the slider that i have in my user control will NOT resize along with the rest of the control unless i change the value of the slider during the resize. The instant i change its value even a fraction it resizes itself automatically. How can i force it to resize?
I determined that the slider was causing the issue and i tried many ways to force the slider to re-draw when it needed to but i was unsuccessful. My final solution was to remove the slider entirely. Works fine now.

How do I access elements that have been dynamically assigned to a control in the form of an XAML resource?

I have the following resource in my window that declares how a certain kind of TabItem should look like.
<Window.Resources>
<StackPanel x:Key="TabSearchContents" x:Shared="False"
Orientation="Vertical">
<Border
BorderThickness="3"
BorderBrush="Purple">
<TextBlock
Text="SEARCH BOOKS"
FontFamily="Verdana"
FontSize="25"
Foreground="Blue"
HorizontalAlignment="Center" />
</Border>
<StackPanel
Height="30"
Orientation="Horizontal"
Margin="5">
<TextBox
x:Name="txtSearch"
Width="650"
FontFamily="Comic Sans MS"
Foreground="Chocolate" />
<Button
x:Name="btnSearch"
Width="100"
Content="Go!"
Click="BtnSearch_Click" />
</StackPanel>
<Grid x:Name="gridResults">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="450"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="itmsSearch" ItemsSource="{Binding}" Padding="4"
ItemTemplate="{StaticResource SearchResultItemDT}">
</ItemsControl>
</ScrollViewer>
<StackPanel x:Name="stkpnlDetails">
</StackPanel>
</Grid>
</StackPanel>
</Window.Resources>
Then, in my code-behind, I dynamically create a tab and assign to the TabControl that is already present in my window.
void BtnNewTab_Click(object sender, RoutedEventArgs e)
{
TabItem tb = new TabItem();
tb.Content = this.Resources["TabSearchContents"];
tb.DataContext = _bridge.SearchBooksByTitle("e");
tb.Header = "Wuttp yo!";
Button btnGo = ((Button)tb.FindName("btnSearch"));
ItemsControl i = (ItemsControl)tb.FindName("itmsSearch");
btnGo.Resources.Add("ResultList", i);
daTabs.Items.Add(tb);
tb.Focus();
}
I want to access the btnSearch Button that is declared in my XAML resource.
As it is, this code throws an exception since btnGo turns out to be null (as well as i) since it can't find the expected control via FindName().
I read about the RegisterName() method, but it requires a reference to an instance of the required control... which I don't have.
I dont think you should define your button like this, try defining it in a style, creating a button and assigning the button that style, i think you will be able to get what you are going for this way.
myTheme.xaml
<ResourceDictionary
<Style x:Key="btnSearch" TargetType="{x:Type Button}">
<Setter Property="Width" Value="100"/>
<Setter Property="Content" Value="Go!"/>
<Setter Property="Click" Value="btn_Click"/>
</Style>
ResourceDictionary/>
myCode.cs
Button btnGo = new Button;
btnGo.Style = "{DynamicResource btnSearch}";
Hope this helps,
Eamonn

Poor Performance When Dynamically Resizing a WPF TextBlock

I'm currently working out the layout of a WPF Application and seem to have it a bit of a snag in the layout of one of my controls. This control is a dynamically sizing, so it should fit the size of the viewport it's a part of. The problem I'm running into is a very visual problem, so I'll do my best to describe it. Here's what it looks like at the moment:
alt text http://gallery.me.com/theplatz/100006/Capture/web.png?ver=12472534170001
The area underneath each of the "Col N Row X" headers is a TextBlock where text of varying length will be placed. To make the TextBlock actually wrap, I found a solution here on stackoverflow that said to bind the width of the textblock to that of the column. Here's a snippet of the Grid definition along with the definition for the first column:
<!-- Change Detail Contents Grid -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="270" Width="2*" />
<ColumnDefinition MinWidth="160" Width="*" />
<ColumnDefinition MinWidth="160" Width="*" />
<ColumnDefinition MinWidth="160" Width="*" />
</Grid.ColumnDefinitions>
<!--
We bind the width of the textblock to the width of this border to make sure things resize correctly.
It's important that the margin be set to 1 larger than the margin of the textblock or else you'll end
up in an infinate loop
-->
<Border Grid.Column="0" Margin="6" Name="FirstBorder" />
<Border Grid.Column="0" BorderThickness="0,0,1,0" BorderBrush="{DynamicResource ColumnBorderBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<Border Style="{DynamicResource DetailHeadingBorder}">
<TextBlock Text="Col 1 Row 1" Style="{DynamicResource DetailHeadingText}" />
</Border>
<TextBlock Text="{Binding IsReason, ElementName=ChangeDetailRoot}" Style="{DynamicResource DetailText}" Width="{Binding ActualWidth, ElementName=FirstBorder}" />
</StackPanel>
<StackPanel Grid.Row="1">
<Border Style="{DynamicResource DetailHeadingBorder}">
<TextBlock Text="Col 1 Row 2" Style="{DynamicResource DetailHeadingText}" />
</Border>
<TextBlock Text="{Binding WasReason, ElementName=ChangeDetailRoot}" Style="{DynamicResource DetailText}" Width="{Binding ActualWidth, ElementName=FirstBorder}" />
</StackPanel>
</Grid>
</Border>
</Grid>
Everything resizes fine when the window/viewport width is increasing. The problem become apparent when the width is decreased. If you suddenly go from maximized to the original size, all of the columns "dance" back to their specified size. What I mean by this is that you can watch each column reduce in size, as it's proportionally resized back to its smaller size. What I've found is that this is directly caused by
Width="{Binding ActualWidth, ElementName=FirstBorder}"
on each of the TextBlocks. The problem also become noticeably worse the more of these controls are on the screen at one time. But, without that line, the text inside each of the TextBlocks will continue to grow to the right the more text is added instead of wrapping down in the column.
Is there a better way to accomplish what I'm trying to accomplish? Using HTML/CSS, this would be a fairly simple thing to accomplish. I've spent hours Googling and looking through stackoverflow for an answer to this question.
I come from a heavy background of HTML/CSS, so if this isn't something that WPF should be good at, please let me know.
I hate to answer my own question, but it appears that I may have found out what I was doing incorrectly. Since it's been so long since the original question was asked, I cannot remember every step I attempted to take, but this is what I do know. The style on each textblock was set as such:
<Style x:Key="DetailText" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="Margin" Value="5,5,5,5" />
</Style>
At that time, I'm assuming that did not produce the desired results and therefore I had to bind the width of the textblock to that of the column. In playing around today, I changed the style to the following (note the different HorizontalAlignment) and removed the bindings and found out that my problem had been resolved:
<Style x:Key="DetailText" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="Margin" Value="5,5,5,5" />
</Style>
I apologize if you've tried this, but does setting TextBlock.TextWrapping to "Wrap" not accomplish your goal?
I'm guessing that will get rid of the need for the bind-to-width stuff you're doing, as the Grid will take care of the resizing. (That is probably what is happening now: The Grid is laying out the controls as it shrinks, and the binding to width is changing the size slightly, causing the dancing.)
[Update]
I tried to duplicate the behavior you're seeing, but it works fine for me. I made a simple style for the TextBlocks like so:
<Style x:Key="DetailText" TargetType="{x:Type TextBlock}">
<Setter Property="TextBlock.TextWrapping" Value="Wrap"/>
</Style>
And I didn't have any of your other dynamic resources (DetailHeadingBorder, DetailHeadingText, or ColumnBorderBrush), so everything was black and white (fine).
Maybe you just have a really old graphics card and it's rendering things in software? Or it has to do with your styles.
I hope I didn't misinterpret your question, but I don't see the need for binding TextBlock.Width?
This xaml seems to work correctly:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="270"
Width="2*" />
<ColumnDefinition MinWidth="160"
Width="*" />
<ColumnDefinition MinWidth="160"
Width="*" />
<ColumnDefinition MinWidth="160"
Width="*" />
</Grid.ColumnDefinitions>
<!-- We bind the width of the textblock to the width of this border to make sure things resize correctly.
It's important that the margin be set to 1 larger than the margin of the textblock or else you'll end
up in an infinate loop -->
<Border Grid.Column="0"
BorderThickness="0,0,1,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<Border>
<TextBlock Text="Col 1 Row 1" />
</Border>
<TextBlock TextWrapping="Wrap"
Text="gfege dfh lh dfl dhliöslghklj h lglsdg fklghglfg flg lgheh" />
</StackPanel>
<StackPanel Grid.Row="1">
<Border>
<TextBlock Text="Col 1 Row 2" />
</Border>
<TextBlock Text="Massor av text som blir en wrappning i slutändan hoppas jag"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Border>
</Grid>
I just removed the width bindings, added TextWrapping (which you probably had in a style), and removed the border named "FirstBorder" as well.

How do I space out the child elements of a StackPanel?

Given a StackPanel:
<StackPanel>
<TextBox Height="30">Apple</TextBox>
<TextBox Height="80">Banana</TextBox>
<TextBox Height="120">Cherry</TextBox>
</StackPanel>
What's the best way to space out the child elements so that there are equally-sized gaps between them, even though the child elements themselves are of different sizes? Can it be done without setting properties on each of the individual children?
Use Margin or Padding, applied to the scope within the container:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="0,10,0,0"/>
</Style>
</StackPanel.Resources>
<TextBox Text="Apple"/>
<TextBox Text="Banana"/>
<TextBox Text="Cherry"/>
</StackPanel>
EDIT: In case you would want to re-use the margin between two containers, you can convert the margin value to a resource in an outer scope, f.e.
<Window.Resources>
<Thickness x:Key="tbMargin">0,10,0,0</Thickness>
</Window.Resources>
and then refer to this value in the inner scope
<StackPanel.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="{StaticResource tbMargin}"/>
</Style>
</StackPanel.Resources>
Another nice approach can be seen here:
http://blogs.microsoft.co.il/blogs/eladkatz/archive/2011/05/29/what-is-the-easiest-way-to-set-spacing-between-items-in-stackpanel.aspx
Link is broken -> this is webarchive of this link.
It shows how to create an attached behavior, so that syntax like this would work:
<StackPanel local:MarginSetter.Margin="5">
<TextBox Text="hello" />
<Button Content="hello" />
<Button Content="hello" />
</StackPanel>
This is the easiest & fastest way to set Margin to several children of a panel, even if they are not of the same type. (I.e. Buttons, TextBoxes, ComboBoxes, etc.)
I improved on Elad Katz' answer.
Add LastItemMargin property to MarginSetter to specially handle the last item
Add Spacing attached property with Vertical and Horizontal properties that adds spacing between items in vertical and horizontal lists and eliminates any trailing margin at the end of the list
Source code in gist.
Example:
<StackPanel Orientation="Horizontal" foo:Spacing.Horizontal="5">
<Button>Button 1</Button>
<Button>Button 2</Button>
</StackPanel>
<StackPanel Orientation="Vertical" foo:Spacing.Vertical="5">
<Button>Button 1</Button>
<Button>Button 2</Button>
</StackPanel>
<!-- Same as vertical example above -->
<StackPanel Orientation="Vertical" foo:MarginSetter.Margin="0 0 0 5" foo:MarginSetter.LastItemMargin="0">
<Button>Button 1</Button>
<Button>Button 2</Button>
</StackPanel>
The thing you really want to do is wrap all child elements. In this case you should use an items control and not resort to horrible attached properties which you will end up having a million of for every property you wish to style.
<ItemsControl>
<!-- target the wrapper parent of the child with a style -->
<ItemsControl.ItemContainerStyle>
<Style TargetType="Control">
<Setter Property="Margin" Value="0 0 5 0"></Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<!-- use a stack panel as the main container -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- put in your children -->
<ItemsControl.Items>
<Label>Auto Zoom Reset?</Label>
<CheckBox x:Name="AutoResetZoom"/>
<Button x:Name="ProceedButton" Click="ProceedButton_OnClick">Next</Button>
<ComboBox SelectedItem="{Binding LogLevel }" ItemsSource="{Binding LogLevels}" />
</ItemsControl.Items>
</ItemsControl>
+1 for Sergey's answer. And if you want to apply that to all your StackPanels you can do this:
<Style TargetType="{x:Type StackPanel}">
<Style.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="{StaticResource tbMargin}"/>
</Style>
</Style.Resources>
</Style>
But beware: if you define a style like this in your App.xaml (or another dictionary that is merged into the Application.Resources) it can override the default style of the control. For mostly lookless controls like the stackpanel it isn't a problem, but for textboxes etc you may stumble upon this problem, which luckily has some workarounds.
Following up on Sergey's suggestion, you can define and reuse a whole Style (with various property setters, including Margin) instead of just a Thickness object:
<Style x:Key="MyStyle" TargetType="SomeItemType">
<Setter Property="Margin" Value="0,5,0,5" />
...
</Style>
...
<StackPanel>
<StackPanel.Resources>
<Style TargetType="SomeItemType" BasedOn="{StaticResource MyStyle}" />
</StackPanel.Resources>
...
</StackPanel>
Note that the trick here is the use of Style Inheritance for the implicit style, inheriting from the style in some outer (probably merged from external XAML file) resource dictionary.
Sidenote:
At first, I naively tried to use the implicit style to set the Style property of the control to that outer Style resource (say defined with the key "MyStyle"):
<StackPanel>
<StackPanel.Resources>
<Style TargetType="SomeItemType">
<Setter Property="Style" Value={StaticResource MyStyle}" />
</Style>
</StackPanel.Resources>
</StackPanel>
which caused Visual Studio 2010 to shut down immediately with CATASTROPHIC FAILURE error (HRESULT: 0x8000FFFF (E_UNEXPECTED)), as described at https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails-with-catastrophic-failure-when-a-style-tries-to-set-style-property#
Grid.ColumnSpacing, Grid.RowSpacing, StackPanel.Spacing are now on UWP preview, all will allow to better acomplish what is requested here.
These properties are currently only available with the Windows 10 Fall Creators Update Insider SDK, but should make it to the final bits!
The UniformGrid might not be available in Silverlight, but someone has ported it from WPF. http://www.jeff.wilcox.name/2009/01/uniform-grid/
My approach inherits StackPanel.
Usage:
<Controls:ItemSpacer Grid.Row="2" Orientation="Horizontal" Height="30" CellPadding="15,0">
<Label>Test 1</Label>
<Label>Test 2</Label>
<Label>Test 3</Label>
</Controls:ItemSpacer>
All that's needed is the following short class:
using System.Windows;
using System.Windows.Controls;
using System;
namespace Controls
{
public class ItemSpacer : StackPanel
{
public static DependencyProperty CellPaddingProperty = DependencyProperty.Register("CellPadding", typeof(Thickness), typeof(ItemSpacer), new FrameworkPropertyMetadata(default(Thickness), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnCellPaddingChanged));
public Thickness CellPadding
{
get
{
return (Thickness)GetValue(CellPaddingProperty);
}
set
{
SetValue(CellPaddingProperty, value);
}
}
private static void OnCellPaddingChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
{
((ItemSpacer)Object).SetPadding();
}
private void SetPadding()
{
foreach (UIElement Element in Children)
{
(Element as FrameworkElement).Margin = this.CellPadding;
}
}
public ItemSpacer()
{
this.LayoutUpdated += PART_Host_LayoutUpdated;
}
private void PART_Host_LayoutUpdated(object sender, System.EventArgs e)
{
this.SetPadding();
}
}
}
Usually, I use Grid instead of StackPanel like this:
horizontal case
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox Height="30" Grid.Column="0">Apple</TextBox>
<TextBox Height="80" Grid.Column="2">Banana</TextBox>
<TextBox Height="120" Grid.Column="4">Cherry</TextBox>
</Grid>
vertical case
<Grid>
<Grid.ColumnDefinitions>
<RowDefinition Width="auto"/>
<RowDefinition Width="*"/>
<RowDefinition Width="auto"/>
<RowDefinition Width="*"/>
<RowDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox Height="30" Grid.Row="0">Apple</TextBox>
<TextBox Height="80" Grid.Row="2">Banana</TextBox>
<TextBox Height="120" Grid.Row="4">Cherry</TextBox>
</Grid>
sometimes you need to set Padding, not Margin to make space between items smaller than default

Resources