Grouping in ItemsControl using ItemsSource from ViewModel - wpf

I've defined ItemsControl like that:
<ItemsControl Grid.Row="2" Style="{StaticResource SellingDashboardToDosList}"
BorderThickness="1" Background="#C7E8F8" HorizontalAlignment="Stretch"
ItemsSource="{Binding Path=ToDoList}">
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<GroupBox Header="{Binding Path=Model.TodoType}" >
<ItemsPresenter />
</GroupBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
</ItemsControl>
The ItemsSource is a SynchronisedObservableCollection<T> in the ViewModel. But this XAML doesn't produce any grouping. I assume that I should specify somehow that ItemsSource is groupable. But where should I specify it?
If I would use an XmlDataProvider with some static data, then I could do it in a CollectionViewSource element like in following example: http://cromwellhaus.com/2010/03/grouping-is-crazy-easy-in-wpf/ (Archived).
I've tried to do it like that:
<CollectionViewSource x:Key="CollectionViewSource1" Source="{Binding Path=ToDoList}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="TodoType"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
But then I get a runtime binding error:
Value produced by BindingExpression is not valid for target property.;
Value='System.Windows.Data.ListCollectionView'
BindingExpression:Path=ToDoList; DataItem='ToDosViewModel'
(HashCode=40956219); target element is 'CollectionViewSource'
(HashCode=51380674); target property is 'Source' (type 'Object');

But then I get a runtime binding error that ItemsSource is of invalid type.
Did you reference it correctly? You need to specify it as Binding.Source:
ItemsSource="{Binding Source={StaticResource CollectionViewSource1}}"

I've used ListCollectionView in C# instead of CollectionViewSource in XAML.

Related

How to set ItemsSource of a ComboBox, if it was present in an ItemsCotrol

ComboBox items were not displaying, if we try to keep that combobox in an ItemsCotrol. Please click here for understanding my requirement
My requirement is to keep a combobox in an ItemsControl, so that the ItemsControl qill be having 5 Comboboxes in it and each combox will be having a collection of items which we can select. So for that i tried with the below code and able to get the comboboxes in the ItemsControl, but the comboboxes collection is getting filled, any suggestions or workaround please..
<xamDataPresenter:Field Label="Reqs" BindingType="Unbound" Row="0" Column="4">
<xamDataPresenter:Field.CellValuePresenterStyle>
<Style TargetType="{x:Type xamDataPresenter:CellValuePresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type xamDataPresenter:CellValuePresenter}">
<ItemsControl Name="I" ItemsSource="{Binding Path=DataItem.CollectionCount}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataItem.Collection}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</xamDataPresenter:Field.CellValuePresenterStyle>
</xamDataPresenter:Field>
Ok, I've written up the most basic of ItemsControl to try and explain how these things work, which you can hopefully adapt for whatever your using for your dataitems.
So in your window's resources i've created a datatemplate. This respresents a repeating step and will be based on a DataItem. In this case my DataItem has 2 properties: DataItemProperty(string) and SelectedItem. The SelectedItem will have the same DataType of whatever it is your planning on showing in the combobox.
<DataTemplate x:Key="StepTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<TextBlock Text="{Binding Path=DataItemProperty}" Grid.Column="0"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding Path=DataContext.ItemsToSelectFrom, Mode=OneWay, RelativeSource={RelativeSource AncestorType=Window}}"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
The combobox in this example will be getting a list of available options from the code behind/viewmodel and not the DataItem, but when you select something it updates the SelectedItem property on the DataItem.
Then to show your items:
<ItemsControl
Focusable="False"
ItemTemplate="{StaticResource StepTemplate}"
ItemsSource="{Binding Path=Steps, Mode=OneWay}" />
So Steps is a property in my codebehind/viewmodel that will determine how many 'rows' get displayed.
The itemsControl allows you to add repeating sets of data easily, without having to write the same xaml multiple times.
Hope that helps?

WPF TreeView IsSelected Binding Throwing Null Reference Exception

I've got a tabcontrol that is bound to a tab collection in my view model. Right now there are 2 types of tabs and one of them has a treeview in it. When the tab is first created, the treeview selection works. When I switch tabs and come back to the tab with the treeview in it, the treeview items seem to become unbound. When I try to select one I get the following error:
System.Windows.Data Error: 8 : Cannot save value from target back to source.
BindingExpression:Path=IsSelected; DataItem='NavigationItem' (HashCode=50956576);
target element is 'TreeViewItem' (Name=''); target property is 'IsSelected' (type
'Boolean') NullReferenceException:'System.NullReferenceException:
Object reference not set to an instance of an object.
at System.ComponentModel.ReflectPropertyDescriptor.SetValue(
Object component, Object value)
at MS.Internal.Data.PropertyPathWorker.SetValue(Object item, Object value)
at MS.Internal.Data.ClrBindingWorker.UpdateValue(Object value)
at System.Windows.Data.BindingExpression.UpdateSource(Object value)'
I'm not sure why this is occurring. When debugging through the application my model still has all of the correct data, and the treeview even displays the information from the model meaning it has to have been bound correctly when the tab was initially changed. It's just when I try to select an item after having left the tab and come back. Here's the source for my tab control and the tab in question with a treeview in it:
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
...
</TabControl.ItemTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type tabs:ArchitectureTabViewModel}">
<views:ArchitectureTab/>
</DataTemplate>
<DataTemplate DataType="{x:Type tabs:TestOrderTabViewModel}">
<views:TestOrderTab/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
And the tab in question:
<Grid DataContext="{Binding GraphNavigationModel}">
...
<TreeView ItemsSource="{Binding NavigationTree}" Background="#00ffffff" Margin="5" BorderThickness="0" MinWidth="200">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Text}">
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Grid>

How to show Master-Detail Informations in a collapsable ListView

I have a class that describes a certain task. This "task"-Class has a list of substeps.
What I wanted to do is to show these Informations in a ListView where the task-description should be used as the Groupheader and the substeps as the details
Also I wanted to be able to collapse and expand these groups.
Here is what I tried so far (simplified, but you'll get the idea hopefully):
public class Task : INotifyPropertyChanged
{
public string Description;
public ObservableCollection<String> substeps;
...
}
in the Viewmodel :
Task t = new Task();
t.Description = "Task1";
t.substeps.Add("substep 1");
t.substeps.Add("substep 2");
...
Tasks = new CollectionViewSource { Source = TaskList }; //TaskList is just a ObservableCollection<Task>
Tasks.GroupDescriptions.Add(new PropertyGroupDescription("Description"));
in xaml:
<s:SurfaceListBox Width="500" Height="1000" ItemsSource="{Binding TaskList.View}">
<s:SurfaceListBox.Resources>
<Style x:Key="ContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Description}" IsExpanded="True">
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</s:SurfaceListBox.Resources>
<s:SurfaceListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ContainerStyle}"/>
</s:SurfaceListBox.GroupStyle>
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding substeps}"/>
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
</s:SurfaceListBox>
The Result is a Listbox with my collapsed item. If I expand it, instead of each step I only see "(Listing)"
Do I have to build a class with the substeps and the description in it, and group by the description?
Try this:
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource={Binding substeps} />
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
Your problem is that the string binding to the ObservableCollection will just do a ToString() on that collection. You need iterate through the collection and display each item. By using the ItemsControl as I have done, you can also DataTemplate each subtask as you see fit.
ControlTemplate Binding
The Expander header binding will not work because it is inside a ControlTemplate which is inside the Style. The DataContext for the control template will not be your ViewModel but the control (i.e. SurfaceListBox) itself. Similar question is here.
There are two ways you can fix this.
1.Use DataTemplate
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Description}"
IsExpanded="True" >
<ItemsControl ItemsSource={Binding substeps} />
</Expander >
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
2.Use TemplatedParent binding
<Expander Header="{Binding Content.Description, , RelativeSource={RelativeSource TemplatedParent}}"
IsExpanded="True">
I personally recommend option 1.

WPF Error: Cannot find governing FrameworkElement for target element

I've got a DataGrid with a row that has an image. This image is bound with a trigger to a certain state. When the state changes I want to change the image.
The template itself is set on the HeaderStyle of a DataGridTemplateColumn. This template has some bindings. The first binding Day shows what day it is and the State changes the image with a trigger.
These properties are set in a ViewModel.
Properties:
public class HeaderItem
{
public string Day { get; set; }
public ValidationStatus State { get; set; }
}
this.HeaderItems = new ObservableCollection<HeaderItem>();
for (int i = 1; i < 15; i++)
{
this.HeaderItems.Add(new HeaderItem()
{
Day = i.ToString(),
State = ValidationStatus.Nieuw,
});
}
Datagrid:
<DataGrid x:Name="PersoneelsPrestatiesDataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AutoGenerateColumns="False" SelectionMode="Single" ItemsSource="{Binding CaregiverPerformances}" FrozenColumnCount="1" >
<DataGridTemplateColumn HeaderStyle="{StaticResource headerCenterAlignment}" Header="{Binding HeaderItems[1]}" Width="50">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter},Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextAlignment="Center" Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
Datagrid HeaderStyleTemplate:
<Style x:Key="headerCenterAlignment" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Day}" />
<Image x:Name="imageValidation" Grid.Row="1" Width="16" Height="16" Source="{StaticResource imgBevestigd}" />
</Grid>
<ControlTemplate.Triggers>
<MultiDataTrigger >
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding State}" Value="Nieuw"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="imageValidation" Property="Source" Value="{StaticResource imgGeenStatus}"/>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now when I startup the project the images doesn't show and I get this error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=HeaderItems[0]; DataItem=null; target element is 'DataGridTemplateColumn' (HashCode=26950454); target property is 'Header' (type 'Object')
Why is this error showing?
Sadly any DataGridColumn hosted under DataGrid.Columns is not part of Visual tree and therefore not connected to the data context of the datagrid. So bindings do not work with their properties such as Visibility or Header etc (although these properties are valid dependency properties!).
Now you may wonder how is that possible? Isn't their Binding property supposed to be bound to the data context? Well it simply is a hack. The binding does not really work. It is actually the datagrid cells that copy / clone this binding object and use it for displaying their own contents!
So now back to solving your issue, I assume that HeaderItems is a property of the object that is set as the DataContext of your parent View. We can connect the DataContext of the view to any DataGridColumn via something we call a ProxyElement.
The example below illustrates how to connect a logical child such as ContextMenu or DataGridColumn to the parent View's DataContext
<Window x:Class="WpfApplicationMultiThreading.Window5"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vb="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Window5" Height="300" Width="300" >
<Grid x:Name="MyGrid">
<Grid.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
</Grid.Resources>
<Grid.DataContext>
<TextBlock Text="Text Column Header" Tag="Tag Columne Header"/>
</Grid.DataContext>
<ContentControl Visibility="Collapsed"
Content="{StaticResource ProxyElement}"/>
<vb:DataGrid AutoGenerateColumns="False" x:Name="MyDataGrid">
<vb:DataGrid.ItemsSource>
<x:Array Type="{x:Type TextBlock}">
<TextBlock Text="1" Tag="1.1"/>
<TextBlock Text="2" Tag="1.2"/>
<TextBlock Text="3" Tag="2.1"/>
<TextBlock Text="4" Tag="2.2"/>
</x:Array>
</vb:DataGrid.ItemsSource>
<vb:DataGrid.Columns>
<vb:DataGridTextColumn
Header="{Binding DataContext.Text,
Source={StaticResource ProxyElement}}"
Binding="{Binding Text}"/>
<vb:DataGridTextColumn
Header="{Binding DataContext.Tag,
Source={StaticResource ProxyElement}}"
Binding="{Binding Tag}"/>
</vb:DataGrid.Columns>
</vb:DataGrid>
</Grid>
</Window>
The view above encountered the same binding error that you have found if I did not have implemented the ProxyElement hack. The ProxyElement is any FrameworkElement that steals the DataContext from the main View and offers it to the logical child such as ContextMenu or DataGridColumn. For that it must be hosted as a Content into an invisible ContentControl which is under the same View.
I hope this guides you in correct direction.
A slightly shorter alternative to using a StaticResource as in the accepted answer is x:Reference:
<StackPanel>
<!--Set the DataContext here if you do not want to inherit the parent one-->
<FrameworkElement x:Name="ProxyElement" Visibility="Collapsed"/>
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn
Header="{Binding DataContext.Whatever, Source={x:Reference ProxyElement}}"
Binding="{Binding ...}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
The main advantage of this is: if you already have an element which is not a DataGrid's ancestor (i.e. not the StackPanel in the example above), you can just give it a name and use it as the x:Reference instead, hence not needing to define any dummy FrameworkElement at all.
If you try referencing an ancestor, you will get a XamlParseException at run-time due to a cyclical dependency.
The way without a proxy is to set bindings in the constructor:
var i = 0;
var converter = new BooleanToVisibilityConverter();
foreach(var column in DataGrid.Columns)
{
BindingOperations.SetBinding(column, DataGridColumn.VisibilityProperty, new Binding($"Columns[{i++}].IsSelected")
{
Source = ViewModel,
Converter = converter,
});
}
The Proxy Element didn't work for me, for a tooltip. For an infragistics DataGrid I did this, you might change it easily to your kind of grid:
<igDP:ImageField Label="_Invited" Name="Invited">
<igDP:Field.Settings>
<igDP:FieldSettings>
<igDP:FieldSettings.CellValuePresenterStyle>
<Style TargetType="{x:Type igDP:CellValuePresenter}">
<Setter Property="ToolTip">
<Setter.Value>
<Label Content="{Binding DataItem.InvitationSent, Converter={StaticResource dateTimeConverter}}"/>
</Setter.Value>
</Setter>
</Style>
</igDP:FieldSettings.CellValuePresenterStyle>
</igDP:FieldSettings>
</igDP:Field.Settings>
</igDP:ImageField>

Binding a CollectionViewSource in a ControlTemplate

I am trying to create a ControlTemplate which will merge 2 IQueryables (and some other things) into a CompositeCollection.
I am having issues getting the CollectionViewSource.Source to bind correctly to the template. It looks like TemplateBinding in the Resources section is not supported. What is the best way to get around this limitation?
Code sample is below:
-- C#
IQueryable Items1; // (DependencyProperty)
IQueryable Items2; // (DependencyProperty)
-- XAML
<ControlTemplate TargetType="{x:Type Components:Selector}">
<ControlTemplate.Resources>
<!-- The two bound properties below fail to bind -->
<CollectionViewSource Source="{Binding Items1,RelativeSource={RelativeSource TemplatedParent}}" x:Key="Items1Key" />
<CollectionViewSource Source="{Binding Items2, RelativeSource={RelativeSource TemplatedParent}}" x:Key="Items2Key" />
<CompositeCollection x:Key="collection">
<CollectionContainer Collection="{Binding Source={StaticResource Items1Key}}" />
<CollectionContainer Collection="{Binding Source={StaticResource Items2Key}}" />
</CompositeCollection>
</ControlTemplate.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource collection}}" />
</ControlTemplate>

Resources