Reputation: 3407
In the following binding,
<TextBlock Text="{Binding Path=., StringFormat=TotalPages:{0}, Converter={StaticResource totalPagesConverter}}"/>
the totalPagesConverter
takes ObservableCollection<MyFileInfo>
and returns the total number of pages.
This works only for the first time, but doesn't not update when a MyFileInfo
object's property is changed. Note that I did implement INotifyPropertyChanged
for MyFileInfo
, and things update properly when they are binded to a Property of MyFileInfo
. But apparently something is missing when binding to a Collection
of such objects. How to bind to a Collection so that it updates properly? Thanks!
UPDATE Thank you all! properties of MyFileInfo
and the converter are shown below:
MyFileInfo
class
public class MyFileInfo : INotifyPropertyChanged
{
private Boolean ifPrint;
private Boolean isValid;
public string Filename {
get; set;
}
public string Filepath {
get; set;
}
public int Copies {
get; set;
}
public int Pages {
get; set;
}
public Boolean IfPrint {
get{
return this.ifPrint;
}
set{
if (this.ifPrint != value){
this.ifPrint = value;
onPropertyChanged("IfPrint");
}
}
}
public Boolean IsValid {
get {
return this.isValid;
}
set {
if (this.isValid!= value) {
this.isValid = value;
onPropertyChanged("IsValid");
}
}
}
private void onPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Converter Class
public class TotalPagesConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
ObservableCollection<MyFileInfo> lst = (ObservableCollection<MyFileInfo>)value;
int t = 0;
foreach(MyFileInfo f in lst){
t += (f.IsValid && f.IfPrint) ? f.Pages : 0;
}
return t.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
A bit on the Converter: I have a CheckBox
for the user to choose files to be printed. Everytime the CheckBox
is toggled, the IfPrint
boolean property of an MyFileInfo
object is flipped. This Converter goes through all the IfPrint && IsValid
files to re-compute the total number of pages, and update the new total to-be-printed pages on GUI (ideally). This isn't that efficient, but the list is short (< 10). Any other idea is welcomed!
Upvotes: 1
Views: 2988
Reputation: 3234
If I understand correctly, you list the items in some sort of list/grid, and each item has a checkbox bound to the IfPrint property. When the user clicks the checkbox, you want the textblock to update and show the number of MyFileInfos which have the IfPrint and IsValid set to true, correct?
Bindings only listen to PropertyChanged events on the object which is its DataContext. This means that the only controls which will react to IfPrint change is the checkbox. The only way for any other control to react, is to manually communicate the change - the easiest way is to create an event:
public class MyFileInfo : INotifyPropertyChanged
{
private Boolean ifPrint;
private Boolean isValid;
...
public Boolean IfPrint {
get{
return this.ifPrint;
}
set{
if (this.ifPrint != value){
this.ifPrint = value;
onPropertyChanged("IfPrint");
OnCanPrintChanged();
}
}
}
public Boolean IsValid {
get {
return this.isValid;
}
set {
if (this.isValid!= value) {
this.isValid = value;
onPropertyChanged("IsValid");
OnCanPrintChanged();
}
}
}
private void onPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void OnCanPrintChanged()
{
if (CanPrintChanged != null)
{
CanPrintChanged(this, EventArgs.Empty);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler CanPrintChanged;
}
In your code behind class you hook up to this event for each MyFileInfo in the list, and update a property in your codebehind/viewmodel:
public class MyCodeBehind : INotifyPropertyChanged
{
private int _printablePageCount = 0;
public int PrintablePageCount
{
get { return _printablePageCount; }
set
{
return _printablePageCount = value;
OnPropertyChanged("PrintablePageCount");
}
}
private void OnCanPrintChanged(object sender, EventArgs arg)
{
int t = 0;
foreach(MyFileInfo f in lst)
{
if (f.IsValid && f.IfPrint)
{
t++;
}
}
PrintablePageCount = t;
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And in your XAML you bind to your PrintablePageCount instead (without the need for a converter):
<TextBlock Text="{Binding Path=PrintablePageCount, StringFormat=TotalPages:{0}}"/>
Upvotes: 1
Reputation: 132588
This works only for the first time, but doesn't not update when a MyFileInfo object's property is changed.
An ObservableCollection
will only raise a notification event when the collection itself changes, such as adding or removing an item.
If you want the collection to also fire an "I have changed" message whenever any of the items inside the collection changes, you'll have to hook up property changed handlers for that yourself.
public MyViewModel()
{
FileInfoCollection = new ObservableCollection<MyFileInfo>();
// Hook up initial changed handler. Could also be done in setter
FileInfoCollection.CollectionChanged += FileInfoCollection_CollectionChanged;
}
void FileInfoCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(MyFileInfo item in e.NewItems)
{
item.PropertyChanged += MyFileInfo_PropertyChanged;
}
}
if (e.OldItems != null)
{
foreach(MyFileInfo item in e.OldItems)
{
item.PropertyChanged -= MyFileInfo_PropertyChanged;
}
}
}
void MyFileInfo_PropertyChanged(object sender, PropertyChange e)
{
// Whenever a FileInfo changes, raise change notification for collection
RaisePropertyChanged("FileInfoCollection");
}
That said, I don't understand why a binding for the count of items in a collection needs to be re-evaluated whenever a property of an item inside the collection changes, unless you're doing some kind of filtering inside your converter.
Also, if you're just binding to the count of the collection, can't you just bind to the .Count
property on ObservableCollection<T>
?
<TextBlock Text="{Binding Path=Count, StringFormat=TotalPages:{0}}" />
Upvotes: 2
Reputation: 1327
Don't forget to raise OnPropertyChanged event while setting value of ObservableCollection property.
public ObservableCollection<MyFileInfo> FileInfos
{
get{return fileInfos;}
set
{
if(fileInfos != value)
{
fileInfos = value;
OnPropertyChanged("FileInfos");
}
}}
Add Mode= TwoWay to your binding.
<TextBlock Text="{Binding Path=FileInfos, Mode=TwoWay, StringFormat=TotalPages:{0}, Converter={StaticResource totalPagesConverter}}"/>
Upvotes: 0