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

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

Related

Showing or hiding a control in WPF template based on bindings

I am new to WPF binding/templating. I have some basic questions about a templated TabControl I have as below :
<TabControl x:Name="tcTabs" ItemsSource="{Binding Rooms, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Margin="5" BorderThickness="1" IsSynchronizedWithCurrentItem="True">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="130"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="22"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" BorderThickness="0" ItemsSource="{Binding Messages}" DisplayMemberPath="Raw" />
<ListBox Grid.Row="0" Grid.Column="1" BorderThickness="1,0,0,0" BorderBrush="#FFBBBBBB" ItemsSource="{Binding Users}" DisplayMemberPath="Nick" />
<TextBox Grid.Row="1" Grid.ColumnSpan="2" BorderThickness="0,1,0,0" BorderBrush="#FFBBBBBB" Height="22" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
The TabControl contains in each tab 2 list boxes and a textbox. One of the listboxes contains user names is is not necessary all the time.
There are 3 kinds of tabs, Server tabs, room tabs and private tabs. In private and server tabs the user list should not exist or be hidden.
I have an enum on the bound room object :
public enum IRCRoomType
{
Server,
Channel,
Private
}
How do I automatically hide the user list based on the enum, I have seen samples of 2 approaches, the binding on visibility with a converter or a trigger. Which is the better approach and are there any more?
When there are no tabs, and the first tab is created it is not automatically selected, how do I select it?
Is there a way of impacting the item styles inside the listboxes depending on tab type? How would I acheive this?
I am just looking for links/hints and not for actual solutions, but if you can give code then that would be a bonus!
It depends on how complicated code. If it's simple I rather use Trigger (you have everything which belows to UI in XAML), but if code is much more complicated consider using Converters (It's actually simpler to use it)
Bind to SelectedIndex of List and set it to 0?
Yes, of course, you can use ContentControl with DataTemplate (Or just DataTemplate in some cases) Some code where I use it:
<ListBox>
<ListBox.Resources>
<DataTemplate DataType="{x:Type your_namespace:your_type}">
... your code ...
</DataTemplate>
<DataTemplate DataType="{x:Type system:String}">
... your code ...
</DataTemplate>
</ListBox.Resources>
</ListBox>
Code you posted is actually a new Template, but you've changed the Style. Please consider override some Template.
Best regards

Designing a custom edit control similar to hotmail style "To" box

I am desiging a custom control for use in my application which simulates a hotmail style "To" textbox which lets the user enter semicolon delimited strings. The control behaves like a textbox, on each ener press or entering semicolon a box(a textblock infact) gets created containing text entered which can be manipulated individually.
The control is implemented by means of a listview with its ItemPresenter set to a WrapPanel
The XAML looks like below:
<ListView x:Name="col" ItemContainerStyle="{StaticResource ContainerStyle}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<local:MyWrapPanel Orientation="Horizontal" MinWidth="400"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<Style TargetType="{x:Type ListViewItem}" x:Key="ContainerStyle">
<Setter Property="ContentTemplate" Value="{StaticResource BoxView}" />
<Style.Triggers>
<Trigger Property="Tag" Value="Edit">
<Setter Property="ContentTemplate" Value="{StaticResource BoxViewEdit}" />
</Trigger>
</Style.Triggers>
</Style>
The control template for the listviewitems (boxes) looks like:
<DataTemplate x:Key="BoxView">
<Border BorderThickness="1" BorderBrush="Brown" Background="Beige" Margin="1,1,1,0" CornerRadius="6" >
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Margin="5,5,5,0" Height="20" Text="{Binding XPath=''}"/>
<DockPanel Grid.Column="1" Grid.Row="0">
<Image Source="edit8.png" MouseLeftButtonUp="edit_MouseLeftButtonUp" ToolTip="Edit" Margin=" 10,0,0,0"></Image>
<Image Source="cancel8.png" MouseLeftButtonUp ="cancel_MouseLeftButtonUp" ToolTip="Remove" Margin=" 5,0,5,0"></Image>
</DockPanel>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
Actually the listview is data bound to a set of xmlnodes having common parent.
The look and feel of the control has to be given such that it looks like a text box.
Now, I have the listview items bound to the xml nodes of an xml document, to show the textbox I add an empty xmlnode in the document and change the control template of the coressponding listviewitem so that it shows as a textbox.
On pressing enter in the text box a new xml node gets appended to the underlying xml at second last position containing innertext set to the textbox text(last positon is dummy node)
The template used for textbox is:
<DataTemplate x:Key="BoxViewEdit">
<TextBox Margin="0,5,5,0" Background="White" MaxWidth="400" BorderThickness="1" Text="{Binding XPath=''}"/>
</DataTemplate>
But the idea of adding an empty dummy xmlnode in the underlying source xml so that i could simulate editing of the listview seems hacky. Is there any cleaner way of doing this.
What i want is to wrap the text box as shown in figure with the contents of the wrappanel seamlessly without adding it to the wrappanel itself by means of creating a dummy xmlnode.
I had the same issue and found some solutions; you can check out the responses and comments here.

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

WPF Toolbar Items HorizontalAligment="Right"

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>

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.

Resources