bind a list of lists to ListView and group them - c#

I have a ListView where I trying to bind a list of lists.
I grouped the items of mainList based on Key. Below is a sample of XAML and
C# code for binding list to ListView.
I Group MainList by Key. it's OK and working but how can I group second ListView for sublist ?
this is XAML a listView for mainList and another listview in itemtemplate for sublist :
<ListView x:Name="MyList" >
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Key}" />
<ListView Grid.Row="1" ItemsSource="{Binding SubList}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding id}" />
<TextBlock Grid.Column="1" Text="{Binding name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Margin="10" Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
</Grid>
<TextBlock Grid.Column="0" Margin="10,0,0,0" Text="Key :" Style="{StaticResource SubHeaderTextBlockStyle}"/>
<TextBlock Grid.Column="1" Margin="10,0,10,0" Text="{Binding Key}" Style="{StaticResource TextBlockStyle}"/>
</StackPanel>
<ItemsPresenter Margin="10" Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
And this is c# :
public MainWindow()
{
InitializeComponent();
ObservableCollection<ListObj> MainList1 = new ObservableCollection<ListObj>
{
new ListObj
{
Key="key1", Value="value1",
SubList=new ObservableCollection<SubListObj>
{
new SubListObj{id="subid1", name="subname1" },
new SubListObj{id="subid2", name="subname2" }
}
},
new ListObj
{
Key="key2", Value="value2",
SubList=new ObservableCollection<SubListObj>
{
new SubListObj{id="subid3", name="subname3" }
}
}
};
MyList.ItemsSource = MainList1;
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(MyList.ItemsSource);
PropertyGroupDescription gd = new PropertyGroupDescription("Key");
view.GroupDescriptions.Add(gd);
}
public class ListObj
{
public string Key { get; set; }
public string Value { get; set; }
public ObservableCollection<SubListObj> SubList { get; set; } = new ObservableCollection<SubListObj>();
}
public class SubListObj
{
public string id { get; set; }
public string name { get; set; }
}

You have to do it all in XAML (except the data initialization). Define two CollectionViewSource objects, one for the outer and one for the inner ListView.
To layout the group header use the GroupStyle.HeaderTemplate (instead of GroupStyle.ContainerStyle):
MainWindow.xaml
<Window>
<Window.Resources>
<CollectionViewSource x:Key="MainCollectionViewSource"
Source="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:SecondWindow}, Path=MainList}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Key" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<ListView ItemsSource="{Binding Source={StaticResource MainCollectionViewSource}}">
<ListView.ItemTemplate>
<DataTemplate DataType="ListObj">
<Grid>
<Grid.Resources>
<CollectionViewSource x:Key="SubCollectionViewSource"
Source="{Binding SubList}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="id" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Value}" />
<ListView Grid.Row="1"
ItemsSource="{Binding Source={StaticResource SubCollectionViewSource}}">
<ListView.ItemTemplate>
<DataTemplate DataType="SubListObj">
<Grid>
<TextBlock Text="{Binding name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate DataType="GroupItem">
<TextBlock FontWeight="Bold"
FontSize="14"
Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate DataType="GroupItem">
<TextBlock FontWeight="Bold"
FontSize="14"
Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public ObservableCollection<ListObj> MainList { get; set; }
public MainWindow()
{
InitializeComponent();
MainList1 = new ObservableCollection<ListObj>
{
new ListObj
{
Key="key1", Value="value1",
SubList=new ObservableCollection<SubListObj>
{
new SubListObj{id="subid1", name="subname1" },
new SubListObj{id="subid2", name="subname2" }
}
},
new ListObj
{
Key="key2", Value="value2",
SubList=new ObservableCollection<SubListObj>
{
new SubListObj{id="subid3", name="subname3" }
}
}
};
}
}

Related

Disable Itemscontrol components on button click

I'm learning wpf through mvvm.
My application consist of below logic.
Form which has a button that will add a selected components horizontally from Itemcontrol like textbox, combobox and rich textbox and a button whenever the button is clicked.
When the add button is clicked the specified set of components will be added in the line information dynamically.
2) Data will be inserted into the table after pressing the add info button.
3) The issue is after clicking the add info button, the components in the itemscontrol should be readonly. This is the place I'm struggling as of now.
Model.cs:
public class textboxModel
{
public string Text { get; set; }
public string lblText { get; set; }
public string isactive { get; set; }
public bool txtboxreadonly { get; set; }
}
public class ButtonDataModel
{
public string Content { get; set; }
public ICommand Command { get; set; }
public string column { get; set; }
public string isactive { get; set; }
public bool buttreadonly { get; set; }
}
ViewModel.cs:
public class viewmodel : notifyproperties
{
public Relaycommand Status { get; set; }
public Relaycommand AddCommand { get; set; }
public labelconversionOnPauseButtonclick pauseclick = new labelconversionOnPauseButtonclick();
public auditinformation auditid = new auditinformation();
public ButtonDataModel bdm = new ButtonDataModel();
public auditinformation adt
{
get { return auditid; }
set
{
if (value != auditid)
{
auditid = value;
OnPropertyChanged("adt");
}
}
}
public labelconversionOnPauseButtonclick res
{
get { return pauseclick; }
set
{
if (value != pauseclick)
{
pauseclick = value;
OnPropertyChanged("res");
}
}
}
public viewmodel()
{
Status = new Relaycommand(pauseclick.Statusdata);
AddCommand = new Relaycommand(o => auditid.addcommand());
}
}
public class auditinformation : notifyproperties
{
public Relaycommand Command { get; set; }
private string _lines;
public string Lines
{
get { return this._lines; }
set
{
this._lines = value;
this.OnPropertyChanged("Lines");
}
}
private readonly ObservableCollection<ButtonDataModel> _MyDatabutton = new ObservableCollection<ButtonDataModel>();
public ObservableCollection<ButtonDataModel> MyData { get { return _MyDatabutton; } }
private readonly ObservableCollection<textboxModel> _MyDatatxtbox = new ObservableCollection<textboxModel>();
public ObservableCollection<textboxModel> MyDatatxtbox { get { return _MyDatatxtbox; } }
private readonly ObservableCollection<LabelDataModel> _MyDataLabel = new ObservableCollection<LabelDataModel>();
public ObservableCollection<LabelDataModel> MyDataLabel { get { return _MyDataLabel; } }
public void addcommand()
{
int num= 1;
for (int i = 0; i < num; i++)
{
MyDatatxtbox.Add(new textboxModel
{
isactive = "1",
});
MyData.Add(new ButtonDataModel
{
Command = new Relaycommand(o => search()),
Content = "Add info",
isactive = "1",
});
}
}
public void search( )
{
var asss = MyDatatxtbox.Where(a=> a.isactive == "1").Select(a => a.Text);
var itemstoremove = MyDatatxtbox.Where(i => i.isactive == "1").ToList();
foreach (var s in asss)
{
foreach (var a in itemstoremove)
{
if (a.isactive == "1")
{
MessageBox.Show(s);
buttreadonly = true;
}
}
}
// var itemstoremove = MyDatatxtbox.Where(i => i.isactive == "1").ToList();
foreach(var a in itemstoremove)
{
a.isactive = "0";
}
//foreach (var a in itemstoremove)
//{
// a.txtboxreadonly = true;
//}
// var itemstoremovebutton = MyData.Where(i => i.isactive == "1").ToList();
// foreach (var a in itemstoremovebutton)
// {
//// MyData.Remove(a);
// a.isactive = "0";
// }
}
}
window.xaml:
<GroupBox Header="Audit Information" Grid.Row="1">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Text="Member ID"></TextBlock>
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" Width="85" ></TextBox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="2" Text="Claim Number"></TextBlock>
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="3" Width="85" ></TextBox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="4" Text="Date Audited"></TextBlock>
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="5" Width="85" ></TextBox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="6" Text="Query ID"></TextBlock>
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="7" Width="85" ></TextBox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="8" Text="Audit ID"></TextBlock>
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="9" Width="85" ></TextBox>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Width="85" Height="25" Command="{Binding AddCommand}" Content="Add" Grid.Column="10"></Button>
</Grid>
</GroupBox>
<GroupBox Grid.Row="2" Margin="0,0,0,0" Header="Line Information" >
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3.2*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.8*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Height="22" Grid.Column="0">
<Label HorizontalAlignment="Center" Content="DOS" />
</StackPanel>
<ItemsControl Grid.Column="0" Grid.Row="1" ItemsSource="{Binding adt.MyDatatxtbox}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,50">
<TextBox IsReadOnly="{Binding txtboxreadonly}" Text="{Binding Text, Mode=TwoWay}" Height="25" Width="85" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel Height="22" Grid.Column="1">
<Label HorizontalAlignment="Center" Content="CPT" />
</StackPanel>
<ItemsControl Grid.Column="1" Grid.Row="1" ItemsSource="{Binding adt.MyDatatxtbox}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,50">
<TextBox IsReadOnly="{Binding txtboxreadonly}" Margin="0,0,30,0" Text="{Binding Text, Mode=TwoWay}" Height="25" Width="85" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel Height="22" Grid.Column="2">
<Label HorizontalAlignment="Right" Content="Open-close Modifier" Margin="0,0,-17,0" Width="121" />
</StackPanel>
<ItemsControl Grid.Column="2" Grid.Row="1" ItemsSource="{Binding adt.MyDatatxtbox}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,50">
<TextBox IsReadOnly="{Binding txtboxreadonly}" Margin="0,0,30,0" Text="{Binding Text, Mode=TwoWay}" Height="25" Width="85" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel Height="22" Grid.Column="3">
<Label HorizontalAlignment="Center" Content="Comments" />
</StackPanel>
<ItemsControl Grid.Column="3" Grid.Row="1" ItemsSource="{Binding adt.MyDatatxtbox}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,10">
<RichTextBox IsReadOnly="{Binding txtboxreadonly}" Margin="0,0,0,0" Height="65" Width="425" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel Height="22" Grid.Column="4">
<Label HorizontalAlignment="Center" Content="Line Status" />
</StackPanel>
<ItemsControl Grid.Column="4" Grid.Row="1" ItemsSource="{Binding adt.MyDatatxtbox}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10,0,0,50">
<ComboBox IsEnabled= "{Binding txtboxreadonly}" Margin="0,0,0,0" Height="25" Width="95" >
<ComboBoxItem Content="Coffie"></ComboBoxItem>
<ComboBoxItem Content="Tea"></ComboBoxItem>
</ComboBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel Height="22" Grid.Column="5">
<Label HorizontalAlignment="Center" Content="Additional Comments" />
</StackPanel>
<ItemsControl Grid.Column="5" Grid.Row="1" ItemsSource="{Binding adt.MyDatatxtbox}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,10">
<RichTextBox IsReadOnly="{Binding txtboxreadonly}" Margin="0,0,0,0" Height="65" Width="425" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.Column="6" Grid.Row="1" ItemsSource="{Binding adt.MyData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,50">
<Button Margin="0,0,0,0" Height="25" Width="55" Content="{Binding Content}" Command="{Binding Command}" >
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ScrollViewer>
</GroupBox>
</Grid>
</Grid>
</TabItem>
</TabControl>
So the question is how to disable/readonly the Line Information ,textbox and cpt on add info button click.
Dirty way would be adding a "global" bool property in your ViewModel and binding it to your Controls IsEnabled/ReadOnly Properties.

How to bind class properties in expander header when grouping items in listview in wpf

I'm grouping the items in listview as shown here. Now I want to bind some of my class properties in the expander header (group header), I'm not able to bind any properties from my class in the <ListView.GroupStyle> section but able to bind properties in <ListView.View>.
Code what I've tried:
<UserControl x:Class="ClientAccessTool.CasesAndTasksDetailsControl"
xmlns:core="clr-namespace:ClientAccessTool.Core;assembly=ClientAccessTool.Core"
DataContext="{x:Static core:SiteListViewModel.Instance}"
>
<UserControl.Resources>
<CollectionViewSource x:Key="groupByCases2" Source="{Binding SelectedSite.CasesAndTasksList}" >
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="CaseName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<Grid>
<ListView x:Name="lvCasesAndTasks"
ItemsSource="{Binding Source={StaticResource groupByCases2}}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Margin="3" Width="383" HorizontalAlignment="Left" >
<Expander x:Name="lvExpander" IsExpanded="True"
Style="{StaticResource ExpanderStyle3}" >
<Expander.Header>
<Grid VerticalAlignment="Center" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Viewbox Height="23" Margin="5" Grid.Column="0" DockPanel.Dock="Left">
<Image Source="/Images/RedFlag.png" RenderOptions.BitmapScalingMode="HighQuality"/>
</Viewbox>
<Grid Grid.Column="1" MinWidth="250" >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Name}"
FontSize="18" VerticalAlignment="Center"
FontWeight="SemiBold"
Foreground="Red"
Margin="5"
/>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5" >
<TextBlock Text="Number of tasks:" FontFamily="{StaticResource FontAwesome}" Foreground="Gray"/>
<TextBlock Margin="5 0 0 0"
Text="{Binding CountOfTasks}"
FontFamily="{StaticResource FontAwesome}" Foreground="Gray" >
</TextBlock>
</StackPanel>
</Grid>
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
c# class:
namespace ClientAccessTool.Core
{
public class Case
{
public string SiteName { get; set; }
public string CaseName { get; set; }
public string CaseDescription { get; set; }
public List<Tasks> ListOfTasks { get; set; } = new List<Tasks>();
public string CountOfTasks => ListOfTasks.Count.ToString() ;
}
}
The item source SelectedSite.CasesAndTasksList is a List of type Case. I want to bind the CountOfTasks property inside the expander header but its not working.

RadioButton grouping not working in WPF when using ItemControl and DataBinding

I am having troubles grouping RadioButtons when using a WPF form and using DataBinding.
If I manually create my controls the behavior is as expected and the user is only allowed to select one item from the radiobutton group (bottom GroupBox), but if I use nested ItemControls to generate my layout and use data binding grouping does not work as expected and a user is allowed to select multiple radiobuttons in one group (top groupBox) as can be seen in this linked screenshot 1.
I have tried binding the GroupName property but fail to somehow find a binding property to use for GroupName property.
Perhaps the Text property of the TextBlock in the same grid row can be used, but I fail to find the proper Binding syntax to reference it.
What would be the way to get grouping to work?
Here is the XAML:
<Window x:Class="RadioButtonMadness.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"
mc:Ignorable="d" Title="MainWindow" SizeToContent="WidthAndHeight">
<Window.Resources>
<Style TargetType="RadioButton">
<Setter Property="Margin" Value="10" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Height" Value="22" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="10" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Height" Value="22" />
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions />
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Header="Prognostic factors - NOK">
<ItemsControl ItemsSource="{Binding PrognosticFactors}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="PrognosticFactors" Grid.Row="0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions />
<TextBlock Text="{Binding Title}" />
<ItemsControl ItemsSource="{Binding Options}" Grid.Column="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Key}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
<GroupBox Header="Prognostic factors - OK" Grid.Row="1">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions/>
<TextBlock Text="Test4" Grid.Column="0"/>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<RadioButton Content="One"/>
<RadioButton Content="Two"/>
<RadioButton Content="Three"/>
</StackPanel>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions />
<TextBlock Text="Test5" Grid.Column="0"/>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<RadioButton Content="One"/>
<RadioButton Content="Two"/>
<RadioButton Content="Three"/>
</StackPanel>
</Grid>
</StackPanel>
</GroupBox>
</Grid>
</Window>
As well as the code behind:
using System;
using System.Collections.Generic;
using System.Windows;
namespace RadioButtonMadness
{
public class Option
{
public String Key{ get; set; }
public Double Value { get; set; }
public Option(String key, Double value)
{
Key = key;
Value = value;
}
}
public class PrognosticFactor
{
private String title;
public List<Option> Options
{
get
{
var list = new List<Option>();
list.Add(new Option("One", 1));
list.Add(new Option("Two", 2));
list.Add(new Option("Three", 3));
return list;
}
}
public String Title
{
get { return title; }
}
public PrognosticFactor(String value)
{
title = value;
}
}
public class ViewModel
{
public List<PrognosticFactor> PrognosticFactors
{
get
{
var list = new List<PrognosticFactor>();
list.Add(new PrognosticFactor("Test1"));
list.Add(new PrognosticFactor("Test2"));
list.Add(new PrognosticFactor("Test3"));
return list;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
}
Just bind GroupName to the Title property. You can do it something like this:
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Key}" GroupName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}, Path=DataContext.Title}" />
</DataTemplate>
</ItemsControl.ItemTemplate>

WPF ListView Headers Repositioning

I have a ListView (with an inner ListView) that displays data like this:
I would like to display the inner ListView headers above the grouping like so:
Is it possible to re-position the column headers as shown or simply create some fake headers on the outer ListView?
Here is the XAML code I have so far:
<ListView Name="ListView_GarnishmentCalculations"
ItemsSource="{Binding GarnishedEmployees, UpdateSourceTrigger=PropertyChanged}"
MaxHeight="{Binding ActualHeight,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollContentPresenter}},
Converter={StaticResource MathConverter}, ConverterParameter=x-220}"
Margin="5,20,10,10"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4">
<!-- Required for right justifying text in a TextBlock -->
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<!-- Group results and show EmpNo, Name and WorkState -->
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="175" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Items[0].EmpNo}"
FontWeight="Bold"
Grid.Column="0" />
<TextBlock Text="{Binding Items[0].FullName}"
FontWeight="Bold"
Grid.Column="1" />
<TextBlock Text="{Binding Items[0].WorkState}"
FontWeight="Bold"
Grid.Column="2" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemTemplate>
<DataTemplate>
<!-- Inner ListView of garnishment details -->
<ListView ItemsSource="{Binding Garnishments}">
<ListView.View>
<GridView>
<!-- CaseID -->
<GridViewColumn Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CaseNumber, Converter={StaticResource StringIsNullOrEmptyConverter}, ConverterParameter='No Case ID'}"
TextAlignment="Left">
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
<GridViewColumn.Header>
<GridViewColumnHeader Content=" Case ID" />
</GridViewColumn.Header>
</GridViewColumn>
<!-- Vendor -->
<GridViewColumn Width="150"
DisplayMemberBinding="{Binding Vendor}">
<GridViewColumn.Header>
<GridViewColumnHeader Content=" Vendor" />
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is what I came up with, NOT by my self I used my google fu skills for this.
Credit to this SO post.
So here is what I have for my model:
namespace Model
{
public class Case
{
public int CaseID { get; set; }
public int Vendor { get; set; }
}
}
And now user:
namespace Model
{
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string State { get; set; }
public List<Case> Cases { get; set; }
}
}
Now in my MainViewModel:
using Model;
using System.Collections.Generic;
namespace VM
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
Users = new List<User>();
for (int i = 0; i < 20000; i++)
{
Users.Add(new User
{
ID = i,
Name = $"John the {i + 1}",
State = i % 2 == 0 ? "CA" : "IL",
Cases = new List<Case>() { new Case { CaseID = (i + 1) * 10, Vendor = ((i + 1) * 10) - 2 }, new Case { CaseID = (i + 1) * 10, Vendor = ((i + 1) * 10) - 2 } }
});
}
}
private List<User> users;
public List<User> Users
{
get { return users; }
set { users = value; OnPropertyChanged(); }
}
}
}
On to the View:
<Window x:Class="SO_App.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:vm="clr-namespace:VM;assembly=VM"
xmlns:model="clr-namespace:Model;assembly=Model"
xmlns:local="clr-namespace:SO_App"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<CollectionViewSource Source="{Binding Users}" x:Key="Users"/>
</Window.Resources>
<Grid>
<ListView>
<ListView.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource Users}}"/>
</CompositeCollection>
</ListView.ItemsSource>
<ListView.View>
<GridView>
<GridViewColumn Header="Case ID" Width="100"/>
<GridViewColumn Header="Vendor" Width="100"/>
</GridView>
</ListView.View>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</DataTemplate.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding ID}" MinWidth="50"/>
<TextBlock Text="{Binding Name}" MinWidth="250" Grid.Column="1"/>
<TextBlock Text="{Binding State}" MinWidth="50" Grid.Column="2"/>
<ListView Grid.Row="1" ItemsSource="{Binding Cases}" Grid.ColumnSpan="3">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding CaseID}" MinWidth="100"/>
<TextBlock Text="{Binding Vendor}" MinWidth="100" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Caveat:
You will need to handle the scroll event on the inner ListView so it doesn't swallow the mouse scroll.
P.S.
this is the BaseViewModel implementation:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace VM
{
public class BaseViewModel : INotifyPropertyChanged
{
#region INPC
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string prop = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
#endregion
}
}
Which then produces this as a result:

WPF MVVM changing viewmodel calls dependency property on old viewmodel

I have a TabViewModel wich contains a dependency property CurrentViewModel. The CurrentViewModel property is bound to a ContentControl in the view TabView.xaml. The TabViewModel also contains a command to change the CurrentViewModel to a ProductViewModel:
public class TabViewModel: BaseViewModel
{
public string TabName { get; set; }
//public List<BaseViewModel> ViewModels { get; set; }
private PageViewModel _currentViewModel;
public PageViewModel CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
OnPropertyChanged("CurrentViewModel");
}
}
public TabViewModel(string tabName, PageViewModel currentViewModel)
{
TabName = tabName;
CurrentViewModel = currentViewModel;
}
private ICommand _navigateToProductViewModelCommand;
public ICommand NavigateToProductViewModelCommand
{
get
{
if (_navigateToProductViewModelCommand == null)
{
_navigateToProductViewModelCommand = new DelegateCommand<Product>(
(p) =>
{
CurrentViewModel = new ProductViewModel();
});
}
return _navigateToProductViewModelCommand;
}
}
}
TabView.xaml
<UserControl x:Class="Monitoring_Tool.Views.TabView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ProgressBar Value="{Binding Path=CurrentViewModel.PageProgress}" Height="5" Grid.Row="0" Margin="0,0,0,10">
<ProgressBar.Style>
<Style TargetType="{x:Type ProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Border BorderThickness="0,0,0,0" Background="LightGray" CornerRadius="0" Padding="0">
<Grid x:Name="PART_Track">
<Rectangle x:Name="PART_Indicator" HorizontalAlignment="Left" Fill="#00B6FA" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ProgressBar.Style>
</ProgressBar>
<ContentControl Content="{Binding Path=CurrentViewModel}" Grid.Row="1" />
</Grid>
I instantiate the TabViewModel like this:
new TabViewModel("Producten", new ProductsViewModel())
The ProductsView.xaml is shown like it should be. In the ProductsView.xaml I call the command from the TabViewModel like this:
<DataGrid.InputBindings>
<MouseBinding
MouseAction="LeftDoubleClick"
Command="{Binding DataContext.NavigateToProductViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type views:TabView}}}"/>
</DataGrid.InputBindings>
When the datagrid is empty, the command is executed and ProductView.xaml appears like it should be. But when the datagrid is not empty somthing strange happens:
the command is executed, and when I debug I can see that the currentViewModel is changed to ProductViewModel. Then when OnPropertyChanged("CurrentViewModel") is called. There is a set call (value = null) to a depedency property (SelectedAssetCategory) on the ProductsViewModel, wich was replaced and doesn't exits anymore?!
When I put CurrentViewModel = null the same thing happens, I can only do CurrentViewModel = new ProductsViewModel. So I guess it's somthing with updating the UI?
In the App.xaml I defined the following recources:
<DataTemplate DataType="{x:Type viewmodels:TabViewModel}">
<views:TabView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ProductsViewModel}">
<views:ProductsView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ProductViewModel}">
<views:ProductView />
</DataTemplate>
The ProductsViewModel looks like this:
class ProductsViewModel: PageViewModel
{
private readonly MonitotingToolEntities _databaseEntities;
public ProductsViewModel()
{
_databaseEntities = new MonitotingToolEntities();
AssetCategories = new ObservableCollection<AssetCategory>(_databaseEntities.AssetCategory.ToList())
{
new AssetCategory() {AssetCategoryID = 0, AssetCategoryName = "Alles"}
};
Results = new ObservableCollection<Product>();
}
public ObservableCollection<AssetCategory> AssetCategories { get; set; }
private AssetCategory _selectedAssetCategory;
public AssetCategory SelectedAssetCategory
{
get { return _selectedAssetCategory; }
set
{
_selectedAssetCategory = value; //this one is called with value = null
OnPropertyChanged("SelectedAssetCategory");
Filter();
}
}
public ObservableCollection<Product> Results { get; set; }
public void Filter()
{
Results.Clear();
List<Product> products =
SelectedAssetCategory.AssetCategoryID == 0
? _databaseEntities.Product.ToList()
: SelectedAssetCategory.Product.ToList();
foreach (Product product in products)
{
Results.Add(product);
}
}
}
ProductsView.xaml:
<UserControl x:Class="Monitoring_Tool.Views.ProductsView"
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:views="clr-namespace:Monitoring_Tool.Views"
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:viewModels="clr-namespace:Monitoring_Tool.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<CollectionViewSource x:Key="CvsAssetCategories" Source="{Binding Path= AssetCategories}" >
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription PropertyName="AssetCategoryID"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<CollectionViewSource x:Key="CvsResults" Source="{Binding Path= Results}" >
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="AssetCategory.AssetCategoryName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style TargetType="Image" x:Key="ImageDisabledStyle">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid Margin="0, 0, 0, 10" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Asset Categorie:" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,10,0"/>
<ComboBox Grid.Column="1"
ItemsSource="{Binding Source={StaticResource CvsAssetCategories}}"
DisplayMemberPath="AssetCategoryName"
SelectedItem="{Binding SelectedAssetCategory}"
Margin="0,0,10,0"/>
<TextBlock Text="Zoeken:" Grid.Column="3" VerticalAlignment="Center" Margin="0,0,10,0"/>
<ComboBox Grid.Column="4"
SelectedItem="{Binding SelectedSearchField}"
Margin="0,0,10,0"/>
<TextBox Text="{Binding Path=SearchQuery, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="5" Margin="0,0,10,0">
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=SearchCommand}" CommandParameter="{Binding SearchQuery}" Key="Enter" />
</TextBox.InputBindings>
</TextBox>
<Button Grid.Column="6"
Command="{Binding SearchCommand}"
CommandParameter="{Binding SearchQuery}"
Padding="5,0,5,0" Margin="0,0,10,0" >
<Button.Content>
<Image Source="/Recourses/SearchIcon.png"
Stretch="None" VerticalAlignment="Top" Style="{Binding Source={StaticResource ImageDisabledStyle}}"/>
</Button.Content>
</Button>
<Button Grid.Column="7"
Command="{Binding CancelSearchCommand}"
IsEnabled="{Binding CancelSearchEnabled}"
Padding="5,0,5,0">
<Button.Content>
<Image Source="/Recourses/CancelSearchIcon.png"
Stretch="None" VerticalAlignment="Top" Style="{Binding Source={StaticResource ImageDisabledStyle}}"/>
</Button.Content>
</Button>
</Grid>
<DataGrid Name="DgProducts" AutoGenerateColumns="False"
RowHeaderWidth="0" Margin="0,0,0,10" Grid.Row="1" IsReadOnly="True"
SelectionMode="Single" CanUserReorderColumns="False"
EnableRowVirtualization="True" VirtualizingPanel.IsVirtualizingWhenGrouping="True"
ItemsSource="{Binding Source={StaticResource CvsResults}}" SelectedItem="{Binding SelectedProduct}">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.InputBindings>
<MouseBinding
MouseAction="LeftDoubleClick"
Command="{Binding DataContext.NavigateToProductViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type views:TabView}}}"
/>
</DataGrid.InputBindings>
<DataGrid.Resources>
<Style TargetType="DataGridColumnHeader" x:Key="DgVerticalColumnHeader">
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="270" />
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="LightGray"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey }"
Color="Black"/>
</DataGrid.Resources>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<StackPanel>
<TextBlock Text="{Binding Path=Name}" Background="DarkGray" Padding="2,0,0,0"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Manager.ManagerName}" Header="Manager" />
<DataGridTextColumn Binding="{Binding Path=ProductName}" Header="Product" />
<DataGridTextColumn Binding="{Binding Path=MonitoringBy}" Header="Monitoring door" />
<DataGridTextColumn Binding="{Binding Path=AumProduct}" Header="AUM Product (mln)" />
<DataGridTextColumn Binding="{Binding Path=AumProductDate, StringFormat='{}{0:dd-MM-yyyy}'}" Header="Datum AUM Product" />
<DataGridTextColumn Binding="{Binding Path=AumStrategy}" Header="AUM Strategie (mln)" />
<DataGridTextColumn Binding="{Binding Path=AumStrategyDate, StringFormat='{}{0:dd-MM-yyyy}'}" Header="Datum AUM Strategie" />
<DataGridTextColumn Binding="{Binding Path=Aum}" Header="AUM (mln)" />
<DataGridTextColumn Binding="{Binding Path=TotalExpenseRatio}" Header="TER (bp)" />
<DataGridTextColumn Binding="{Binding Path=Fee}" Header="Total Fee" />
</DataGrid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Results.Count, StringFormat='{}{0} Producten'}" Grid.Column="0" Margin="0,0,0,0"/>
<Button Grid.Column="2"
Content="Toevoegen"
Padding="5,0,5,0" Margin="0,0,10,0"
Command="{Binding AddProductCommand}" />
<Button Grid.Column="3"
Content="Verwijderen"
Padding="5,0,5,0"
Command="{Binding Path=RemoveProductCommand}"
CommandParameter="{Binding Path=SelectedProduct}"/>
</Grid>
</Grid>
PageViewModel is an abstract class:
public abstract class PageViewModel: BaseViewModel
{
private int _pageProgress;
public int PageProgress
{
get { return _pageProgress; }
set
{
_pageProgress = value;
OnPropertyChanged("PageProgress");
}
}
}
It's kind of weird but it has something to do with the collectionviewsource (CvsAssetCategories) bound to the combobox. If I bind directly to the combobox without using the collectionviewsource the dependency property is not called. However, I like to use the collectionviewsource for the sortdescriptor. My solution now is a not null check on the setter from the dependency property, but I think its nasty way to go.

Categories