I have an ObservableCollection which is the DataContext for a Grid. The Grid is a User Control inserted into the main Window.
I would like to display 'Record x of y' in the StatusBar so, as a first step, I am attempting to display it in the Grid using this XAML:
<TextBlock Grid.Row="2" Grid.Column="3">
<TextBlock Text="{Binding CurrentPosition}" /> <TextBlock Text="{Binding Count}" />
</TextBlock>
The Count works without issue, and updates automatically as new items are added. The CurrentPosition, which is defined in my code below, stays at 0 constantly.
How can I cause the CurrentPosition to update automatically? I am hoping not to have to use INotify** because this is already an ObservableCollection.
I also do not have any code-behind, so I hope it is achievable within my class (or model) and the XAML.
I did attempt to work with CurrentChanged but without success:
public MyObservableCollection() : base() {
this.GetDefaultView().CurrentChanged += MyObservableCollection_CurrentChanged;
}
MyObservableCollection:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace ToDoApplication.Models {
public class MyObservableCollection<T> : ObservableCollection<T> {
public MyObservableCollection() : base() {
}
public MyObservableCollection(List<T> list) : base(list) {
}
public MyObservableCollection(IEnumerable<T> collection) : base(collection) {
}
private System.ComponentModel.ICollectionView GetDefaultView() {
return System.Windows.Data.CollectionViewSource.GetDefaultView(this);
}
public int CurrentPosition {
get {
return this.GetDefaultView().CurrentPosition;
}
}
public void MoveFirst() {
this.GetDefaultView().MoveCurrentToFirst();
}
public void MovePrevious() {
this.GetDefaultView().MoveCurrentToPrevious();
}
public void MoveNext() {
this.GetDefaultView().MoveCurrentToNext();
}
public void MoveLast() {
this.GetDefaultView().MoveCurrentToLast();
}
public bool CanMoveBack() {
return this.CurrentPosition > 0;
}
public bool CanMoveForward() {
return (this.Count > 0) && (this.CurrentPosition < this.Count - 1);
}
}
public enum Navigation {
First, Previous, Next, Last, Add
}
}
Update: I'm adding the following code as a possible solution, but I don't really like it and I'm hoping a better one comes along that doesn't require me to use INotifyPropertyChanged - I suspect I'll end repeating all the functionality that should already be available with an ObservableCollection. (I also don't know why I need to re-notify of the Count changing.)
Update 2: The following is not a (full) solution as it interferes with other behaviours (notifications) of the collection, but I've kept it here in-case it contains any useful information.
namespace ToDoApplication.Models {
public class MyObservableCollection<T> : ObservableCollection<T>, INotifyPropertyChanged {
public new event PropertyChangedEventHandler PropertyChanged;
private int _currentPos = 1;
public MyObservableCollection() : base() {
this.GetDefaultView().CurrentChanged += MyObservableCollection_CurrentChanged;
this.CollectionChanged += MyObservableCollection_CollectionChanged;
}
public MyObservableCollection(List<T> list) : base(list) {
this.GetDefaultView().CurrentChanged += MyObservableCollection_CurrentChanged;
this.CollectionChanged += MyObservableCollection_CollectionChanged;
}
public MyObservableCollection(IEnumerable<T> collection) : base(collection) {
this.GetDefaultView().CurrentChanged += MyObservableCollection_CurrentChanged;
this.CollectionChanged += MyObservableCollection_CollectionChanged;
}
void MyObservableCollection_CurrentChanged(object sender, EventArgs e) {
this.CurrentPosition = this.GetDefaultView().CurrentPosition;
}
void MyObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
RaisePropertyChanged("Count");
}
private System.ComponentModel.ICollectionView GetDefaultView() {
return System.Windows.Data.CollectionViewSource.GetDefaultView(this);
}
public int CurrentPosition {
get {
return _currentPos;
}
private set {
if (_currentPos == value + 1) return;
_currentPos = value + 1;
RaisePropertyChanged("CurrentPosition");
}
}
private void RaisePropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void MoveFirst() {
this.GetDefaultView().MoveCurrentToFirst();
}
public void MovePrevious() {
this.GetDefaultView().MoveCurrentToPrevious();
}
public void MoveNext() {
this.GetDefaultView().MoveCurrentToNext();
}
public void MoveLast() {
this.GetDefaultView().MoveCurrentToLast();
}
public bool CanMoveBack() {
return this.CurrentPosition > 1;
}
public bool CanMoveForward() {
return (this.Count > 0) && (this.CurrentPosition < this.Count);
}
}
public enum Navigation {
First, Previous, Next, Last, Add
}
}
With this I can display "Item 1 of 3" in the Grid:
<TextBlock Grid.Row="2" Grid.Column="3" x:Name="txtItemOf">Item
<TextBlock x:Name="txtItem" Text="{Binding CurrentPosition}" /> of
<TextBlock x:Name="txtOf" Text="{Binding Count}" />
</TextBlock>
I no longer need this TextBlock though, as I can refer to the DataContext properties directly in the (main) StatusBar
:
<StatusBar DockPanel.Dock="Bottom">
Item <TextBlock Text="{Binding ElementName=vwToDo, Path=DataContext.CurrentPosition}" />
Of <TextBlock Text="{Binding ElementName=vwToDo, Path=DataContext.Count}" />
</StatusBar>
PROBLEM AND SOLUTION
Following @JMarsch 's answer: Naming my property CurrentPosition
is masking the property of the same name that is already available directly from the DataContext, because the binding is to the collection's default view (which has this property).
The solution is either to rename to MyCurrentPosition
, and refer to the original property from the StatusBar or, as I did, to remove my version of this property (and of GetDefaultView
) altogether: they aren't doing anything particularly useful.
I then use the following simple ValueConverter to convert 0,1,2,.. to 1,2,3,.. in the StatusBar.
[ValueConversion(typeof(int), typeof(int))]
class PositionConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return (int)value + 1;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return (int)value - 1;
}
}
StatusBar:
<StatusBar DockPanel.Dock="Bottom" x:Name="status">
Item <TextBlock Text="{Binding ElementName=vwToDo,
Path=DataContext.CurrentPosition, Converter={StaticResource posConverter}}" />
Of <TextBlock Text="{Binding ElementName=vwToDo, Path=DataContext.Count}" />
</StatusBar>