How to setup a grid as template for an Items control? - wpf

I'm trying to create an ItemsControl that uses a grid as its ItemsPanel in such a way that it has two columns, where the first columns width is the width of the widest item in that column, and has as may rows needed to display all the items
Basically, I want the following, but somehow within an ItemsControl so that I can bind to a collection of objects:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Content="{Binding Items[0].Header}"/>
<TextBox Text="{Binding Items[0].Content}" Grid.Column="1"/>
<Label Content="{Binding Items[1].Header}" Grid.Row="1"/>
<TextBox Text="{Binding Items[1].Content}" Grid.Row="1" Grid.Column="1"/>
<Label Content="{Binding Items[2].Header}" Grid.Row="2"/>
<TextBox Text="{Binding Items[2].Content}" Grid.Row="2" Grid.Column="1"/>
</Grid>
Edit : Rachels answer worked great, here is a working example.
(I moved the Grid.IsSharedSizeScope="True" to the ItemsPanel, not sure if Rachel meant to put it in the ItemTemplate (which didn't work))
namespace WpfApplication23
{
public partial class Window1 : Window
{
public List<Item> Items { get; set; }
public Window1()
{
Items = new List<Item>()
{
new Item(){ Header="Item0", Content="someVal" },
new Item(){ Header="Item1", Content="someVal" },
new Item(){ Header="Item267676", Content="someVal" },
new Item(){ Header="a", Content="someVal" },
new Item(){ Header="bbbbbbbbbbbbbbbbbbbbbbbbbb", Content="someVal" },
new Item(){ Header="ccccccc", Content="someVal" }
};
InitializeComponent();
DataContext = this;
}
}
public class Item
{
public string Header { get; set; }
public string Content { get; set; }
}
}
<Window x:Class="WpfApplication23.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Grid.IsSharedSizeScope="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="ColumnOne" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Content="{Binding Header}"/>
<TextBox Text="{Binding Content}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>

There are multiple problems here for an ItemsControl:
Getting your first column to match the width of the largest item
Generating a dynamic number of rows
Generating more than one item for each iteration of the ItemsControl
The last one is really the biggest problem, because an ItemsControl wraps each ItemTemplate in a ContentPresenter, so there is no default way of creating more than one item in the panel for each Iteration of the ItemsControl. Your end result would look like this:
<Grid>
...
<ContentPresenter>
<Label Content="{Binding Items[0].Header}"/>
<TextBox Text="{Binding Items[0].Content}" Grid.Column="1"/>
</ContentPresenter>
<ContentPresenter>
<Label Content="{Binding Items[1].Header}" Grid.Row="1"/>
<TextBox Text="{Binding Items[1].Content}" Grid.Row="1" Grid.Column="1"/>
</ContentPresenter>
<ContentPresenter>
<Label Content="{Binding Items[2].Header}" Grid.Row="2"/>
<TextBox Text="{Binding Items[2].Content}" Grid.Row="2" Grid.Column="1"/>
</ContentPresenter>
</Grid>
My best suggestion would be to create an ItemTemplate that contains a 1x2 Grid, and use Grid.IsSharedSizeScope to make the width of the first column shared. (The ItemsPanelTemplate would remain the default StackPanel.)
This way, the end result would look like this:
<StackPanel>
<ContentPresenter>
<Grid IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="ColumnOne" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Content="{Binding Header}"/>
<TextBox Text="{Binding Content}" Grid.Column="1"/>
</Grid>
</ContentPresenter>
<ContentPresenter>
<Grid IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="ColumnOne" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Content="{Binding Header}"/>
<TextBox Text="{Binding Content}" Grid.Column="1"/>
</Grid>
</ContentPresenter>
...
</StackPanel>

You can use a ListView
<ListView ItemsSource="{Binding MyList}">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn
Header=""
Width="Auto"
DisplayMemberBinding="{Binding Header}"/>
<GridViewColumn
Header=""
DisplayMemberBinding="{Binding Value}"/>
</GridView>
</ListView.View>
</ListView>
the ColumnHeaderContainerStyle hides the GridViewHeader

Related

Two ListBoxes with two (almost similar) templates

I have 2 Listboxes with 2 data templates that are almost identical except that one of them contains TextBox instead of ComboBox.
First Template :
<DataTemplate x:Key="OldPanelsTemplate" DataType="{x:Type VM:CustomPanelBoard}">
<Grid Height="60" Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue"
Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=(ItemsControl.AlternationIndex),
Converter={StaticResource IncrementerConverter}}" />
</Border>
<TextBlock Grid.Column="1" Text="{Binding Name}" />
<TextBlock Grid.Column="2" Text="{Binding DistributionSystemName}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of circuits to be copied: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfCircuitsToBeCopied}" />
</Grid>
</DataTemplate>
Second Template :
<DataTemplate x:Key="NewPanelsTemplate" DataType="{x:Type VM:CustomPanelBoard}">
<Grid Height="60">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue"
Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=(ItemsControl.AlternationIndex),
Converter={StaticResource IncrementerConverter}}" />
</Border>
<TextBlock Grid.Column="1" Text="{Binding Name}" />
<ComboBox Grid.Column="2" ItemsSource="{Binding ValidDistributionSystemsForPanel}"
SelectedItem="{Binding SelectedValidDistributionSystemsForPanel}" HorizontalAlignment="Stretch"
IsHitTestVisible="{Binding DistributionSystemNotAssigned}" IsEnabled="{Binding DistributionSystemNotAssigned}"
ItemTemplate="{StaticResource DistributionSystemTemplate}" >
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of available ways: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfAvailableWays}" />
</Grid>
</DataTemplate>
As you can see they are both almost identical except for this part :
<ComboBox Grid.Column="2" ItemsSource="{Binding ValidDistributionSystemsForPanel}"
SelectedItem="{Binding SelectedValidDistributionSystemsForPanel}" HorizontalAlignment="Stretch"
IsHitTestVisible="{Binding DistributionSystemNotAssigned}" IsEnabled="{Binding DistributionSystemNotAssigned}"
ItemTemplate="{StaticResource DistributionSystemTemplate}" >
</ComboBox>
The problem is whenever i change anything in one of them i have to change the same thing in the other also ... Any way that can merge them in some way and make the combobox the only variable that changes according to which listbox is calling the template ?
Here an attempt on how you can achieve that, but I have to admit that it is messy but answers your question blindly! a better approach would be to properly implement a DataTemplateSelector.
The idea is to decouple the changing parts into two separate DataTemplates and put them into the resources, one with the Combobox and one for the TextBlock in your case:
<DataTemplate x:Key="DataTemplateCombobox">
<ComboBox ItemsSource="{Binding ValidDistributionSystemsForPanel}" ...>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="DataTemplateTextblock" >
<TextBlock Text="{Binding DistributionSystemName}" ... />
</DataTemplate>
Now those controls will be replaced with a ContentPresenter in your main (common) DataTemplate. The ContentPresenter uses a ContentTemplateSelector to select which sub-DataTemplate to used based on the name of the ListBox on which this DataTemplate is applied, this is why the Content is bound directly o the ListBox using ancestry binding:
<local:ValueDataTemplateSelector x:Key="TemplateSelector"
DefaultDataTemplate="{StaticResource DataTemplateTextblock}"
ComboboxDataTemplate="{StaticResource DataTemplateCombobox}"
TextBlockDataTemplate="{StaticResource DataTemplateTextblock}" />
<DataTemplate x:Key="OldPanelsTemplate">
<Grid Height="60" Margin="0" Name="OldPanelsTemplateGrid" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue" Text="tex" />
</Border>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" />
<ContentPresenter ContentTemplateSelector="{StaticResource TemplateSelector}" Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Grid.Row="0" Grid.Column="2">
</ContentPresenter>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of circuits to be copied: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfCircuitsToBeCopied}" />
</Grid>
</DataTemplate>
And here how your DataTemplateSelector should be implemented (basically):
public class ValueDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultDataTemplate { get; set; }
public DataTemplate ComboboxDataTemplate { get; set; }
public DataTemplate TextBlockDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var lb = item as ListBox;
if (lb is null)
return DefaultDataTemplate;
if (lb.Name == "ListOne")
return ComboboxDataTemplate;
if (lb.Name == "ListTwo")
return TextBlockDataTemplate;
return DefaultDataTemplate;
}
}
Finally, since your sub-DataTemplates lose their DataContexts due to the Content of the ContentPresenter being bound to the ListBox directly, then just hook their DataContexts again using ElementName Binding or something:
<DataTemplate x:Key="DataTemplateCombobox">
<ComboBox DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}" ItemsSource="{Binding ValidDistributionSystemsForPanel}" >
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="DataTemplateTextblock" >
<TextBlock Text="{Binding DistributionSystemName}" DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}"/>
</DataTemplate>
OldPanelsTemplateGrid is the First Grid in the main DataTemplate that should have the valid ListBoxItem DataContext.
Here is the full Xaml code:
</Window ...
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800" >
<Window.Resources>
<DataTemplate x:Key="DataTemplateCombobox">
<ComboBox DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}" ItemsSource="{Binding ValidDistributionSystemsForPanel}" >
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="DataTemplateTextblock" >
<TextBlock Text="{Binding DistributionSystemName}" DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}"/>
</DataTemplate>
<local:ValueDataTemplateSelector x:Key="TemplateSelector"
DefaultDataTemplate="{StaticResource DataTemplateTextblock}"
ComboboxDataTemplate="{StaticResource DataTemplateCombobox}"
TextBlockDataTemplate="{StaticResource DataTemplateTextblock}" />
<DataTemplate x:Key="OldPanelsTemplate">
<Grid Height="60" Margin="0" Name="OldPanelsTemplateGrid" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue" Text="tex" />
</Border>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" />
<ContentPresenter ContentTemplateSelector="{StaticResource TemplateSelector}" Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Grid.Row="0" Grid.Column="2">
</ContentPresenter>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of circuits to be copied: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfCircuitsToBeCopied}" />
</Grid>
</DataTemplate>
</Window.Resources>
<!--DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorLevel=2,AncestorType=DataTemplate}}"-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox x:Name ="ListOne" ItemsSource="{Binding MyCollection}" ItemTemplate="{StaticResource OldPanelsTemplate}"/>
<ListBox x:Name ="LisTwo" ItemsSource="{Binding MyCollection}" ItemTemplate="{StaticResource OldPanelsTemplate}" Grid.Row="1"/>
</Grid>
I managed to solve it by merging them in one template like this:
<DataTemplate x:Key="NewOldPanelsTemplate" DataType="{x:Type VM:CustomPanelBoard}">
<Grid Height="60">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue"
Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=(ItemsControl.AlternationIndex),
Converter={StaticResource IncrementerConverter}}" />
</Border>
<TextBlock Grid.Column="1" Text="{Binding Name}" />
<!-- To have same template for new and old panels we had the two elements (combobox and textblock) for distribution system and toggle visibility by converters according to their groupbox title -->
<ComboBox Grid.Column="2" ItemsSource="{Binding ValidDistributionSystemsForPanel}"
SelectedItem="{Binding SelectedValidDistributionSystemsForPanel}" HorizontalAlignment="Stretch"
IsHitTestVisible="{Binding DistributionSystemNotAssigned}" IsEnabled="{Binding DistributionSystemNotAssigned}"
ItemTemplate="{StaticResource DistributionSystemTemplate}"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=GroupBox, Mode=FindAncestor}, Path=Header,Converter={StaticResource NewPanelsTemplateVisibilityConverter}}">
</ComboBox>
<TextBlock Grid.Column="2" Text="{Binding DistributionSystemName}"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=GroupBox, Mode=FindAncestor}, Path=Header,Converter={StaticResource OldPanelsTemplateVisibilityConverter}}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of available ways: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfAvailableWays}" />
</Grid>
</DataTemplate>
and control the visibility of variable parts(ComboBox and TextBlock in my case) with converters like this :
public class OldPanelsTemplateVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((string)value == "Old Panels")
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
}
public class NewPanelsTemplateVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((string)value == "Old Panels")
{
return Visibility.Collapsed;
}
return Visibility.Visible;
}}
The solution above is based on that each of my listboxes is surrounded by groupbox with certain header (In other cases you may want to use ListBox Names in the converter as visibility toggle)
Thanks to #PavelAnikhouski for the idea of converters.
Also i tried the solution of DataTemplateSelector proposed by #SamTheDev and it worked also (Thanks #SamTheDev), but i preferred the converter solution because i didn't feel comfortable with the idea of losing datacontext through using content presenter (The bottom line here is both solutions work and no one is more elegant it is just personal preference)

WPF SelectedItem working inconsistently....(MVVM data binding)

I have a WPF ListBox that is a child of a ListView.
The ListBox has a 'SelectedItem' attribute that is bound to a property on my ViewModel SelectedTask.
When I click a row on my ListBox, then it updates SelectedTask. But only sometimes.....
By sometimes:
Every List Box row will update SelectedTask at least once
The ones in the "middle" of the screen will always update SelectedTask. I can hop from one row to another and back again, each time SelectedTask is updated
The ones at the top or bottom of the screen will update SelectedTask, but maybe only once. If I select another row and then return to this one, I can not get SelectedTask to be updated.
A highly cut-down/edited view of my XAML is as follows:
<ListView ItemsSource="{Binding WorkItems}" >
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="850"/>
</Grid.ColumnDefinitions>
/* Display properties from Work Item */
<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Code}" />
/* This SelectedItem works sometimes, but not all the time */
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding Tasks}" SelectedItem="{Binding Path=DataContext.SelectedTask, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
/* Display properties from Work Item Task */
<TextBlock Text="{Binding Path=Description}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
EDIT
I was asked what the SelectedTask property looked like:
public MyTaskType SelectedTask
{
get { return _selectedTask; }
set
{
_selectedTask = value;
NotifyPropertyChanged();
}
}

Changing ListView content in wpf

I would like to change the content of a ListView either programmatically or something xaml based.
<Page.Resources>
<CollectionViewSource
x:Name="queuesViewSource" Source="{Binding TicketQueues}" d:Source="{Binding Groups[0].TicketQueues, Source={d:DesignData Source=/DataModel/SampleData.json, Type=data:SampleDataSource}}"/>
<CollectionViewSource
x:Name="ticketsViewSource"
Source="{Binding Tickets}"
d:Source="{Binding Tickets, Source={d:DesignData Source=/DataModel/TicketsList.json, Type=data:TicketsListDataSource}}"/>
<DataTemplate x:Key="queuesViewItemTemplate">
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" Width="60" Height="60">
<Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
</Border>
<StackPanel Grid.Column="1" Margin="10,0,0,0">
<TextBlock Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" MaxHeight="40"/>
<TextBlock Text="{Binding Subtitle}" Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap"/>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ticketsListViewTemplate">
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Margin="10,0,0,0">
<TextBlock Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" MaxHeight="40"/>
<TextBlock Text="{Binding Due}" Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap"/>
</StackPanel>
</Grid>
</DataTemplate>
<local:ServiceDeskMasterViewDataTemplateSelector
QueuesTemplate="{StaticResource queuesViewItemTemplate}"
TicketListTemplate="{StaticResource ticketsListViewTemplate}"
x:Name="masterViewTemplateSelector" />
</Page.Resources>
...
<ListView Grid.Row="1" x:Name="masterList" ItemTemplateSelector="{StaticResource masterViewTemplateSelector}" />
And in the code-behind:
Constructor of Page:
{
// Initial Data setting...
Binding items = new Binding();
items.Source = this.Resources["queuesViewSource"];
this.masterList.SetBinding(ItemsControl.ItemsSourceProperty, items);
this.InvalidateVisualState();
}
And finally, the list view item selection to "hopefully" load the listview with different content, but it's not working. Please help
if (eArgs.AddedItems.Count == 1 && eArgs.RemovedItems.Count == 1)
{
Binding items = new Binding();
items.Source = this.Resources["ticketsViewSource"];
this.masterList.SetBinding(ItemsControl.ItemsSourceProperty, items);
//this.masterList.SetBinding(ItemsControl.ItemTemplateProperty, )
this.masterList.ItemTemplate = masterViewTemplateSelector.TicketListTemplate;
//this.masterList.ApplyTemplate();
this.InvalidateVisualState();
}
Try adding a new property like "MasterListItems" and bind to this property instead of directly to the "ticketsViewSource" or the "queueViewSource"
MasterListItems = this.Resources["ticketsViewSource"];
Binding items = new Binding();
items.Mode = BindingMode.TwoWay;
items.Source = MasterListItems;
Then instead of creating a new binding when changing the content, just update the ItemsSource:
MasterListItems = this.Resource["ticketsViewSource"]

TreeView, ObjectDataProvider, HierarchicalDataTemplate, xsd2Code not showing anything

I have an object hierarchy created with xsd2code. The object hierarchy
consist of a root object of type Project which contains an ObservableCollection
of Folder named folder which is a recursive object containing ObservableCollection
of Folder named folder and File named file
I want to bind this object hierarchy to a TreeView thru an ObjectDataProvider
and control the display thru HierarchicalDataTemplate definitions
The ObjectDataProvider and DataTemplates are defined in a resource merged in App.xaml like this
<HierarchicalDataTemplate x:Key="projectTemplate"
DataType="{x:Type model:Project}"
ItemsSource="{Binding Path=folder}"
>
<Grid ToolTip="{Binding Path=Name}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="Images/folder_documents_512.ico" Grid.Column="0"/>
<TextBlock Grid.Column="1" Margin="6,0,0,0"
Text="{Binding Path=Name}"
/>
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type model:Folder}"
ItemsSource="{Binding Path=file}">
<Grid ToolTip="{Binding Path=name}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="Images/folder_vectors_512.ico" Grid.Column="0"/>
<TextBlock Grid.Column="1" Margin="6,0,0,0"
Text="{Binding Path=name}"
/>
</Grid>
</HierarchicalDataTemplate>
<DataTemplate
DataType="{x:Type model:File}">
<Grid ToolTip="{Binding Path=name}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="Images/article-24.png" Grid.Column="0"/>
<TextBlock Grid.Column="1" Margin="6,0,0,0"
Text="{Binding Path=name}"
/>
</Grid>
</DataTemplate>
<ObjectDataProvider
x:Key="project"
>
</ObjectDataProvider>
The TreeView is defined like this
<TreeView x:Name="tvProject"
ItemsSource="{Binding Source={StaticResource project}}"
>
The global processing is : I load the object hierarchy from an Xml file (xsd2code functionality). As I can see in debug my object hierarchy is clean.
Then, I bind the object hierarchy to the ObjectDataProvider in code like this
ObjectDataProvider dp = Application.Current.FindResource("project") as ObjectDataProvider;
dp.ObjectInstance = _project;
Well, nothing appears in the TreeView.
What am I missing ?
I resolved my issue,
First, I had to bound a collection to the ObjectDataProvider and not just a single object. I donĀ“t think this is an ObjectDataProvider requirement but more a requirement for HierarchicalDataTemplate.
ObjectDataProvider dp = Application.Current.FindResource("project") as ObjectDataProvider;
ArrayList ocp = new ArrayList();
ocp.Add(_project);
dp.ObjectInstance = ocp;
Then I had to chain the reference to the different DataTemplates thru the ItemTemplate property like this:
First on the TreeView
<TreeView x:Name="tvProject"
ItemsSource="{Binding Source={StaticResource project}}"
ItemTemplate="{StaticResource projectTemplate}"
>
And then in the diferent DataTemplate
<DataTemplate x:Key="fileTemplate"
DataType="{x:Type model:File}">
<Grid ToolTip="{Binding Path=name}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="Images/article-24.png" Grid.Column="0"/>
<TextBlock Grid.Column="1" Margin="6,0,0,0"
Text="{Binding Path=name}"
/>
</Grid>
</DataTemplate>
<HierarchicalDataTemplate x:Key="folderTemplate"
DataType="{x:Type model:Folder}"
ItemsSource="{Binding Path=file}"
ItemTemplate="{StaticResource fileTemplate}">
<Grid ToolTip="{Binding Path=name}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="Images/folder_vectors_512.ico" Grid.Column="0"/>
<TextBlock Grid.Column="1" Margin="6,0,0,0"
Text="{Binding Path=name}"
/>
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="projectTemplate"
DataType="{x:Type model:Project}"
ItemsSource="{Binding Path=folder}"
ItemTemplate="{StaticResource folderTemplate}"
>
<Grid ToolTip="{Binding Path=Name}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="Images/folder_documents_512.ico" Grid.Column="0"/>
<TextBlock Grid.Column="1" Margin="6,0,0,0"
Text="{Binding Path=Name}"
/>
</Grid>
</HierarchicalDataTemplate>
Now it works
EDIT
Since Collection can give back different types (i.e Folder and File)
you need a template selector
public class FileSystemObjectTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is FileSystemObject)
{
if (item is File)
return Application.Current.FindResource("fileTemplate") as DataTemplate;
if (item is Folder)
return Application.Current.FindResource("folderTemplate") as DataTemplate;
}
return null;
}
}
and the call it in the XAML
<local:FileSystemObjectTemplateSelector x:Key="FileSystemObjectTemplateSelector" />
<HierarchicalDataTemplate x:Key="folderTemplate"
DataType="{x:Type model:Folder}"
ItemsSource="{Binding Path=fsObjects, Converter={StaticResource SortFoldersAndFiles}}"
ItemTemplateSelector="{StaticResource FileSystemObjectTemplateSelector}" >
</HierarchicalDataTemplate>

WPF items not visible when grouping is applied

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.

Resources