I have a tab control with 3 objects, 2 lists and a textbox. The text box is bound two way :
<TabControl x:Name="tcTabs" ItemsSource="{Binding Rooms, UpdateSourceTrigger=PropertyChanged}" 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="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="22"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding ReceivedMessages}" DisplayMemberPath="Raw" Grid.Row="0" Grid.Column="0" BorderThickness="0" />
<ListBox ItemsSource="{Binding Users}" DisplayMemberPath="Nick" Visibility="{Binding Type, Converter={StaticResource UserListVisibilityConverter}}" Grid.Row="0" Grid.Column="1" BorderThickness="1,0,0,0" BorderBrush="#FFBBBBBB" Width="130" />
<TextBox Text="{Binding CurrentInput, Mode="TwoWay"}" Grid.Row="1" Grid.ColumnSpan="2" BorderThickness="0,1,0,0" BorderBrush="#FFBBBBBB" Height="22" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
Backing object :
public string CurrentInput
{
get
{
return _currentInput;
}
set
{
if (value != _currentInput)
{
_currentInput = value;
OnPropertyChanged();
}
}
}
Problem is, when I change the text and click another tab it does not update the backing field (does not even hit the setter), however if I change then click the listbox it does...
Any reason for this odd behaviour?
That is not an odd behaviour and has been asked multiple times before. Read about Binding.UpdateSourceTrigger, also see the remarks of the respective property you bind.
I've solve this problem (Twoway Binding) by manual trigger the databinding engine using
DataContext = this;
Related
My WPF application is employing the MVVM pattern, where the views are discovered using WPFs type-based DataTemplate mechanism.
I have one View that contains a ListBox, and the DataTemplate for the ViewModel type in the ListBox is in a Resource on the Grid that contains the ListBox.
In this DataTemplate, I have a custom Button class that has a DependencyProperty that is bound to a string property on the underlying ViewModel. When the Button is clicked, code-behind in the custom Button class gets the value out of the DependencyProperty and passes it in to a modal dialog, gets the edited value back from the dialog and updates the DependencyProperty (which then flows, through binding, back into the ViewModel). This whole process works fine.
However, the very first time this happens, something in the rendering of the dialog causes WPF to do something which results in the items in the ListBox being re-rendered. As a result of this, the actual Button that was clicked is detached from the Visual Tree, and the object being bound to is "disconnected". This only happens the very first time the dialog is evoked during the run of the application. Once it happens, everything is fine until you restart the application.
I'm at a loss. My best guess (based on the call stack for the constructor of my custom Button) is that something is causing WPF to recompile the resources for my application (or, at least, this one View), but I can't imagine what, nor how to prevent it (or force it to happen during startup).
Any suggestions would be awesome. Thanks!
David Mullin
IMA Technologies
Edited: Here's an attempt to show what the code looks like (boiler plate code omitted)
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type ct:RecordDisplayDataSourceAuthoringViewModel}">
<ctConfigControls:SetInfoButton Info="{Binding AutoNewSetInfoXml.Value}" Content="Set Info" />
</DataTemplate>
</Grid.Resources>
<!-- The DataSources property contains a list of RecordDisplayDataSourceAuthoringViewModel -->
<ListBox Grid.Row="1" ItemsSource="{Binding Path=DataSources}" />
</Grid>
public class SetInfoButton : Button
{
public static readonly DependencyProperty InfoProperty = DependencyProperty.Register("Info", typeof(string), typeof(SetInfoButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Info
{
get { return (string)GetValue(InfoProperty); }
set { SetValue(InfoProperty, value); }
}
public SetInfoButton()
{
Click += new RoutedEventHandler(SetInfoButton_Click);
Focusable = false;
}
void SetInfoButton_Click(object sender, RoutedEventArgs e)
{
string info = Info;
if (SetInfoDialog.Show(VisualTreeHelperEx.FindVisualAncestor<Window>(this), ref info) == true)
{
Info = info;
}
}
}
public partial class SetInfoDialog : DialogWindow, IDesignerDataSource
{
public static bool Show(Window parent, ref string info)
{
SetInfoDialog dlg = new SetInfoDialog(info);
dlg.Owner = parent;
if (dlg.ShowDialog() == true)
{
info = dlg.RawInfo;
return true;
}
return false;
}
public SetInfoDialog(string rawInfo)
{
//...
}
}
<ct:DialogWindow x:Class="CaseTrakker.Windows.Dialogs.SetInfoDialog">
<ct:DialogWindow.Buttons>
<ct:ImageButton Image="{DynamicResource {x:Static ct:Common.OkImageKey}}" Content="_Ok" Click="Ok_Click" />
<ct:ImageButton Image="{DynamicResource {x:Static ct:Common.CancelImageKey}}" Content="_Cancel" Click="Cancel_Click" IsCancel="True" />
</ct:DialogWindow.Buttons>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Visibility="{Binding Path=HideObjectSelectorControls, Converter={StaticResource InvertedBoolToVisibilityConverter}}" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0">System Object</Label>
<ct:SystemObjectComboBox Grid.Row="0" Grid.Column="1" CreateOnly="True" SelectedValue="{Binding Path=SystemObjectNumber}" />
<Label Grid.Row="1" Grid.Column="0">Type</Label>
<ctConfigControls:TemplateComboBox Grid.Row="1" Grid.Column="1"
SystemObject="{Binding Path=SystemObjectNumber, Converter={StaticResource IntToStandardSystemObjectConverter}}"
SelectedValue="{Binding Path=TemplateId}" />
</Grid>
<Button Grid.Row="0" Grid.Column="1" Click="Import_Click">Import</Button>
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Vertical" Margin="4,8,4,4"
Visibility="{Binding Path=DisplayText, Converter={StaticResource EmptyToVisibilityConverter}}">
<Label FontWeight="Bold">Notes</Label>
<TextBox Text="{Binding Path=DisplayText, Mode=OneWay}" IsReadOnly="True"/>
</StackPanel>
<ctConfigDesigner:DataDesigner x:Name="Designer" Grid.Column="1" Loaded="DataDesigner_Loaded" />
</Grid>
</Grid>
</ct:DialogWindow>
<Style x:Key="DialogWindowStyle" TargetType="{x:Type ctWindows:DialogWindow}" BasedOn="{StaticResource {x:Type Window}}">
<Setter Property="Background" Value="{DynamicResource {x:Static ctThemes:Common.StandardWindowBackgroundBrushKey}}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.LayoutTransform>
<ScaleTransform x:Name="ZoomFrame"
ScaleX="{Binding Path=ZoomValue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
ScaleY="{Binding Path=ZoomValue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"/>
</Grid.LayoutTransform>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Height="35" Width="35"
Visibility="{Binding Path=HasTitleImage, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}, Converter={StaticResource BoolToVisibilityConverter}}"
Source="{Binding Path=TitleImage, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}" />
<Label Grid.Row="0" Grid.Column="1" Content="{Binding Path=Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
Style="{DynamicResource LabelTitle}" FontWeight="Bold" />
<ContentControl Grid.Row="0" Grid.Column="2"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
Content="{Binding Path=HeaderContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}" />
</Grid>
<ContentControl Grid.Row="1" Grid.Column="0"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
Content="{Binding Path=WindowContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}" />
<ContentControl Grid.Row="2" Grid.Column="0"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
Content="{Binding Path=ButtonPanel, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
I need to conditionally change a TextBlock binding based on the value of an object which is one of many in an OC in my ViewModel. Im sure I need to use DataTriggers to accomplish this. Specifically I want to dynamically change the property that the first TexBlock below binds to for its Text Property. The OC Summary contains a collection of Name objects and each object has a Property called NameType. If NameType = 1 I want to bind to Name1, NameType=2 bind to Name2 etc. The second TextBlock is fine the way it is as it shows an associated value.
<ItemsControl Grid.Row="1" ItemsSource="{Binding Summary}" Margin="0,3,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource TooltipLeftColumnTextStyle}" Text="{Binding Name}" />
<TextBlock Style="{StaticResource TooltipRightColumnTextStyle}" Text="{Binding Value}" />
<Border Grid.Row="1" Style="{StaticResource TooltipSeparatorBorder}" />
<Border Grid.Row="2" Style="{StaticResource TooltipSeparatorAlternateBorder}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Something like this might work for you:
<ItemsControl Grid.Row="1" ItemsSource="{Binding Summary}" Margin="0,3,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="TheTextBlock" Style="{StaticResource TooltipLeftColumnTextStyle}" Text="{Binding Name}" />
<TextBlock Style="{StaticResource TooltipRightColumnTextStyle}" Text="{Binding Value}" />
<Border Grid.Row="1" Style="{StaticResource TooltipSeparatorBorder}" />
<Border Grid.Row="2" Style="{StaticResource TooltipSeparatorAlternateBorder}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding NameType}" Value="1">
<Setter TargetName="TheTextBlock" Property="Text" Value="{Binding Name1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding NameType}" Value="2">
<Setter TargetName="TheTextBlock" Property="Text" Value="{Binding Name2}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Otherwise, the other option is to control the value of the bound Name in the ViewModel so that Name returns the value you want. When NameType changes, you would raise the PropertyChanged event with the parameter "Name" on the view model so that the UI becomes aware of the text change.
What you are looking for is a DataTemplateSelector, this will allow you to swap out the template based on the value of the object (or a property on that object) that is being passed in.
There are lots of better examples on the web than I can give here but this should get you started..
http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector
http://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector.aspx
http://breakingdotnet.blogspot.co.uk/2012/05/data-template-selector-in-xaml.html
We are attempting to implement simple grouping of items within a ComboBox using the ICollectionView. The grouping and sorting being used in the CollectionView works correctly. But the popup items list created by the ComboBox does not function as intended because the scroll bar scrolls the groups not the items.
eg: If there were 2 groups of 25 items, then the scroll bar would have two positions/points to scroll through rather than the desired 50.
Can someone explain why the scroll bar scrolls the groups not the items within the groups, and how we might change this behavior?
Viewmodel setting up the ICollectionView:
public ViewModel()
{
CurrenciesView.Filter = CurrencyFilter;
CurrenciesView.SortDescriptions.Add(new SortDescription("MajorCurrency", ListSortDirection.Descending));
CurrenciesView.SortDescriptions.Add(new SortDescription("Code", ListSortDirection.Ascending));
CurrenciesView.GroupDescriptions.Add(new PropertyGroupDescription("MajorCurrency", new CurrencyGroupConverter()));
public ICollectionView CurrenciesView { get { return CollectionViewSource.GetDefaultView(currencies); } }
private ObservableCollection<Currency> currencies = new ObservableCollection<Currency>();
public ObservableCollection<Currency> Currencies
{
get { return this.currencies; }
set
{
if (this.currencies != value)
{
this.currencies = value;
this.PropertyChanged(this, new PropertyChangedEventArgs("Currencies"));
}
}
}
XAML UserControl hosting the ComboBox
<UserControl x:Class="FilterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ViewModels">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="CurrencyItem">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Code}" FontWeight="ExtraBold"/>
<TextBlock Text="{Binding Description}" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
<Style TargetType="ComboBox">
<Setter Property="MinWidth" Value="100"/>
<Setter Property="Margin" Value="0,5,0,5"/>
</Style>
<DataTemplate x:Key="GroupHeader">
<TextBlock Text="{Binding Name}" Padding="3"/>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0">Currency</Label>
<ComboBox Grid.Column="1" Grid.Row="0" Name="Currency"
ItemsSource="{Binding Path=CurrenciesView, Mode=OneWay}"
ItemTemplate="{StaticResource CurrencyItem}"
SelectedValuePath="Code"
IsSynchronizedWithCurrentItem="True">
<ComboBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupHeader}"/>
</ComboBox.GroupStyle>
</ComboBox>
</Grid>
</UserControl>
Fixed by setting CanContentScroll = false on PART_Popup template for the child ScrollViewer.
<UserControl x:Class="FilterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ViewModels">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="CurrencyItem">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Code}" FontWeight="ExtraBold"/>
<TextBlock Text="{Binding Description}" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
<Style TargetType="ComboBox">
<Setter Property="MinWidth" Value="100"/>
<Setter Property="Margin" Value="0,5,0,5"/>
</Style>
<DataTemplate x:Key="GroupHeader">
<TextBlock Text="{Binding Name}" Padding="3"/>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0">Currency</Label>
<ComboBox Grid.Column="1" Grid.Row="0" Name="Currency"
ItemsSource="{Binding Path=CurrenciesView, Mode=OneWay}"
ItemTemplate="{StaticResource CurrencyItem}"
SelectedValuePath="Code"
IsSynchronizedWithCurrentItem="True"
**ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True"**>
<ComboBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupHeader}"/>
</ComboBox.GroupStyle>
</ComboBox>
</Grid>
</UserControl>
Or in ComboBox derived control:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Popup popup = GetTemplateChild("PART_Popup") as Popup;
if (popup != null)
{
ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(popup.Child);
if (scrollViewer != null)
{
scrollViewer.CanContentScroll = false;
}
}
}
<Combobox ScrollViewer.CanContentScroll="False"></Combobox>
Use above code it will solve problem.
Regards,
Ratikanta
I am having some difficulty figuring out how to template the following TreeView item layout:
I have several items, SearchList, which contains a collection of Search, which contains a collection of DataSet (sort of, but that is beside the point). What I am having difficulty with is styling each node level the way I want. I am using MVVM, and the TreeViews ItemsSource property is set to an ObservableCollection of SearchListViewModels which in turn contain my objects all the way down the object tree.
I can successfully style the SearchList HierarchicalDataTemplate to display them correctly. Where I get hung up is on SearchTerm nodes styling. I want the DataSets to be represented in a wrap panel or uniform grid (I haven't decided yet) to the right of the SearchTerm content area. I have modified a TreeViewItem control template to behave this way I think), however if I set it in the ItemContainerStyle property of the Search HierarchicalDataTemplate, it does nothing. All that gets displayed is the content for the Search.
My Altered TreeViewItem Template
<Style TargetType="{x:Type TreeViewItem}" x:Key="AlteredTreeViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="19" />
<ColumnDefinition Width="0.414*" />
<ColumnDefinition Width="0.586*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Bd" HorizontalAlignment="Stretch"
Grid.Column="1" Grid.ColumnSpan="1" Background="#7F058956">
<ContentPresenter x:Name="PART_Header" Margin="10,0" />
</Border>
<WrapPanel x:Name="ItemsHost"
Grid.Column="2" IsItemsHost="True"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My Search Hierarchical Data Template
<HierarchicalDataTemplate DataType="{x:Type local:SearchViewModel}" ItemsSource="{Binding MySearch.Custodians}" ItemContainerStyle="{StaticResource AlteredTreeViewItem}">
<TextBlock Text="{Binding MySearch.SearchName}" Foreground="Black" FontFamily="Arial" FontSize="16"/>
</HierarchicalDataTemplate>
Surely it is possible to both style differently and have child items laid out differently? How can this be achieved?
It seems that you are pretty close to what you're after. I tried to recreate your scenario based on the code you posted and I noted some problems with it (which of course are based on my interpretation of the code you posted)
You are missing the ContentSource="Header" part of the ContentPresenter
I think you are applying the ItemContainerStyle at the wrong HierarchicalDataTemplate level. It should be specified on the parent in order to affect the children (in your case SearchListViewModel).
The default Template for TreeViewItem lays out the ContentPresenter in an Auto sized ColumnDefinition so the WrapPanel won't succesfully wrap unless you modify the ItemContainerStyle for the parent as well. I changed it to a UniformGrid in my sample below
With the changes from above and a few other things I got a result that looks like this which hopefully is pretty close to what you're after
I uploaded the sample solution here: https://www.dropbox.com/s/4v2t8imikkagueb/TreeViewAltered.zip?dl=0
And here is the Xaml code for it (too much code to post it all..)
<Window.Resources>
<!-- DataSet-->
<HierarchicalDataTemplate DataType="{x:Type data:DataSet}">
<Border BorderThickness="3"
BorderBrush="Gray"
Background="Green">
<TextBlock Text="{Binding Path=Tables[0].TableName}"
Margin="5"/>
</Border>
</HierarchicalDataTemplate>
<!-- SearchViewModel -->
<HierarchicalDataTemplate DataType="{x:Type viewModel:SearchViewModel}"
ItemsSource="{Binding DataSets}">
<TextBlock Text="{Binding DisplayName}"
Foreground="Black"
FontFamily="Arial"
FontSize="16"/>
</HierarchicalDataTemplate>
<!-- SearchListViewModel -->
<HierarchicalDataTemplate DataType="{x:Type viewModel:SearchListViewModel}"
ItemsSource="{Binding SearchList}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="19" />
<ColumnDefinition Width="0.414*" />
<ColumnDefinition Width="0.586*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Bd"
HorizontalAlignment="Stretch"
Grid.Column="1"
Grid.ColumnSpan="1"
Background="#7F058956">
<ContentPresenter x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Border>
<UniformGrid x:Name="ItemsHost"
Grid.Column="2"
Columns="3"
IsItemsHost="True"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<TextBlock Text="{Binding DisplayName}"
FontSize="20"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding SearchListViewModels}" />
</Grid>
Something I learnt a long time ago when trying to create a similar interface was that you are better using a ListBox than a TreeView.
Why?
If you only have one level of expansion (as it appears from your sample) you will a lot more control of the layout as you have a single DataTemplate to style.
It is lot easier to customize a ListBox than a TreeView as you do not have be concerned with the GridViewColumnHeader and GridViewColumnPresenters etc.
To get the expansion part (which is why you initially selected a TreeView), simply use a Grid with two rows defined and an Expander in the second row bound to the IsChecked property of a ToggleButton. See the example that I pulled from my Log Viewer.
<DataTemplate>
<Grid Margin="0,0,0,3" Grid.IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" SharedSizeGroup="SSG_TimeIcon"/>
<ColumnDefinition Width="120" SharedSizeGroup="SSG_Time"/>
<ColumnDefinition Width="30" SharedSizeGroup="SSG_LevelIcon"/>
<ColumnDefinition Width="70" SharedSizeGroup="SSG_Level"/>
<ColumnDefinition Width="*" SharedSizeGroup="SSG_Message"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- ProgramTime -->
<Rectangle Grid.Column="0" Grid.Row="0" Margin="0,0,0,0" Width="16" Height="16" VerticalAlignme="Top" HorizoalAlignme="Stretch" Fill="{StaticResource Icon_Timer}"/>
<TextBlock Grid.Column="1" Grid.Row="0" Margin="5,0,0,0" VerticalAlignme="Top" HorizoalAlignme="Stretch" Text="{Binding Path=TimeStamp, Converter={StaticResource ObjectToStringConverter}}" ToolTip="{Binding Path=ProgramTime}"/>
<!-- Level -->
<Rectangle Grid.Column="2" Grid.Row="0" Margin="10,0,0,0" Width="16" Height="16" VerticalAlignme="Top" HorizoalAlignme="Stretch" Fill="{Binding Path=Level, Converter={StaticResource MappingConverterNinjaLogLevelEnumToBrushResource}}"/>
<TextBlock Grid.Column="3" Grid.Row="0" Margin="5,0,0,0" Text="{Binding Path=LevelFriendlyName}" VerticalAlignme="Top" HorizoalAlignme="Stretch"/>
<!-- Message -->
<StackPanel Grid.Column="4" Grid.Row="0" Margin="10,0,0,0" Orieation="Horizoal" >
<TextBlock Margin="0,0,0,0" Text="{Binding Path=LogMessage}" TextWrapping="Wrap" VerticalAlignme="Top" HorizoalAlignme="Stretch"/>
<ToggleButton x:Name="ExpandExceptiooggleButton" VerticalAlignme="Top" Margin="5,0,0,0" IsChecked="False"
Coe="Show Details" Tag="Hide Details" Style="{StaticResource TextButtonStyle}"
Foreground="{StaticResource BlueBrush}" Background="{StaticResource RedBrush}"
Visibility="{Binding Path=HasException, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
<Expander IsExpanded="{Binding Path=IsChecked, ElemeName=ExpandExceptiooggleButton}" Style="{StaticResource CoeExpanderStyle}"
Margin="10,0,0,0" Grid.Column="4" Grid.Row="1">
<Border BorderBrush="{StaticResource DarkGreyBrush}" BorderThickness="1,0,0,0">
<TextBlock Text="{Binding Path=Exception}" Margin="5,0,0,0"/>
</Border>
</Expander>
</Grid>
</DataTemplate>
Can you see how much easier it is to define a header and expandable body. If you do have a need for nested data, add a Level property your view model (you are using MVVM aren't you?!) and then create a IValueConverter that returns a Margin (i.e. Thickness) to fake the indent.
I'm having this strange issue with my ItemsControl grouping. I have the following setup:
<ItemsControl Margin="3" ItemsSource="{Binding Communications.View}" >
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemCount, StringFormat='{}[{0}] '}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Text="{Binding Name, Converter={StaticResource GroupingFormatter}, StringFormat='{}Subject: {0}'}" FontWeight="Bold" />
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock FontWeight="Bold" Text="{Binding Inspector, Converter={StaticResource NameFormatter}, StringFormat='{}From {0}:'}" Margin="3" />
<TextBlock Text="{Binding SentDate, StringFormat='{}{0:dd/MM/yy}'}" Grid.Row="1" Margin="3"/>
<TextBlock Text="{Binding Message }" Grid.Column="1" Grid.RowSpan="2" Margin="3"/>
<Button Command="vm:CommunicationViewModel.DeleteMessageCommand" CommandParameter="{Binding}" HorizontalAlignment="Right" Grid.Column="2">Delete</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my ViewModel, I expose a CollectionViewSource named 'Communications'. I proceed to adding a grouping patter like so:
Communications.GroupDescriptions.Add(new PropertyGroupDescription("Subject"));
Now, the problem i'm experience is the grouping work fine, but I can't see any items inside the groups. What am I doing wrong? Any pointers would be much appreciated.
I can't seem to reproduce the problem - I assume you are using a CollectionViewSource? It might be because you bound to the View property directly.
Here's the C# code I used:
public class Communication
{
public string Subject { get; set; }
public string Body { get; set; }
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
var source = (CollectionViewSource)Resources["Communications"];
source.Source = new List<Communication>()
{
new Communication { Subject = "WPF 4.0", Body = "I love what's happening with 4.0"},
new Communication { Subject = "WPF 4.0", Body = "I hear the text rendering is the best feature"},
new Communication { Subject = "Blend 3.0", Body = "Behaviors in Blend 3 change everything"}
};
source.GroupDescriptions.Add(new PropertyGroupDescription("Subject"));
}
}
Here is the XAML - it's the same as yours but with a couple of things removed since I don't have your converters or commands:
<Window
x:Class="GroupStyleDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
>
<Window.Resources>
<CollectionViewSource x:Key="Communications" />
</Window.Resources>
<Grid>
<ItemsControl Margin="3" ItemsSource="{Binding Source={StaticResource Communications}}" >
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemCount, StringFormat='{}[{0}] '}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Text="{Binding Path=Name}" FontWeight="Bold" />
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Body }" Grid.Column="1" Grid.RowSpan="2" Margin="3"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
After hitting this problem myself I discovered the cause: the template for ItemsControl directly including a panel with IsItemsHost="true".
You must insert an ItemPresenter into your template and set the ItemsControl.ItemsPanel property instead.