Styling depending on content - c#

I'm currently trying to write proper style for item displayed as selected in combobox. The reason I'm doing it is, that I have not much control over how ComboBox display the selected item and - for example - on dark background, the item is still displayed black.
I came with the following solution:
<DataTemplate x:Key="MyItem" DataType="ComboBoxItem">
<TextBlock Text="{Binding}" Foreground="White"/>
</DataTemplate>
<!-- (...) -->
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- ... -->
<!-- Displaying currently selected item -->
<ContentPresenter Margin="2" IsHitTestVisible="False"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
Name="ContentSite"
ContentTemplate="{StaticResource MyItem}"
Content="{TemplateBinding ComboBox.SelectionBoxItem}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now when the simple ComboBoxItem is selected, it is correctly displayed in the ComboBox. On the other hand, if I - for example - display a button with some content, in return I get the text System.Windows.Shapes.Rectangle, what is far from what I want to display.
I would like to use different templates for different data types displayed in the ComboBox - I will be able to customize their appearance. How can I achieve that?
Edit:
To be perfectly clear, I'm talking about selected (=chosen) ComboBox item in this context:
(not about the selected ComboBox item on the ComboBox's list)

It seems to me that you are looking for ContentTemplateSelector:
http://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplateselector%28v=vs.110%29.aspx
http://zamjad.wordpress.com/2011/09/21/using-contenttemplateselector/
Take a look at those links.

Right, there are a few different aspects to this question. Firstly, we can get rid of the default selection colours by adding this into the Resources section:
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="White" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
Experiment with these settings as two of them define the colour of the text and two set the colour for the background of the selected item. In fact, you can use whatever colours here to provide a quick way to change the selected item colours... without having to go to all of the trouble that you have gone to. But for your example, let's just leave them as Transparent.
Ok, so next you want to have a different look for each different data type that appears in the ComboBox... this can also be easily achieved. All you need to do is to declare a DataTemplate for each type and do not set the x:Key property so that they will be implicitly applied. Here is a basic example:
<Window.Resources>
<DataTemplate DataType="{x:Type Type1}">
<TextBlock Text="{Binding}" Foreground="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type Type2}">
<TextBlock Text="{Binding}" Foreground="Green" />
</DataTemplate>
<DataTemplate DataType="{x:Type Type3}">
<TextBlock Text="{Binding}" Foreground="Blue" />
</DataTemplate>
</Window.Resources>
If you populated a ComboBox with items of these three types with these DataTemplates, then the items would be coloured in Red, GreenandBlue`.

I'll leave the solution with ContentTemplateSelector for all, who will seek solution for the same problem.
Add the following class to your library/application:
public class ComboBoxSelectionTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate
{
get;
set;
}
public DataTemplate ContentTemplate
{
get;
set;
}
public override System.Windows.DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is string)
return TextTemplate;
else
return ContentTemplate;
}
}
Define two different DataTemplates:
<DataTemplate x:Key="ComboBoxTextItem">
<TextBlock Text="{Binding}" Foreground="{DynamicResource {x:Static vs:VsBrushes.WindowTextKey}}" />
</DataTemplate>
<DataTemplate x:Key="ComboBoxOtherItem">
<ContentPresenter Content="{Binding}" />
</DataTemplate>
Instantiate the selector inside style you want to use it in:
<Style.Resources>
<local:ComboBoxSelectionTemplateSelector ContentTemplate="{StaticResource ComboBoxOtherItem}"
TextTemplate="{StaticResource ComboBoxTextItem}" x:Key="ContentTemplateSelector" />
</Style.Resources>
Use it in the ContentPresenter:
<!-- ... -->
<ControlTemplate TargetType="ComboBox">
<Grid>
<ContentPresenter ContentTemplateSelector="{StaticResource ContentTemplateSelector}"
Content="{TemplateBinding ComboBox.SelectionBoxItem}" />
<!-- Other items -->
<Popup ... /> <!-- For displaying ComboBox's list -->
</Grid>

Related

uwp how to resize items in a templated item source all at once?

I have this listview and I want to dynamically resize the item's properties (fontsize, width and height etc..) upon a window's size change. So far I've been able to do so by modifying item source's elements' layout one by one in c# (with a for loop). As the listview items' count grows, the effect becomes glitchy later on. I wonder if there is a way to uniformly modify the style of these templated items in listview. I've tried using and modifying a staticResource style that is applied to the template but to no avail.
<ListView
x:Name="leftMenubar"
Grid.Column="0"
Height="auto"
VerticalAlignment="Top"
VerticalContentAlignment="Stretch"
IsItemClickEnabled="True"
ItemClick="LeftBarMenuItemClicked"
ItemsSource="{x:Bind Items}"
Loaded="LeftMenubar_Loaded">
<ListView.Resources>
<SolidColorBrush x:Key="ListViewItemBackgroundPointerOver" Color="Transparent" />
<SolidColorBrush x:Key="ListViewItemBackgroundPressed" Color="Transparent" />
<SolidColorBrush x:Key="ListViewItemBackgroundSelectedPressed" Color="Transparent" />
<SolidColorBrush x:Key="ListViewItemBackgroundSelected" Color="Transparent" />
<SolidColorBrush x:Key="ListViewItemBackgroundSelectedPointerOver" Color="Transparent" />
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel
Margin="0"
VerticalAlignment="Top"
GroupPadding="0"
Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:LeftMenuBarItem">
<StackPanel Margin="0,0,0,0" Padding="{x:Bind Pad, Mode=OneWay}">
<BitmapIcon
Height="{x:Bind IconHeight, Mode=OneWay}"
MinHeight="20"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Foreground="{x:Bind Foreground, Mode=OneWay}"
UriSource="{x:Bind ImgPath}" />
<TextBlock
Width="Auto"
Margin="0,5,0,0"
HorizontalAlignment="Center"
FontSize="10"
Foreground="{x:Bind Foreground, Mode=OneWay}"
Text="{x:Bind Title}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can accomplish this a few different ways. You could setup the data template to bind the font size to the height of the control and calculate there. But then you have a converter firing for every item in your list.
I think the best way is to setup one binding for the ListViewItem to the FontSize (and other properties) on the ListView Here is an example of how you can set this up
class AdjustableListView : ListView
{
public AdjustableListView()
{
SizeChanged += OnSizeChanged;
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var listItem = (ListViewItem)element;
var binding = new Binding { Source = this, Path = new PropertyPath("FontSize")};
listItem.SetBinding(ListViewItem.FontSizeProperty, binding);
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
FontSize = Math.Max(12, e.NewSize.Height / 24);
}
}
User Controls allow you to encapsulate common passages of XAML that you intend to use frequently, whether on a single page or across the entire app. However, User Controls are most helpful when you want to change the size of the data template for a GridView or ListView when employing an adaptive layout, I think that this is the best approach to do the task.
This video shows you how.
Bob Tabor explaining how to utilize User Controls as Data Templates

How to get ControlTemplate not to override DataTemplate?

Similar to gmail's Select button, I wanted to create a ComboBox for the ListView which allows the user to quickly select entries of their choosing (ex. All, None, Read, Unread). However, the selected value would display a tri-state CheckBox equivalent to All, Some, or None of the entries being selected. I succeeded in doing so. Below is the xaml for an example Window utilizing this feature(*):
<Window x:Class="WPFTest.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfTest="clr-namespace:WPFTest.ViewModels"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Mvvm;assembly=Microsoft.Practices.Prism.Mvvm.Desktop"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFTest.Models"
mc:Ignorable="d" Title="WPF Test" Height="221.256" Width="605"
d:DataContext="{d:DesignInstance wpfTest:MainWindowViewModel, IsDesignTimeCreatable=True}"
prism:ViewModelLocator.AutoWireViewModel="True" WindowStartupLocation="CenterScreen">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="/WPFTest;Component/Resources/Resources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ListView
ItemsSource="{Binding Entries}"
SelectedValue="{Binding SelectedEntry}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ScrollViewer.CanContentScroll="True"
SelectionMode="Single"
Margin="10">
<ListView.View>
<GridView>
<GridViewColumn Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding Selected}"
Command="{Binding DataContext.RowSelectedCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
<GridViewColumnHeader>
<ComboBox
ItemsSource="{Binding Options.Items}"
SelectedValue="{Binding Options.SelectedItem}"
ItemTemplateSelector="{DynamicResource itemTemplateSelector}"
HorizontalAlignment="Stretch"
Margin="0,0,0,0"
VerticalAlignment="Stretch"
Width="44"
Height="34"
FontSize="20"
VerticalContentAlignment="Top"
HorizontalContentAlignment="Left">
<ComboBox.Resources>
<DataTemplate x:Key="selectedTemplate">
<TextBlock
x:Name="displayText"
Text="{Binding DataContext.Options.SelectedDisplay, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
FontSize="20"
Height="22"/>
</DataTemplate>
<DataTemplate x:Key="dropDownTemplate">
<TextBlock Text="{Binding}" FontSize="12"/>
</DataTemplate>
<local:ComboBoxItemTemplateSelector
x:Key="itemTemplateSelector"
SelectedTemplate="{StaticResource selectedTemplate}"
DropDownTemplate="{StaticResource dropDownTemplate}"/>
</ComboBox.Resources>
</ComboBox>
</GridViewColumnHeader>
</GridViewColumn>
<GridViewColumn
Width="Auto"
DisplayMemberBinding="{Binding Type, Mode=OneWay}"
Header="Type"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
Recently I was asked to make a style for every ComboBox on our screens - changing their backgrounds. Because I'm on Windows8, setting the Background alone isn't enough. Using this tutorial I was able to create the ControlTemplate to get the correct behavior, with one minor error fix:
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="True"/>
<!-- Comment out the following, it throws an error. -->
<!--<Condition>
<Condition.Value>
<sys:Boolean>False</sys:Boolean>
</Condition.Value>
</Condition>-->
</MultiTrigger.Conditions>
And usage:
<Style
TargetType="{x:Type ComboBox}">
...
<Setter
Property="Template"
Value="{StaticResource ComboBoxStyle1}" />
</Style>
This successfully styles the ComboBox Background. However, revisiting the former screen, I noticed that this breaks my gmail-like display.
How can I get this ControlTemplate and the dynamic DataTemplate to cooperate?
(*) ViewModels and Models can be provided if necessary for a solution. Or see full working example.
I'm not sure why you doing so complicated ComboBox - you defined ItemTemplateSelector twice...
Ok - here is my 2 cents: ComboBox is lookless control. It based on ControlTemplate target type = ComboBox. Inside ComboBox ControlTemplate you will find ContentPresenter. Whatever coming into Content of ContentPresenter could be styled with DataTemplate. Generally - when you define DataTemplate it wrap only ContentPresenter or ItemsPresenter for range based controls - not the whole ControlTemplate obviously.
So if you want to change 'Selected' template for ComboBox is ok but all other data should be defined via DataTemplate for this type {x:Type local:SomeType} that will be used by ComboBox.
Also - consider using #galakt suggestion: use Style with TargetType - it easy to read, refactor, find, understand...
Color the Background
As per the OP, following this tutorial, you generate the ControlTemplate.
To do this, you can right-click on the ComboBox element in design mode
in Visual Studio 2012 or 2013 and select the “Edit template” option
and then the “Edit a copy…” option.
Again note the bug fix from the OP. Changing the background can then be done by:
<Border
x:Name="templateRoot"
BorderBrush="#FFACACAC"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True"
Background="{StaticResource MyComboBackgroundBrush}"> <!-- option 1 -->
<!-- <Border.Background> -->
<!-- option 2 -->
<!-- <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF0F0F0" Offset="0"/>
<GradientStop Color="#FFE5E5E5" Offset="1"/>
</LinearGradientBrush>-->
<!-- option 3 -->
<!-- <SolidColorBrush Color="Yellow"/> -->
<!-- </Border.Background>
<Border x:Name="splitBorder" BorderBrush="Transparent" BorderThickness="1" HorizontalAlignment="Right" Margin="0" SnapsToDevicePixels="True" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}">
<Path x:Name="Arrow" Data="F1M0,0L2.667,2.66665 5.3334,0 5.3334,-1.78168 2.6667,0.88501 0,-1.78168 0,0z" Fill="#FF606060" HorizontalAlignment="Center" Margin="0" VerticalAlignment="Center"/>
</Border> -->
</Border>
Interact with the DataTemplate
In the resource file, following the above instructions, you declared:
<ControlTemplate x:Key="ComboBoxControlTemplateBasic" TargetType="{x:Type ComboBox}">
...
<ContentPresenter
x:Name="contentPresenter"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
... />
...
</ControlTemplate>
Create a copy of it with the following change, where selectedTemplate is the key of the DataTemplate you want to cooperate with the ControlTemplate:
<ControlTemplate x:Key="ComboBoxControlTemplateHeader" TargetType="{x:Type ComboBox}">
...
<ContentPresenter
x:Name="contentPresenter"
ContentTemplate="{DynamicResource selectedTemplate}"
... />
...
</ControlTemplate>
Declare the appropriate styles:
<Style
TargetType="{x:Type ComboBox}">
<Setter Property="Template"
Value="{StaticResource ComboBoxControlTemplateBasic}" />
<!-- other generic style setters -->
</Style>
<Style
x:Key="ComboBoxHeader"
x:Name="ComboBoxHeader"
TargetType="{x:Type ComboBox}"
BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="Template"
Value="{StaticResource ComboBoxControlTemplateHeader}" />
<Setter Property="ItemTemplateSelector"
Value="{DynamicResource itemTemplateSelector}"/>
<!-- other specific style setters -->
</Style>
Results
Every ComboBox shares the same look.
The dynamic use of DataTemplate selectedTemplate allows each ListView header gmail-like ComboBox to have the same look with unique binding values.
Take for example the OP ComboBox header plus the following ComboBox with the same item source:
<ComboBox
HorizontalAlignment="Left"
Margin="10,152,0,0"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Options.Items}"
SelectedValue="{Binding Options.SelectedItem}"/>
<GridViewColumnHeader>
<ComboBox
ItemsSource="{Binding Options.Items}"
SelectedValue="{Binding Options.SelectedItem}"
Style="{StaticResource ComboBoxHeader}">
<ComboBox.Resources>
<DataTemplate x:Key="selectedTemplate">
<TextBlock
x:Name="displayText"
Text="{Binding DataContext.Options.SelectedDisplay, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
FontSize="20"
Height="22"/>
</DataTemplate>
<DataTemplate x:Key="dropDownTemplate">
<TextBlock Text="{Binding}" FontSize="12"/>
</DataTemplate>
<local:ComboBoxItemTemplateSelector
x:Key="itemTemplateSelector"
SelectedTemplate="{StaticResource selectedTemplate}"
DropDownTemplate="{StaticResource dropDownTemplate}"/>
</ComboBox.Resources>
</ComboBox>
</GridViewColumnHeader>

WPF Treeview HierarchicalDataTemplate ItemTemplateSelector

I am trying to create a simple 2 level Treeview in WPF (MVVM approach). For my first level I have a standard datatemplate, for my second level I want to use a Template Selector so that I can change the appearance of each item based on one of its properties.
Below is my Treeview xaml
<Treeview ItemsSource={Binding ListA}>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ListB}" ItemTemplateSelector={StaticResource TemplateSelector}>
<Textblock Text={Binding Name}/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
My first level is
<Textblock Text={Binding Name}/>
will just display a name
For my second level the TemplateSelector is returning a datatemplate which is something like
<DataTemplate x:Key="SomeKey">
<StackPanel Orientation="Horizontal">
<ViewBox>
-----
</ViewBox>
<TextBlock Text={Binding Name}/>
</StackPanel>
</DataTemplate>
But all I see for my second level is my second level ViewModel name. I double checked the template selector and it is definitely returning the correct data template but it is just not getting displayed.
Can anyone please point me in the right direction?
Edit -- Added more code as per request
this is my template selector
public class DataFieldsDataTemplateSelector : DataTemplateSelector
{
public DataTemplate AlphaTemplate { get; set; }
public ------
public ------
public DataFieldsDataTemplateSelector()
{
//This is getting the template from my ResourceDictionary
AlphaTemplate = (DataTemplate)dDictionary["alphaTemplate"];
}
public override DataTemplate SelectTemplate(object item,DependencyObject container)
{
//Somecode
return AlphaTemplate;
}
}
my template for AlphaTemplate in my dictionary is
<DataTemplate x:Key="alphaTemplate">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox IsHitTestVisible="False">
<Path Data="M0,0L56.622002,0 56.622002,14.471 35.715,14.471 35.715,64 20.715,64 20.715,14.471 0,14.471z" Stretch="Uniform" Fill="{DynamicResource ButtonForegroundNormal}" VerticalAlignment="Center" Width="15" Height="15" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<TransformGroup.Children>
<RotateTransform Angle="0" />
<ScaleTransform ScaleX="1" ScaleY="1" />
</TransformGroup.Children>
</TransformGroup>
</Path.RenderTransform>
</Path>
</Viewbox>
<textBlock Text="{Binding Name}/>
</Grid>
</DataTemplate>
my class TypeB contains a Name(Text) and DataType(Text) Fields
if the DataType is Alpha I return AlphaTemplate in my templateSelector and so on
I have an action(dragDrop) on the window which adds items to the second level. And I want the template selector should pick up the correct datatemplate for that dropped item based on its DataType
My main ViewModel contains ICollectionView of TypeA Objects and Each TypeA ViewModel contains ICollectionView of TypeB ViewModels.
Let me know if you need anything
I dont know what is wrong with this as this will require to debug the code, but what you wanted to achieve can be done by defining the default DataTemplate for your TypeB and switching the content depending on the binding like this:
<DataTemplate DataType="{x:Type TypeB}">
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<!-- Default template here for your item -->
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding XYZ}" Value="true">
<Setter Property="ContentTemplate">
<Setter.Value>
<!-- Different template for your item -->
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
Thanks

wpf combobox with custom itemtemplate text

I have ComboBox with custom ItemTemplate.
<ComboBox Height="20" Width="200"
SelectedItem="{Binding Path=SelectedDesign}"
ItemsSource="{Binding Path=Designs}" HorizontalAlignment="Left"
ScrollViewer.CanContentScroll="False">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type formdesign:FormDesignContainer}">
<Rectangle Width="200" Height="100">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=ImageThumb}" Stretch="Uniform" />
</Rectangle.Fill>
</Rectangle>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This works well. However WPF tries to draw rectangle as Combobox Text. How can I set "text" for this template. By "text" I mean string or control which represent selected item and write into combobox when item is selected
In other words I'd like to do this:
But now I got this
Try setting SelectionBoxItemTemplate with a TextBlock.
Appears that SelectionBoxItemTemplate is read-only. So another approach is to override ItemContainerStyle.Template. Example
I found this solution by Ray Burns a good approach. You can define two DataTemplate one for Items in the drop down list and the other for the selected item which should be shown in the Combobox. The using a trigger and checking the visual tree it decide which one to use.
<Window.Resources>
<DataTemplate x:Key="NormalItemTemplate" ...>
...
</DataTemplate>
<DataTemplate x:Key="SelectionBoxTemplate" ...>
...
</DataTemplate>
<DataTemplate x:Key="CombinedTemplate">
<ContentPresenter x:Name="Presenter"
Content="{Binding}"
ContentTemplate="{StaticResource NormalItemTemplate}" />
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource FindAncestor,ComboBoxItem,1}}"
Value="{x:Null}">
<Setter TargetName="Presenter" Property="ContentTemplate"
Value="{StaticResource SelectionBoxTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
...
<ComboBox
ItemTemplate="{StaticResource CombinedTemplate}"
ItemsSource="..."/>
Add Textblock to the datatemplate and bind it
or add Contentpersenter on the rectangle
Edit:
it seems like i didn't got what you were tring to accomplish ,

Getting a separator to fill up the remaining space

I'm sure this is dead simple, but I can't seem to figure it out.
I have a ListBox to display items, and these are displayed with a DataTemplate. I now want to group these items, so have added a group based on the manufacturer property. This is done in code behind.
ICollectionView view = CollectionViewSource.GetDefaultView(Items);
PropertyGroupDescription groups = new PropertyGroupDescription("Manufacturer");
view.GroupDescriptions.Add(groups);
I wanted to have each group in an expander, so they can be hidden. I have got this working by looking at GroupTemplates at MSDN This involves, having an expander, textblock and then a seperator to rule off the extra space like in Windows Vista/7 Groups. As Below.
The problem I am having is I cannot get the separator to fill up the remaining space correctly. If I use a MinWidth value, all my expanders have the same width. If I use the {binding ActualWidth, ElementName=MyListBox}, then the separator is too wide, as its as wide as the control that contains it. So it sets the scroll bars to be visible, (see screenshot below). If i leave width blank, then the seperator is not drawn at all.
My gut feeling is the stackpanel should have expanded the seperator to use the remaining space but it didn't. So i tried a DockPanel as in the XamlCode below, yet this also fails. I have a few other problems with getting controls to fill up the remaining space, by using a suitable width so if you can help me resolve this, it would be great.
My current WPF Xaml Markup. You will need to add elements to get this to display something.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="myStackPanel">
<ListBox x:Name="MyListBox">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<DockPanel HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="{Binding ActualWidth, ElementName=MyListBox}"
Margin="10">
<TextBlock DockPanel.Dock="Left" Margin="0" FontSize="14" FontWeight="Bold" Foreground="Black" Text="{Binding Path=Name}"/>
<Separator DockPanel.Dock="Right" Margin="4,0,4,0"></Separator>
</DockPanel>
</Expander.Header>
<ItemsPresenter Margin="5,0,0,0" />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<!-- Data Template Here -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</ScrollViewer>
It is in fact not trivial, the control template of the Expander consists of a ToggleButton as the Header and a ContentPresenter for the content. The problem is that the ToggleButton itself has a special style & template which contains the arrow that has the alignment hard-coded into it, the default one looks something like this:
<Style x:Key="ExpanderDownHeaderStyle"
TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent"
SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ... -->
<!-- The HorizontalAlignment needs to be set to stretch here -->
<ContentPresenter Grid.Column="1"
Margin="4,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
SnapsToDevicePixels="True"
RecognizesAccessKey="True"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!-- ... -->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
To get your style to work you need to modify the default Expander template (get default templates on MSDN - Default WPF Themes link). Not nice but you don't really have much of a choice.

Categories

Resources