Combobox inside datagrid selected item - silverlight

I have a datagrid which has a column of comboboxes.
The data grid itemssource is a collection of UserInfo objects.
Here's the definition of UserInfo class:
public class UserInfo
{
public string User { get; set; }
public UserRole Role { get; set; }
}
public enum UserRole
{
None = 0,
Administrator = 1,
Reviewer = 2,
}
When I have the collection, I assign it to the datagrid:
private void svc_GetAllUsersCompleted(object sender, ServiceReference1.GetAllUsersCompletedEventArgs args)
{
ObservableCollection<UserInfo> users = args.Result;
UsersPage.dataGrid1.ItemsSource = users;
}
Here's the xaml of the datagrid:
<data:DataGrid Margin="5,25,5,17" AutoGenerateColumns="False" AllowDrop="True" Name="dataGrid1" SelectionMode="Single" UseLayoutRounding="True" SelectionChanged="dataGrid1_SelectionChanged" Grid.RowSpan="2" Grid.ColumnSpan="2" Grid.Row="1" ItemsSource="{Binding}" >
<data:DataGrid.Resources>
<DataTemplate x:Key="UserRoleTemplate">
<Border BorderThickness="0,0,0,0" BorderBrush="#6FBDE8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ComboBox Name="cmbUserRoleTypes" VerticalAlignment="Center" Grid.Column="0" Loaded="cmbUserRoleTypes_Loaded" SelectedIndex="0" ItemsSource="{Binding GetListOfRoles,Source={StaticResource rList}}" SelectedValue="{Binding Role, Mode=TwoWay}" ></ComboBox>
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="UserNameTemplate">
<Border BorderThickness="0,0,0,0" BorderBrush="#6FBDE8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Name="txtUserName" VerticalAlignment="Center" Grid.Column="0" Loaded="cmbUserRoleTypes_Loaded" Text="{Binding Path=Name}" ></TextBlock>
</Grid>
</Border>
</DataTemplate>
</data:DataGrid.Resources>
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="User Name" Width="200"
Binding="{Binding User}" />
<data:DataGridTemplateColumn Header="User Role" Width="200"
CellTemplate="{StaticResource UserRoleTemplate}" />
<!--<data:DataGridTextColumn Header="Assigned Issues" />-->
</data:DataGrid.Columns>
</data:DataGrid>
The combo is filled using a collection from a class that has all the users roles:here's the xaml:
<UserControl.Resources>
<local:RolesTypes x:Key="rList">
</local:RolesTypes>
</UserControl:Resources>
And here's the class that has the collection:
public class RolesTypes
{
public List<string> GetListOfRoles
{
get
{
List<string> RolesList = new List<string>();
RolesList.Add("administrator");
RolesList.Add("reviewer");
return RolesList;
}
}
}
My problem is:
The combo fills fine with the list of roles, but when I receive the usersinfo collection, I want each user to have its role selected in its matching combo and it doesn't happen. no role is selected in the combo, although the users roles DO exist in the list of roles .
Any ideas?

CAVEAT: this populates the grid with a combobox and sets the combobox to the users' role. It's done in code behind, which I think violates all the MVVM principals, but I couldn't get binding to work. (Maybe some binding expert could modify this) That said, if you go with it you should probably attach a handler to the combobox to update you're user's role when the combo box is changed. Hope this helps and good luck!
REVISED cmbUserRoleTypes_Loaded to populate combo box, and removed converter code. Note that the different role values are hard coded, you probably want to make that generic.
REVISED to include combo box, sorry was rushing to finish before I had to leave and didn't re-read your post. I don't really like that it has to set the combo box in code-behind, it seems like there should be some way to data bind it. NOTE: I'm having trouble with binding the combo box selection to the user record, but at least this gets the populated combo boxes in there. Hope it helps.
Here is the xaml
<UserControl xmlns:data="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="StackOverflowProblems.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StackOverflowProblems"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Populate users" Click="btn_Click" HorizontalAlignment="Left"></Button>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<data:Label Content="Roles:"/>
<ComboBox VerticalAlignment="Center" Name="myComboBox" ></ComboBox>
</StackPanel>
<Grid Grid.Row="2" x:Name="LayoutRoot" Background="White">
<data:DataGrid Margin="5,25,5,17" AutoGenerateColumns="False" AllowDrop="True" Name="dataGrid1" SelectionMode="Single" UseLayoutRounding="True" SelectionChanged="dataGrid1_SelectionChanged" Grid.RowSpan="2" Grid.ColumnSpan="2" Grid.Row="1" ItemsSource="{Binding}" >
<data:DataGrid.Resources>
<DataTemplate x:Key="UserRoleTemplate">
<Border BorderThickness="0,0,0,0" BorderBrush="#6FBDE8">
<ComboBox VerticalAlignment="Center" Loaded="cmbUserRoleTypes_Loaded" >
</ComboBox>
</Border>
</DataTemplate>
<DataTemplate x:Key="UserNameTemplate">
<Border BorderThickness="0,0,0,0" BorderBrush="#6FBDE8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Name="txtUserName" VerticalAlignment="Center" Grid.Column="0" Loaded="cmbUserRoleTypes_Loaded" Text="{Binding Path=Name}" ></TextBlock>
</Grid>
</Border>
</DataTemplate>
</data:DataGrid.Resources>
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="User Name" Width="200" Binding="{Binding User}" />
<data:DataGridTemplateColumn Header="User Role" Width="200" CellTemplate="{StaticResource UserRoleTemplate}" />
</data:DataGrid.Columns>
</data:DataGrid>
</Grid>
</Grid>
</UserControl>
Here is the code behind
using System;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
namespace StackOverflowProblems
{
public partial class MainPage : UserControl
{
ObservableCollection<UserInfo> users = new ObservableCollection<UserInfo>();
ObservableCollection<string> roles = new ObservableCollection<string>();
public MainPage()
{
InitializeComponent();
LayoutRoot.DataContext = this;
InitializeRoles();
}
public void InitializeRoles()
{
// turn enumeration into a collection of strings
Type enumType = typeof(UserRole);
foreach (FieldInfo fieldInfo in enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
{
roles.Add(fieldInfo.Name.ToString());
}
myComboBox.ItemsSource = roles;
myComboBox.SelectedIndex = 0;
}
public void svc_GetAllUsersCompleted()
{
users.Add(new UserInfo("Fred", UserRole.Administrator));
users.Add(new UserInfo("George", UserRole.None));
users.Add(new UserInfo("Mary", UserRole.Reviewer));
dataGrid1.ItemsSource = users;
}
private void dataGrid1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
private void cmbUserRoleTypes_Loaded(object sender, RoutedEventArgs e)
{
ComboBox bx = (ComboBox)sender;
UserInfo ui = (UserInfo)bx.Tag;
bx.ItemsSource = roles;
int userRoleIndex = 0;
switch (ui.Role)
{
case UserRole.None:
userRoleIndex = 0;
break;
case UserRole.Administrator:
userRoleIndex = 1;
break;
case UserRole.Reviewer:
userRoleIndex = 2;
break;
default:
throw new Exception("Invalid Role Detected");
}
bx.SelectedIndex = userRoleIndex;
}
private void btn_Click(object sender, RoutedEventArgs e)
{
svc_GetAllUsersCompleted();
}
}
}
Here is the supporting class file
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
namespace StackOverflowProblems
{
public class UserInfo : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private string _User = "";
public string User
{
get { return _User; }
set
{
if (_User != value)
{
_User = value;
NotifyPropertyChanged("User");
}
}
}
private UserRole _Role = UserRole.None;
public UserRole Role
{
get { return _Role; }
set
{
if (_Role != value)
{
_Role = value;
NotifyPropertyChanged("User");
}
}
}
public UserInfo(string user, UserRole role)
{
User = user;
Role = role;
}
}
public enum UserRole
{
None = 0,
Administrator = 1,
Reviewer = 2,
}
}

It is also very possible for your SelectedValue to receive it's value before the ItemsSource gets its value for GetListOfRoles.
If I remember right, that can cause issues with the 'value' being set to an item that does not exist in the ItemsSource yet.

Related

WPF EventHandler for TextBox.TextChanged in XAML or code behind?

Description
In WPF, using MvvmLight, I have a viewModel with an integer property SelectedIndex. Changing the value of this property is an expensive operation, so I only want to update the property if the operator is fairly certain that he finished typing.
I have a TextBox and a button. The operator types a number, and presses the button. This should lead to a command that updates the property.
Standard WPF MvvmLight solution for this
class MyViewModel
{
private int selectedIndex;
public MyViewModel()
{
this.CommandSelectIndex = new RelayCommand(ExecuteSelectIndex, CanSelectIndex);
}
public public RelayCommand<int> CommandSelectIndex { get; }
public int SelectedIndex
{
get => this.selectedIndex;
set => base.Set(nameof(SelectedIndex), ref this.selectedIndex, value);
}
private bool CanSelectIndex(int proposedIndex)
{
return proposedIndex > 0 && proposedIndex < MyData.Count;
}
private void ExecuteSelectIndex(int proposedIndex)
{
this.SelectedIndex = proposedIndex;
ProcessSelectedIndex(proposedIndex); // Expensive!
}
}
For those who know MvvmLight, this is fairly straightforward.
So while the operator is typing a number, I only want to update the button. I don't want to do anything with the intermediate values:
1 --> 12 --> 123 --> (typing error, backspace) --> 124 [press button]
XAML
<StackPanel Name="Test1" Orientation="Horizontal">
<TextBox Name="ProposedValue1" Text="1234" Width="300" Height="20"/>
<Button x:Name="ButtonChangeText1" Content="Change"
Height="30" Width="74" Padding="5,2"
Command="{Binding Path=CommandSelectedIndex}"
CommandParameter="{Binding ElementName=ProposedValue1, Path=Text}"/>
</StackPanel>
This works partly: at startup CanSelectIndex(1234) is called; If the button is pressed ExecuteSelectedIndex(1234) is called.
Problem
However, if the text of the TextBox changes, CanSelectIndex is not called.
The reason is because event ICommand.CanExecuteChanged is not raised when the textbox changes.
Solution:
Add an event handler:
XAML:
<TextBox Name="ProposedValue1" Text="1234" Width="300" Height="20"
TextChanged="textChangedEventHandler"/>
Code behind:
private void textChangedEventHandler(object sender, TextChangedEventArgs args)
{
((MyViewModel)this.DataContext).CommandSelectedIndex.RaiseCanExecuteChanged();
}
I always feel a bit uneasy whenever I have to write code behind. Is it standard to write eventhandlers in code behind, or is that a simplification that I only see in tutorials.
Is there a method that I can do this in XAML? Something with Binding?
TextChanged="TextChanged="{Binding Path=CommandSelectIndex ??? RaiseCanExecuteChanged() }
The RelayCommand class in MvvmLight has two implementations.
In the GalaSoft.MvvmLight.Command namespace and in the GalaSoft.MvvmLight.CommandWpf namespace.
You've probably used from namespace GalaSoft.MvvmLight.Command.
And this type doesn't actually update the state of the command.
If used from the GalaSoft.MvvmLight.CommandWpf namespace, then the state of the command is updated according to the predetermined logic.
Is there a method that I can do this in XAML? Something with Binding?
Just bind the Text property of the TextBox to a string source property of the view model and raise call the RaiseCanExecuteChanged method of the command from the setter of this one.
If you really want to handle an actual event for some reason, you should look into interaction triggers.
#Harald Coppulse, you are absolutely right!
Here is my test code for MvvmLight.
ViewModel:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
namespace InvalidateCommandMvvmLight.ViewModel
{
public class MyViewModel : ViewModelBase
{
private string _text;
private int _number;
public string Text { get => _text; private set => Set(ref _text, value); }
public int Number { get => _number; set => Set(ref _number, value); }
public RelayCommand<string> CommandTest { get; }
public RelayCommand<int> CommandNumber { get; }
public MyViewModel()
{
CommandTest = new RelayCommand<string>(Test, CanTest);
CommandNumber = new RelayCommand<int>(IntTest, CanIntTest);
}
private bool CanTest(string text)
{
// the text must have a minimum length of 4
// and be different from the current one
return text != null && text.Length >= 4 && text != Text;
}
private void Test(string text)
{
Text = text;
}
private bool CanIntTest(int num)
{
// The "num" parameter must be positive, less than 100
// and is not equal to the Number property
return num > 0 && num <100 && num != Number;
}
private void IntTest(int num)
{
Number = num;
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="55" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding ElementName=tbNumber, Path=Text}"/>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
</Grid>
</Window>
Unfortunately, the CommandsWpf.RelayCommand class in MvvmLight is implemented not correctly.
It does not take into account the peculiarities of working with values ​​of different types in WPF.
To work in a typical for WPF way, an implementation should have something like this:
using System.ComponentModel;
namespace Common
{
#region Delegates for WPF Command Methods
/// <summary>Delegate of the executive team method.</summary>
/// <param name="parameter">Command parameter.</param>
public delegate void ExecuteHandler<T>(T parameter);
/// <summary>Command сan execute method delegate.</summary>
/// <param name="parameter">Command parameter.</param>
/// <returns><see langword="true"/> if command execution is allowed.</returns>
public delegate bool CanExecuteHandler<T>(T parameter);
#endregion
/// <summary>Class for typed parameter commands.</summary>
public class RelayCommand<T> : RelayCommand
{
/// <summary>Command constructor.</summary>
/// <param name="execute">Executable command method.</param>
/// <param name="canExecute">Method allowing command execution.</param>
public RelayCommand(ExecuteHandler<T> execute, CanExecuteHandler<T> canExecute = null)
: base
(
p => execute(TypeDescriptor.GetConverter(typeof(T)).IsValid(p) ? (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p) : default),
p => (canExecute == null) || (TypeDescriptor.GetConverter(typeof(T)).IsValid(p) && canExecute((T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p)))
)
{}
}
}
Unless you have the ability to change the RelayCommand implementation, you need to somehow use Binding's ability to auto-convert values.
One variant.
Create a property of the desired type in the ViewModel and use it as a proxy for autoconversion.
But if a non-numeric value is entered, then the command will not be able to define it.
You also need to check Validation.HasError.
ViewModel:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
namespace InvalidateCommandMvvmLight.ViewModel
{
public class MyViewModel : ViewModelBase
{
private string _text;
private int _number;
private int _numberView;
public string Text { get => _text; private set => Set(ref _text, value); }
public int Number { get => _number; set => Set(ref _number, value); }
public int NumberView { get => _numberView; set => Set(ref _numberView, value); }
public RelayCommand<string> CommandTest { get; }
public RelayCommand<int> CommandNumber { get; }
public MyViewModel()
{
CommandTest = new RelayCommand<string>(Test, CanTest);
CommandNumber = new RelayCommand<int>(IntTest, CanIntTest);
}
private bool CanTest(string text)
{
// the text must have a minimum length of 4
// and be different from the current one
return text != null && text.Length >= 4 && text != Text;
}
private void Test(string text)
{
Text = text;
}
private bool CanIntTest(int num)
{
// The "num" parameter must be positive, less than 100
// and is not equal to the Number property
return num > 0 && num <100 && num != Number;
}
private void IntTest(int num)
{
Number = num;
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel NumberView="55"/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="{Binding NumberView, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding NumberView}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=tbNumber}"
Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
</Grid>
</Window>
Second variant.
Create an explicit proxy converter.
Converter:
using System;
using System.ComponentModel;
using System.Windows;
namespace InvalidateCommandMvvmLight
{
public class ProxyBinding : Freezable
{
public Type Type
{
get { return (Type)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
// Using a DependencyProperty as the backing store for Type. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TypeProperty =
DependencyProperty.Register(nameof(Type), typeof(Type), typeof(ProxyBinding), new PropertyMetadata(typeof(object), ChangedValueOrType));
private static void ChangedValueOrType(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ProxyBinding proxy = (ProxyBinding)d;
if (proxy.Type == null)
{
proxy.Value = null;
return;
}
if (proxy.Source == null)
return;
if (proxy.Type == proxy.Source.GetType())
return;
if (TypeDescriptor.GetConverter(proxy.Type).IsValid(proxy.Source))
proxy.Value = TypeDescriptor.GetConverter(proxy.Type).ConvertFrom(proxy.Source);
else
proxy.Value = null;
}
public object Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(nameof(Source), typeof(object), typeof(ProxyBinding), new PropertyMetadata(null, ChangedValueOrType));
public object Value
{
get { return GetValue(ValueProperty); }
protected set { SetValue(ValuePropertyKey, value); }
}
// Using a DependencyProperty as the backing store for readonly Value. This enables animation, styling, binding, etc...
protected static readonly DependencyPropertyKey ValuePropertyKey =
DependencyProperty.RegisterReadOnly(nameof(Value), typeof(object), typeof(ProxyBinding), new PropertyMetadata(null));
public static readonly DependencyProperty ValueProperty = ValuePropertyKey.DependencyProperty;
protected override Freezable CreateInstanceCore()
{
return new ProxyBinding();
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Window.Resources>
<local:ProxyBinding x:Key="ProxyInt"
Type="{x:Type sys:Int32}"
Source="{Binding ElementName=tbNumber, Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="55" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding Value, Source={StaticResource ProxyInt}}">
</Button>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Text="{Binding Value,Source={StaticResource proxy}}"/>
</Grid>
</Window>
Another variant.
Create converter for bindings:
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
namespace InvalidateCommandMvvmLight
{
public class ValueTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is Type type && TypeDescriptor.GetConverter(type).IsValid(value))
return TypeDescriptor.GetConverter(type).ConvertFrom(value);
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Window.Resources>
<local:ValueTypeConverter x:Key="ValueTypeConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="55" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding Text, Converter={StaticResource ValueTypeConverter}, ConverterParameter={x:Type sys:Int32}, ElementName=tbNumber}">
</Button>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
</Grid>
</Window>

How I can print listbox item count along with Expander header?

I want to print listbox item count alog with header of expander like
header1 (count) . How can I achieve this in WPF
You haven't provided any code for what you have done, such as if you are using a ViewModel or doing code-behind. There are multiple ways to go about it.
My ViewModel Way
In this example, I've made a ViewModel containing an ObservableCollection of string to populate the ListBox. The Expander header is bound to a property that is populated using a combination of the HeaderText and ItemCount.
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _headerText = string.Empty;
private string _headerTextFull = string.Empty;
private ObservableCollection<string> _listItems = new ObservableCollection<string>();
private int _itemCount = 0;
public ViewModel() { }
public string HeaderText
{
get { return _headerText; }
set
{
_headerText = value;
NotifyPropertyChanged("HeaderText");
UpdateHeader();
}
}
public string HeaderTextFull
{
get { return _headerTextFull; }
set
{
_headerTextFull = value;
NotifyPropertyChanged("HeaderTextFull");
}
}
public ObservableCollection<string> ListItems
{
get { return _listItems; }
set
{
_listItems = value;
NotifyPropertyChanged("ListItems");
ItemCount = (_listItems != null ? _listItems.Count : 0);
}
}
public int ItemCount
{
get { return _itemCount; }
set
{
_itemCount = value;
NotifyPropertyChanged("ItemCount");
UpdateHeader();
}
}
private void UpdateHeader()
{
HeaderTextFull = String.Format("{0} ({1})", _headerText, _itemCount);
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The XAML for the Window:
<Window x:Class="SO37192142.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Expander Grid.Row="0" Name="expander1" Header="{Binding Path=HeaderTextFull, FallbackValue='Items'}" IsExpanded="True">
<Grid>
<ListBox Name="listBox1" Width="Auto" Height="Auto" ItemsSource="{Binding Path=ListItems}" />
</Grid>
</Expander>
<Button Grid.Row="1" Content="Add an Item" Click="Button_Click" />
</Grid>
</Window>
The code-behind:
public partial class Window1 : Window
{
ViewModel myModel = new ViewModel();
public Window1()
{
InitializeComponent();
myModel.ListItems.CollectionChanged += new NotifyCollectionChangedEventHandler(ListItems_CollectionChanged);
myModel.HeaderText = "Items";
myModel.ListItems.Add("Item 1");
myModel.ListItems.Add("Item 2");
this.DataContext = myModel;
}
void ListItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
myModel.ItemCount = myModel.ListItems.Count;
}
void Button_Click(object sender, RoutedEventArgs e)
{
myModel.ListItems.Add("Another item");
}
}
When it starts, the Expander header will say "Items (2)". Every time the button is clicked, the header will update to show the new count.
Slight Variation of Above
Here's an example that provides the above example, but also adds a second list to demonstrate a different way. Notice the Expander.Header section.
<Window x:Class="SO37192142.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Expander Grid.Row="0" Grid.Column="0" Name="expander1" Header="{Binding Path=HeaderTextFull, FallbackValue='Items'}" IsExpanded="True">
<Grid>
<ListBox Name="listBox1" Width="Auto" Height="Auto" ItemsSource="{Binding Path=ListItems}" />
</Grid>
</Expander>
<!-- SECOND EXPANDER THAT DOESN'T RELY ON A VIEWMODEL -->
<Expander Grid.Row="0" Grid.Column="1" Name="expander2" IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ElementName=listBox2, Path=Items.Count, UpdateSourceTrigger=PropertyChanged, StringFormat={}Items ({0})}" />
</StackPanel>
</Expander.Header>
<Grid>
<ListBox Name="listBox2" Width="Auto" Height="Auto" ItemsSource="{Binding Path=ListItems}" />
</Grid>
</Expander>
<Button Grid.Row="1" Grid.ColumnSpan="2" Content="Add an Item" Click="Button_Click" />
</Grid>
</Window>
Only Code Behind
If, for some reason, you are just using code-behind, you can do it this way:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
((INotifyCollectionChanged)listBox1.Items).CollectionChanged += new NotifyCollectionChangedEventHandler(ListBox_CollectionChanged);
}
void ListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
expander1.Header = "Items (" + listBox1.Items.Count + ")";
}
}

Trouble with DataBinding to a ListBox in WP7 which is not displaying the bound content

I have a DataBinding on a ListBox, bound to an ObservableCollection. Debugging at runtime shows the ObservableCollection does have items in it, and they're not null. My code all looks fine, however for some reason nothing is being displayed in my ListBox. It definitely was working previously, however it no longer is - and I can't figure out why. I've examined previous versions of the code and found no differences that would have any effect on this - minor things like Width="Auto" etc.
I based my code off of the example found here:
http://msdn.microsoft.com/en-us/library/hh202876.aspx
So, my code:
XAML:
<phone:PhoneApplicationPage
x:Class="MyNamespace.MyItemsListPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
shell:SystemTray.IsVisible="True">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="PageTitle" Text="MyPageTitle" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<!-- Bind the list box to the observable collection. -->
<ListBox x:Name="myItemsListBox" ItemsSource="{Binding MyItemsList}" Margin="12, 0, 12, 0" Width="440">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Width="440">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Text="{Binding MyItemNumber}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Grid.Column="0"
VerticalAlignment="Center"
Margin="0,10"
Tap="TextBlock_Tap"/>
<TextBlock
Text="{Binding MyItemName}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Grid.Column="1"
VerticalAlignment="Center"
Margin="0,10"
Tap="TextBlock_Tap" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
C#:
namespace MyNamespace
{
public partial class MyItemsListPage : PhoneApplicationPage, INotifyPropertyChanged
{
private static ObservableCollection<MyItem> _myItemsList;
private ObservableCollection<MyItem> MyItemsList
{
get
{
return _myItemsList;
}
set
{
if (_myItemsList!= value)
{
_myItemsList= value;
NotifyPropertyChanged("MyItemsList");
}
}
}
public MyItemsListPage ()
{
InitializeComponent();
this.DataContext = this;
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
HelperClass helper = new HelperClass();
MyItemsList = helper.GetItems(this.NavigationContext.QueryString["query"]);
base.OnNavigatedTo(e); // Breakpoint here shows "MyItemsList" has MyItem objects in it.
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify Silverlight that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
The Helper class is a connector to my read-only local database on the device. It returns an ObservableCollection<MyItem>:
public ObservableCollection<MyItem> GetItems(string itemName)
{
// Input validation etc.
// Selecting all items for testing
var itemsInDB =
from MyItem item in db.Items
select item;
return new ObservableCollection<MyItem>(itemsInDB);
}
And finally the MyItem class:
[Table]
public class MyItem: INotifyPropertyChanged, INotifyPropertyChanging
{
private int _myItemId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int MyItemId
{
...
}
private string _myItemName;
[Column(CanBeNull = false)]
public string MyItemName
{
get
{
return _myItemName;
}
set
{
if (_myItemName!= value)
{
NotifyPropertyChanging("MyItemName");
_myItemName= value;
NotifyPropertyChanged("MyItemName");
}
}
}
private int _myItemNumber;
[Column]
public int MyItemNumber
{
get
{
return _myItemNumber;
}
set
{
if (_myItemNumber!= value)
{
NotifyPropertyChanging("MyItemNumber");
_myItemNumber= value;
NotifyPropertyChanged("MyItemNumber");
}
}
}
// Other properties, NotifyPropertyChanged method, etc...
}
This is rather frustrating as my DataBinding elsewhere in the application is working perfectly, so I've no idea why I can't get this to work.
The problem was that my ObservableCollection was private. Changing it to have a public access modifier allowed my ListBox to display the contents:
public ObservableCollection<MyItem> MyItemsList
Simply that you're binding to properties named incorrectly:
Text="{Binding ItemName}" should be Text="{Binding MyItemName}"
Notice you left out "My"

Observablecollection<T> in Listbox is not updating

I have a Silverlight Listbox bound to Observablecollection which shows up fine (first time) but when I try to update it through the code behind, the change does not reflect in UI. I have used MVVM pattern . Pl have a look and at view and viewmodel.
<UserControl x:Class="GridOperations.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GridOperations.ViewModel"
mc:Ignorable="d"
d:DesignHeight="700" d:DesignWidth="700">
<UserControl.DataContext>
<local:ShowUserListViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<local:ShowUserListViewModel x:Key="viewModelKey"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ListBox VerticalAlignment="Top" Margin="0,20,0,0" x:Name="lstBox" ItemsSource="{Binding UserList}" Width="700" Height="150" Background="AliceBlue">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="30,0,0,0" Text="{Binding UserId}" HorizontalAlignment="Left"/>
<TextBlock Margin="30,0,0,0" Text="{Binding Path=UserName, Mode=TwoWay}" Grid.Column="1" HorizontalAlignment="Left"/>
<Button Margin="30,0,0,0" Grid.Column="2" Content="Edit" Height="20" HorizontalAlignment="Left" Command="{Binding Source={StaticResource viewModelKey}, Path=UpdateUserCommand}" />
<Button Margin="10,0,0,0" Grid.Column="3" Content="Del" Height="20" HorizontalAlignment="Left" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
public class ShowUserListViewModel : INotifyPropertyChanged
{
public ShowUserListViewModel()
{
mUserList = new ObservableCollection<User>();
mUserList.Add(new User() { UserId = Guid.NewGuid().ToString(), UserName = "testuser1", IsAdmin = true });
mUserList.Add(new User() { UserId = Guid.NewGuid().ToString(), UserName = "testuser2", IsAdmin = true });
this.UpdateUserCommand = new DelegateCommand(this.UpdateUser);
}
public ICommand UpdateUserCommand { get; private set; }
private ObservableCollection<User> mUserList;
public ObservableCollection<User> UserList
{
get
{
return mUserList;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void UpdateUser()
{
this.UserList.Add(new User() { UserId = Guid.NewGuid().ToString(), UserName = "test", IsAdmin = false });
}
}
Firstly, you should avoid putting a collection property writable. IE, replace this code :
private ObservableCollection<User> mUsers = new ObservableCollection<User>();
public ObservableCollection<User> Users
{
get
{
return mUsers;
}
set
{
mUsers = value;
RaisePropertyChangedEvent("Users");
}
}
by this code :
private ObservableCollection<User> mUsers = new ObservableCollection<User>();
public ObservableCollection<User> Users
{
get
{
return mUsers;
}
}
Then, when you add an item to the collection, you don't have to tell the collection has changed (the collection property hasn't changed in itself), but it's the responsibility of the collection itself (via INotifyCollectionChanged) :
So, the "onclick" method can be simply like this:
private void OnClick(User userInstanceToEdit)
{
this.Users.Add(new User() { UserId = Guid.NewGuid().ToString(), UserName = "test", IsAdmin = false });
}
Finally, I would replace the ListBox.ItemsSource declaration with this one, as two ways is not required :
<ListBox x:Name="lstBox" ItemsSource="{Binding Users}"
Got a hint from this post Stackoverflow
Basically I need to use Datacontext with Listbox control (even after setting the Datacontext at UserControl level). The only line that required change is:
<ListBox x:Name="lstBox" ItemsSource="{Binding Users}" DataContext="{StaticResource viewModelKey}" Width="750" Background="AliceBlue">

GroupStyle sum not updated when multiple items

I have successfully applied the trick explained here. But I still have one problem.
Quick recap : I display users in a ListView. Users are regrouped by Country, and in the GroupStyle DataTemplate I display the sum of all group related Users.Total, using a Converter. But UI users can change the "Total" property value of Users through a modal window.
When there is only one item in the Group, both the User Total displayed and the sum are properly updated. But when there are multiple items in the group, only the User Total is updated (through binding) but the Converter that's supposed to make the sum (TotalSumConverter) is not even called!
Do you have any idea where it could come from? Should I use some kind of a trigger to make sure the Converter is called when there is a modification in the items?
The problem is that the value converter that calculates the sum for all the items in a group don't run when an item is changed, since there's no notification for changed items. One solution is to bind to something else that you can control how it does notifications and notify the group header when needed.
Below is a working example. You can change the count for a user in the text box and totals gets recalculated.
XAML:
<Window x:Class="UserTotalTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:userTotalTest="clr-namespace:UserTotalTest"
Title="Window1" Height="300" Width="300"
Name="this">
<Window.Resources>
<userTotalTest:SumConverter x:Key="SumConverter" />
<CollectionViewSource Source="{Binding Path=Users}" x:Key="cvs">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Country"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListView
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
ItemsSource="{Binding Source={StaticResource cvs}}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="Count" DisplayMemberBinding="{Binding Path=Count}" />
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel Margin="10">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
<ItemsPresenter />
<TextBlock FontWeight="Bold">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource SumConverter}">
<MultiBinding.Bindings>
<Binding Path="DataContext.Users" ElementName="this" />
<Binding Path="Name" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<ComboBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
ItemsSource="{Binding Path=Users}"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=SelectedUser}" />
<TextBlock Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Path=SelectedUser.Country}" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding Path=SelectedUser.Count}" />
</Grid>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace UserTotalTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new UsersVM();
}
}
public class UsersVM : INotifyPropertyChanged
{
public UsersVM()
{
Users = new List<User>();
Countries = new string[] { "Sweden", "Norway", "Denmark" };
Random random = new Random();
for (int i = 0; i < 25; i++)
{
Users.Add(new User(string.Format("User{0}", i), Countries[random.Next(3)], random.Next(1000)));
}
foreach (User user in Users)
{
user.PropertyChanged += OnUserPropertyChanged;
}
SelectedUser = Users.First();
}
private void OnUserPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Count")
{
PropertyChanged(this, new PropertyChangedEventArgs("Users"));
}
}
public List<User> Users { get; private set; }
private User _selectedUser;
public User SelectedUser
{
get { return _selectedUser; }
set
{
_selectedUser = value; if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedUser"));
}
}
}
public string[] Countries { get; private set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class User : INotifyPropertyChanged
{
public User(string name, string country, double total)
{
Name = name;
Country = country;
Count = total;
}
public string Name { get; private set; }
private string _country;
public string Country
{
get { return _country; }
set
{
_country = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Country"));
}
}
}
private double _count;
public double Count
{
get { return _count; }
set
{
_count = value; if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class SumConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
IEnumerable<User> users = values[0] as IEnumerable<User>;
string country = values[1] as string;
double sum = users.Cast<User>().Where(u =>u.Country == country).Sum(u => u.Count);
return "Count: " + sum;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The trick you are using databinds the group footer to ListView.Items which will not update your view automatically, like a DependencyObject does for example. Instead force a refresh after each update to Total like this:
CollectionViewSource viewSource = FindResource("ViewSource") as CollectionViewSource;
viewSource.View.Refresh();
The problem with refreshing the view is that it completely refreshes it, and I have expanders for my grouping, that will expand back to their original state, even if the user closed them. So yes it's a possible workaround, but it's not completely satisfying.
Also your DependencyObject explanation is interesting, but then, why is it working when I have only one item in my group?

Resources