Why doesn't this datatemplate work at designtime? - wpf

The following simple datatemplate only works at runtime. At designtime it displays nothing. Why is this so?
<DataTemplate x:Key="SomeEnumDataTemplate">
<ListBox Name="list" Width="20" IsSynchronizedWithCurrentItem="True" SelectedIndex="{Binding Mode=OneWay, Converter={StaticResource EnumToIntConverter}}">
<ListBox.Template>
<ControlTemplate TargetType="ListBox">
<ContentPresenter Content="{TemplateBinding SelectedItem}" />
</ControlTemplate>
</ListBox.Template>
<Rectangle Height="10" Width="10" Fill="Red" />
<Rectangle Height="10" Width="10" Fill="Green" />
<Rectangle Height="10" Width="10" Fill="Yellow" />
</ListBox>
</DataTemplate>
I use it like this in another DataTemplate:
<HierarchicalDataTemplate x:Key="NodeDataTemplate" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" ToolTip="{Binding Description}">
<ContentControl ContentTemplate="{StaticResource SomeEnumDataTemplate}" Content="{Binding Mode}" Margin="3,0,0,0" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
Which again is used in a UserControl that has design-time data:
<UserControl x:Class="MyProject.Views.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ViewModels="clr-namespace:MyProject.ViewModels" mc:Ignorable="d"
d:DesignHeight="780" d:DesignWidth="400" d:DataContext="{x:Static ViewModels:SampleData.RootNode}">
<TreeView ItemsSource="{Binding}" ItemTemplate="{StaticResource NodeDataTemplate}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</UserControl>

You can easily create design time data:
Create your DataModel:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public class PersonCollection : List<Person>
{
public PersonCollection()
{
}
}
Create a file with .xaml extension containing:
DesignTimeTreeData.xaml
<local:PersonCollection xmlns:local="clr-namespace:Test_TreeWithDesignData">
<local:Person Name="Joan Solo" Age="32" />
<local:Person Name="Amara Skywalker" Age="31" />
</local:PersonCollection>
Use the d:DataContext and d:DesignData to use the data you specified in the DesignTimeTreeData.xaml:
MainWindow.xaml
<Window x:Class="Test_TreeWithDesignData.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test_TreeWithDesignData"
Title="MainWindow"
Height="350"
Width="525"
mc:Ignorable="d">
<Grid>
<TreeView
d:DataContext="{d:DesignData Source=./DesignTimeTreeData.xaml}"
ItemsSource="{Binding}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type local:Person}" >
<StackPanel Orientation="Horizontal" Height="25">
<Label Content="{Binding Name}"/>
<Label Content="{Binding Age}" Margin="3,0,0,0"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
Since tipically the Window.DataContext property is set to a ViewModel and the TreeView.DataContext is a collection of it, in order yo keep both datasources working, you can surround the TreeView with a Grid which DataContext is set to the ViewModel's collection.
DummyViewModel.cs
public class DummyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public PersonCollection Persons { get; set; }
public DummyViewModel()
{
this.Persons = new PersonCollection();
}
}
MainWindow.xaml
<Window x:Class="Test_TreeWithDesignData.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test_TreeWithDesignData"
Title="MainWindow"
Height="350"
Width="525"
mc:Ignorable="d">
<Window.DataContext>
<local:DummyViewModel />
</Window.DataContext>
<Grid Name="RootGrid">
<Grid Name="TreeGrid" DataContext="{Binding Persons}">
<TreeView d:DataContext="{d:DesignData Source=./DesignTimeTreeData.xaml}"
ItemsSource="{Binding}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type local:Person}" >
<StackPanel Orientation="Horizontal" Height="25">
<Label Content="{Binding Name}"/>
<Label Content="{Binding Age}" Margin="3,0,0,0"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Grid>
</Window>
Result:
Edit
The next question is going to be: Ho do I expand the items in the Designer?
The Person is going to have a collection of persons:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public PersonCollection Childs { get; set; }
}
The design time data is going to have a child
DesignTimeTreeData.xaml
<local:PersonCollection xmlns:local="clr-namespace:Test_TreeWithDesignData">
<local:Person Name="Joan Solo" Age="32" />
<local:Person Name="Amara Skywalker" Age="31">
<local:Person.Childs>
<local:PersonCollection>
<local:Person Name="Han Skywalker" Age="10" />
</local:PersonCollection>
</local:Person.Childs>
</local:Person>
</local:PersonCollection>
The tree is going to have a HierarchicalDataTemplate now:
<HierarchicalDataTemplate
DataType="{x:Type local:Person}"
ItemsSource="{Binding Childs}">
<StackPanel Orientation="Horizontal" Height="25">
<Label Content="{Binding Name}"/>
<Label Content="{Binding Age}" Margin="3,0,0,0"/>
</StackPanel>
</HierarchicalDataTemplate>
And the TreeView will bind to the DesignerProperties.IsInDesignMode to expand the items in the designer:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource Self}, Path=(pf:DesignerProperties.IsInDesignMode)}"
Value="true"
>
<Setter Property="IsExpanded" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
This is the xaml for the window:
MainWindow.xaml
<Window x:Class="Test_TreeWithDesignData.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test_TreeWithDesignData"
xmlns:pf="clr-namespace:System.ComponentModel;assembly=PresentationFramework"
Title="MainWindow"
Height="250"
Width="325"
mc:Ignorable="d"
>
<Window.DataContext>
<local:DummyViewModel />
</Window.DataContext>
<Grid Name="RootGrid">
<Grid Name="TreeGrid" DataContext="{Binding Persons}">
<TreeView
d:DataContext="{d:DesignData Source=./DesignTimeTreeData.xaml}"
ItemsSource="{Binding}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource Self}, Path=(pf:DesignerProperties.IsInDesignMode)}"
Value="true"
>
<Setter Property="IsExpanded" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:Person}"
ItemsSource="{Binding Childs}"
>
<StackPanel Orientation="Horizontal" Height="25">
<Label Content="{Binding Name}"/>
<Label Content="{Binding Age}" Margin="3,0,0,0"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Grid>
</Window>
And this is the result:

Once you start Binding, setting DataContext, ItemSource etc, the designer will crap out on you. just remove all your bindings (looks like 3) and your designer will work. Get it all lined up, or whatever you need to do, then add bindings back.

Related

WPF Can't Remove Red Border from UserControl

I've built a simple login page and when invalid input is detected, a red border is drawn around the usercontrol.
Here is my layout code:
<UserControl x:Class="WPFTest.UI.Views.EmptyLayout"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFTest.UI.Views"
xmlns:vm="clr-namespace:WPFTest.UI.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=vm:EmptyLayoutViewModel}"
Background="White"
>
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="20" FontWeight="Bold">Test Sales Estimator</TextBlock>
<ContentControl Content="{Binding Layout}" Grid.Row="1">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:LoginViewModel}">
<local:LoginView/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</UserControl>
And here is the login view:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFTest.UI.Views" xmlns:viewmodels="clr-namespace:WPFTest.UI.ViewModels"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" x:Class="WPFTest.UI.Views.LoginView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Background="White"
d:DataContext="{d:DesignInstance Type={x:Type viewmodels:LoginViewModel}}"
Margin="20 0 0 0"
Padding="10"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="2" Margin="0 50 0 0">
<TextBlock Text="Welcome" FontSize="17" Grid.Row="1" Margin="0 0 0 20" Height="50"/>
<StackPanel>
<TextBlock Text="Username" />
<TextBox Text="{Binding Username}"/>
</StackPanel>
<StackPanel Grid.Row="2" Margin="0 10 0 0">
<TextBlock Text="Password" />
<PasswordBox x:Name="Password" PasswordChanged="PasswordBox_PasswordChanged" >
</PasswordBox>
</StackPanel>
<Button
Grid.Row="2"
Margin="0 20 0 0"
Padding="5 2"
HorizontalAlignment="Left"
Command="{Binding HandleLoginCommand}"
IsEnabled="{Binding CanLogin}"
Content="Login">
</Button>
</StackPanel>
</Grid>
</UserControl>
I have tried to override the border with the following template:
<Style TargetType="views:LoginView">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="BorderBrush" Value="Blue"/>
<Setter Property="BorderThickness" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
But the border remains red. I've also tried changing the target on the template to things like UserControl, and ContentControl. I've also tried setting the Validation.ErrorTemplate attribute to {x:Null} on both the UserControl and on the element inside the layout usercontrol.
For the LoginViewModel, I am using the CommunityToolkit.Mvvm's ObservableValidator as my base class, so it handles the validation logic and here is my Username property.
private string _username;
[Required]
[MinLength(4)]
public string Username
{
get { return _username; }
set {
SetProperty(ref _username, value, true);
OnPropertyChanged(nameof(HandleLoginCommand));
}
}
The border is from your contentcontrol.
Set Validation.ErrorTemplate="{x:Null}" on that.
You can just use a contentpresenter.
<ContentPresenter Content="{Binding Layout}" Grid.Row="1"
Validation.ErrorTemplate="{x:Null}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginView/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
I have a simplified layout to explore this, but the outer red border does not appear when I make that change.
The validation decoration does not use the control's Border(Thcikness/Brush). It is instead a separate Visual that is drawn in a separate AdornerLayer. To modify its looks, you should pass a dedicated template for the ErrorTemplate like this: (example for TextBox)
<Style TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<Border BorderBrush="Yellow" BorderThickness="3">
<AdornedElementPlaceholder x:Name="AdornedElementPlaceholder" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
To actually remove the border, try setting Validation.ErrorTemplate to {x:Null}.

DataGrid Grouping in XAML with Data from SQL Table

I have an SQL table names Cities that have four fields:
City
Province
EnName
Code
I want to display its data on WPF DataGrid and set grouping by Province on it
This is my XAML design
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:SLTADataSet x:Key="SLTADataSet"/>
<CollectionViewSource x:Key="CountriesViewSource" Source="{Binding Countries, Source={StaticResource SLTADataSet}}"/>
<CollectionViewSource x:Key="CitiesViewSource" Source="{Binding Cities, Source={StaticResource SLTADataSet}}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Province"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid >
<DataGrid x:Name="CitiesDataGrid" ItemsSource="{Binding Source={StaticResource CitiesViewSource}}" CanUserAddRows="False" >
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="false" Background="Gray" >
<Expander.Header>
<TextBlock FontWeight="Bold" Text="{Binding Source={StaticResource CitiesViewSource }, Path=Province}" Background="Yellow" />
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<!-- Style for groups under the top level. -->
<!--<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<DockPanel Background="yellow">
--><!--<TextBlock Text="{Binding Source={StaticResource CitiesViewSource}, Path=Province}" Foreground="Black" Margin="30,0,0,0" Width="100"/>--><!--
<TextBlock Text="SSSSSSSSSSSSSS" Foreground="Black" Margin="30,0,0,0" Width="100"/>
</DockPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>-->
</DataGrid.GroupStyle>
</DataGrid>
</Grid>
</Window>
here is the behind code
Class MainWindow
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs) Handles MyBase.Loaded
Dim SLTADataSet As WpfApp1.SLTADataSet = CType(Me.FindResource("SLTADataSet"), WpfApp1.SLTADataSet)
Dim SLTADataSetCitiesTableAdapter As WpfApp1.SLTADataSetTableAdapters.CitiesTableAdapter = New WpfApp1.SLTADataSetTableAdapters.CitiesTableAdapter()
Dim CitiesViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CitiesViewSource"), System.Windows.Data.CollectionViewSource)
SLTADataSetCitiesTableAdapter.Fill(SLTADataSet.Cities)
CitiesViewSource.View.MoveCurrentToFirst()
End Sub
Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
End Sub
End Class
Grouping is done and working perfect
but I have a problem with expander in Datagrid grouping style
it always the name of first province in table for all the group item I cannot find what the problem is.
Would you please help me on it.
Since you have grouped by the Province property, you could just bind to the Name property of the GroupItem:
<TextBlock FontWeight="Bold" Text="{Binding Name}" Background="Yellow" />

UserControl default value for dependency property of type DataTemplate

I have a simple EntriesViewModel that stores a list of items as well as supporting Add/Remove commands. (Add/Remove commanding left out)
public class EntriesViewModel
{
public ObservableCollection<string> Entries { get; private set; }
public EntriesViewModel()
{
Entries = new ObservableCollection<string>() { "One", "Two", "Three" };
}
}
To view this I have created a simple EntriesEditor usercontrol that uses a listbox to display the entries and buttons to Add/Remove (Add/Remove commanding left out)
public partial class EntriesEditor : UserControl
{
public EntriesEditor()
{
InitializeComponent();
}
}
<UserControl x:Class="UserControlDefaults.EntriesEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:UserControlDefaults"
VerticalAlignment="Top">
<DockPanel Height="200">
<UniformGrid Columns="2"
DockPanel.Dock="Bottom">
<Button Content="Add" />
<Button Content="Remove" />
</UniformGrid>
<ListBox ItemsSource="{Binding Entries}"
ItemTemplate="{Binding EntryTemplate, RelativeSource={RelativeSource AncestorType={x:Type l:EntriesEditor}}}"/>
</DockPanel>
And is used as so :
<l:EntriesEditor DataContext="{Binding EntriesViewModel}"/>
I now want the EntriesEditor to support the ability to define a custom ItemTemplate. So I add an EntryTemplate property.
public partial class EntriesEditor : UserControl
{
public DataTemplate EntryTemplate
{
get { return (DataTemplate)GetValue(EntryTemplateProperty); }
set { SetValue(EntryTemplateProperty, value); }
}
public static readonly DependencyProperty EntryTemplateProperty =
DependencyProperty.Register("EntryTemplate", typeof(DataTemplate), typeof(EntriesEditor), new PropertyMetadata(null));
public EntriesEditor()
{
InitializeComponent();
}
}
<UserControl x:Class="UserControlDefaults.EntriesEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:UserControlDefaults"
VerticalAlignment="Top">
<DockPanel Height="200">
<UniformGrid Columns="2"
DockPanel.Dock="Bottom">
<Button Content="Add" />
<Button Content="Remove" />
</UniformGrid>
<ListBox ItemsSource="{Binding Entries}"
ItemTemplate="{Binding EntryTemplate, RelativeSource={RelativeSource AncestorType={x:Type l:EntriesEditor}}}"/>
</DockPanel>
Which is used as so:
<l:EntriesEditor DataContext="{Binding EntriesViewModel}">
<l:EntriesEditor.EntryTemplate>
<DataTemplate>
<Border BorderThickness="1"
BorderBrush="Green"
Padding="5">
<ContentPresenter Content="{Binding}"/>
</Border>
</DataTemplate>
</l:EntriesEditor.EntryTemplate>
</l:EntriesEditor>
But now I want to define a default EntryTemplate within the EntriesEditor control, how would I do this?
The only way I can think of, is like this :
<UserControl x:Class="UserControlDefaults.EntriesEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:UserControlDefaults"
VerticalAlignment="Top">
<UserControl.Style>
<Style TargetType="{x:Type l:EntriesEditor}">
<Setter Property="EntryTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderThickness="2"
BorderBrush="Red">
<ContentPresenter Content="{Binding}" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Style>
<DockPanel Height="200">
<UniformGrid Columns="2"
DockPanel.Dock="Bottom">
<Button Content="Add" />
<Button Content="Remove" />
</UniformGrid>
<ListBox ItemsSource="{Binding Entries}"
ItemTemplate="{Binding EntryTemplate, RelativeSource={RelativeSource AncestorType={x:Type l:EntriesEditor}}}"/>
</DockPanel>
But that doesn't seem right.

accordion contenttemplate communication with viewmodel

In a silverlight 5 mvvm project I have the following code:
View:
<navigation:Page x:Class="LobDemo.View.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}" >
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<toolkit:DockPanel Grid.Column="0">
<toolkit:Accordion Name="accordion1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Path=MenuItems}"
Margin="5,5,5,5">
<toolkit:Accordion.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</toolkit:Accordion.ItemTemplate>
<toolkit:Accordion.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Path=SubMenuItems}"
Margin="2 2 0 0"
BorderThickness="0"
SelectedItem="{Binding Path=SelectedMenuItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</toolkit:Accordion.ContentTemplate>
</toolkit:Accordion>
</toolkit:DockPanel>
</Grid>
ViewModel: (I'm only showing the required properties)
public ObservableCollection<MenuItem> MenuItems
{
get { return _menuItems; }
set
{
_menuItems = value;
RaisePropertyChanged("MenuItems");
}
}
public object SelectedMenuItem
{
get { return _selectedMenuItem; }
set
{
_selectedMenuItem = value;
RaisePropertyChanged("SelectedMenuItem");
}
}
MenuItem:
public string Name { get; set; }
public ObservableCollection<SubMenuItem> SubMenuItems { get; set; }
SubMenuItem:
public string Name { get; set; }
The code is working fine, my MenuItems are visible in the accordion control, the SubMenuItems are also loaded in the listbox. The problem comes when I select one of the items in the listbox, I want the selected item reported back to my ViewModel as SelectedMenuItem. But the property SelectedMenuItem is never filled, so I'm guessing the code cannot resolve the location of the property.
Can somebody point out what I'm doing wrong?
I found the solution for my problem, I've updated the Accordion.ContentTemplate code in the view, the code now looks like this:
<toolkit:Accordion.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Path=SubMenuItems}"
Margin="2 2 0 0"
BorderThickness="0"
SelectedItem="{Binding RelativeSource={RelativeSource AncestorType=navigation:Page}, Path=DataContext.SelectedMenuItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
With this code, the view now finds the SelectedMenuItem property in the ViewModel

WPF - Binding to current item from within group header style

I'm something of a WPF noob so please take it easy on me ;-)
I am trying to create a grouped DataGrid (WPF toolkit version).
I have successfully created the data source, the DataGrid itself, the required CollectionViewSource and the Style for the group header (which uses an expander).
I want to group by a property called 'Assign_To' and have the relevant value (the value that the grouped items share) show up in the header. However, I cannot work out how to bind to the current group/item in order to return its Assign_To property.
The closest I have got (shown below) is binding to the overall CollectionViewSource, which returns a fixed value for Assign_To. What would be the proper way to bind to the current item/group in order to return the correct value for 'Assign_To'?
Hope someone can help. Thanks!
Andy T.
Here's the source...
<Window DataContext="{Binding Source={StaticResource SampleDataSource}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="DataGridTest.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480" mc:Ignorable="d">
<Window.Resources>
<CollectionViewSource x:Key="CVS" Source="{Binding MyData}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Assign_To"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Source={StaticResource CVS}, Path=Assign_To}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<dg:DataGrid
ItemsSource="{Binding Source={StaticResource CVS}}"
SelectionUnit="CellOrRowHeader"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False">
<dg:DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<dg:DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</dg:DataGrid.GroupStyle>
</dg:DataGrid>
</Grid>
</Window>
Thanks for your reply. I really appreciate it and will check it out to see if it works.
Anyway, as it turns out, after some poking and prodding, I have worked it out using XAML only. What I had been missing was the fact that each item the group header is bound to is a GroupItem and that the default DataContext of a GroupItem is a CollectionViewGroup. In turn, a CollectionViewGroup has an Items property, which is a collection and I can therefore get the Assign_To value of the first item in the collection and use that in my header text. Like this:
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Items[0].Assign_To}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
binding settings depend on the type of the Assign_To property. The simplest settings which could probably work for you would be:
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Name}"/>
pls, check if an example below would work for you; also this link WPF Toolkit DataGrid Part IV: TemplateColumns and Row Grouping might be helpful for you
code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var dataProvider = (CollectionViewSource)FindResource("CVS");
dataProvider.Source = Test.GetTests();
}
}
public class Test
{
public string Assign_To { get; set; }
public string Test0 { get; set; }
public int Test1 { get; set; }
public static List<Test> GetTests()
{
List<Test> tests = new List<Test>();
tests.Add(new Test { Assign_To = "a", Test0 = "aaaa", Test1 = 1 });
tests.Add(new Test { Assign_To = "a", Test0 = "bbbb", Test1 = 1 });
tests.Add(new Test { Assign_To = "b", Test0 = "cccc", Test1 = 2 });
return tests;
}
}
xaml:
<Window.Resources>
<CollectionViewSource x:Key="CVS" >
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Assign_To"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<DataGrid
ItemsSource="{Binding Source={StaticResource CVS}}"
HorizontalScrollBarVisibility="Hidden" SelectionMode="Extended"
AutoGenerateColumns="False"
Name="dataGrid1">
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Test0" Binding="{Binding Path=Test0}" />
<DataGridTextColumn Header="Test1" Binding="{Binding Path=Test1}" />
</DataGrid.Columns>
</DataGrid>
</Grid>

Resources