StatusBar x of y entry

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> 
+7
c # wpf
source share
2 answers

Something very important to know about data binding to collections is that XAML is always bound to the collection view, not the collection itself. So, although your XAML seems to be attached to the collection, at runtime you really are attached to the collection view by default.

Here's a cool side effect: Collectionview already has a CurrentPosition property. I think something is breaking down for you because you are inadvertently interfering with your collection.

Below is a very quick and dirty little program that illustrates the working binding to CurrentPostion and Count, without even determining the current position in the collection (because under the covers you are really attached to the CollectionView, and it already has a CurrentPosition Property that notifies you of the change.

Run this program and note that when you click the increment button, the user interface is updated accordingly.

Here's the XAML:

 <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <StackPanel x:Name="ContentPanel"> <Button x:Name="IncrementButton" Content="Increment" Click="IncrementButton_Click"/> <StackPanel Orientation="Horizontal"> <TextBlock x:Name="CurrentPositionTextBlock" Text="{Binding CurrentPosition}"/> <TextBlock Text=" / "/> <TextBlock Text="{Binding Count}"/> </StackPanel> </StackPanel> </Window> 

Here is the code:

 using System.Collections.ObjectModel; using System.ComponentModel; using System.Windows; using System.Windows.Data; namespace WpfApplication1 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.Collection = new TestObservableCollection<object>() {new object(), new object(), new object()}; this.ContentPanel.DataContext = this.Collection; } public TestObservableCollection<object> Collection { get; set; } private void IncrementButton_Click(object sender, RoutedEventArgs e) { this.Collection.GetDefaultView().MoveCurrentToNext(); } } public class TestObservableCollection<T> : ObservableCollection<T> { public ICollectionView GetDefaultView() { return CollectionViewSource.GetDefaultView(this); } } } 

Read more for you here:

http://msdn.microsoft.com/en-us/library/ms752347 (v = vs .110) .aspx

http://msdn.microsoft.com/en-us/library/system.windows.data.collectionviewsource(v = vs .110) .aspx

EDIT

If you need a little more evidence of what is happening, paste the code below into my button click handler - you will see that the actual type of the object the text field is attached to is ListCollectionView, not the actual collection

 System.Diagnostics.Debug.WriteLine(this.CurrentPositionTextBlock.GetBindingExpression(TextBlock.TextProperty).ResolvedSource.GetType().FullName); 
+3
source share

Linking your copies is updated as the ObservableCollection<T> base raises the PropertyChanged when items are added and removed.

Your positioning code should raise PropertyChanged when you move the current record pointer so that the Binding subsystem knows that the property is required, but rather, why INotifyPropertyChanged exists. I would probably write it as shown below. Note the use of ObservableCollections<T> OnPropertyChanged to raise the correct event from the already implemented INotifyPropertyChanged in your inheritance tree.

  public void MoveFirst() { this.GetDefaultView().MoveCurrentToFirst(); OnPropertyChanged(new PropertyChangedEventArgs("CurrentPosition")); } public void MovePrevious() { this.GetDefaultView().MoveCurrentToPrevious(); OnPropertyChanged(new PropertyChangedEventArgs("CurrentPosition")); } public void MoveNext() { this.GetDefaultView().MoveCurrentToNext(); OnPropertyChanged(new PropertyChangedEventArgs("CurrentPosition")); } public void MoveLast() { this.GetDefaultView().MoveCurrentToLast(); OnPropertyChanged(new PropertyChangedEventArgs("CurrentPosition")); } 
+1
source share

All Articles