Reputation: 1641
In Listbox I have items with checkboxes and in the TextBlock I want to update the counter showing how many of them are currently checked. Simple scenario, but I have problems with refreshing the binding for the counter.
My xaml:
<Window x:Class="ListboxWithCounting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ListboxWithCounting"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MyViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="CheckBoxListItem">
<Border x:Name="CheckBoxItemElement"
Background="Transparent"
Padding="0,2,0,2">
<CheckBox Content="{Binding Name}"
IsChecked="{Binding Path=IsChecked,Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="1">
<TextBlock.Text>
<MultiBinding StringFormat="Items checked: {0} of {1}">
<Binding Path="ColumnsChecked" />
<Binding Path="ColumnsCount" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<ListBox ItemTemplate="{StaticResource CheckBoxListItem}"
x:Name="Columns"
ItemsSource="{Binding Columns}" />
</Grid>
</Window>
My view model and single Item:
public class Item : INotifyPropertyChanged
{
public string Name { set; get; }
private bool isChecked;
public bool IsChecked
{
get
{
return isChecked;
}
set
{
isChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
public class MyViewModel
{
public int ColumnsCount
{
get
{
return Columns.Count;
}
}
public int ColumnsChecked
{
get
{
return Columns.Where(x => x.IsChecked).Count();
}
}
public List<Item> Columns
{
get
{
var data = new List<Item>()
{
new Item(){ Name = "Item1", IsChecked=true },
new Item(){ Name = "Item2" },
new Item(){ Name = "Item3" }
};
data.ForEach(x => x.PropertyChanged += (s, e) => { });
return data;
}
}
}
How can I trigger the multibinding when each and every checkbox on the list state is changed?
[UPDATE]
Thanks to slugster, the working code:
public class Item : INotifyPropertyChanged
{
public string Name { set; get; }
private bool isChecked;
public bool IsChecked
{
get
{
return isChecked;
}
set
{
if (isChecked != value)
{
isChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
public class MyViewModel : INotifyPropertyChanged
{
public int ColumnsCount
{
get
{
return Columns.Count;
}
}
public int ColumnsChecked
{
get
{
return Columns.Where(x => x.IsChecked).Count();
}
}
private List<Item> columns;
public List<Item> Columns
{
get
{
if (columns == null)
{
columns = new List<Item>()
{
new Item(){ Name = "Item1", IsChecked=true },
new Item(){ Name = "Item2" },
new Item(){ Name = "Item3" }
};
}
columns.ForEach(x =>
{
x.PropertyChanged -= x_PropertyChanged;
x.PropertyChanged += x_PropertyChanged;
});
return columns;
}
}
void x_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("ColumnsChecked"));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Upvotes: 1
Views: 496
Reputation: 49974
Your viewmodel also needs to implement INotifyPropertyChanged
so that the binding can be made aware of the property change on the list items.
To be honest I can't tell you off the top of my head whether notifying on just one property will cause the whole multibinding to be evaluated (in any case you can quickly find out), however you probably want to notify on both properties in case of any other bindings.
public class MyViewModel : INotifyPropertyChanged
{
[...snip...]
public List<Item> Columns
{
get
{
var data = new List<Item>()
{
new Item(){ Name = "Item1", IsChecked=true },
new Item(){ Name = "Item2" },
new Item(){ Name = "Item3" }
};
data.ForEach(x => x.PropertyChanged += (s, e) => {
OnPropertyChanged("ColumnsCount");
OnPropertyChanged("ColumnsChecked");
});
return data;
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Upvotes: 2