I have an ObservableCollection, which is a DataContext for a Grid. A grid is a user control inserted in the main window.
I would like to map "Record x of y" to the StatusBar, so as a first step, I'm trying to map it to the Grid using this XAML:
<TextBlock Grid.Row="2" Grid.Column="3"> <TextBlock Text="{Binding CurrentPosition}" /> <TextBlock Text="{Binding Count}" /> </TextBlock>
The graph works without problems and is automatically updated as new elements are added. CurrentPosition, which is defined in my code below, always remains at 0.
How can I get CurrentPosition to automatically update? I hope not to use INotify ** because it is already an ObservableCollection.
I also have no code, so I hope it is reached in my class (or model) and XAML.
I tried working with CurrentChanged, but to no avail:
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 am adding the following code as a possible solution, but I do not really like it, and I hope that it will be better with it. This does not require me to use INotifyPropertyChanged - I suspect that I will finish repeating all the functions that should already be available with the ObservableCollection. (I also do not know why I need to re-notify me of a change in Count.)
Update 2 : the following is not a (complete) solution, as it interferes with other collection behavior (notifications), but I saved 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 "Element 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, as I can reference 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 CurrentPosition property masks a property with the same name that is already available directly from the DataContext, because the binding refers to the default view of the collection (which has this property).
The solution is to rename to MyCurrentPosition and refer to the original property from the StatusBar or, as I did, delete my version of this property (and GetDefaultView ) at all: they do nothing particularly useful.
Then I use the following simple ValueConverter to convert 0,1,2, .. to 1,2,3, .. into a 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>