With my blog post about the new data source feature in Visual Studio 2010 for WPF applications, Luciano asked about a BindingNavigator counterpart to Windows Forms.The BindingNavigator that is available with Windows Forms is not here with WPF, and thus the automatically created UI from the data source window in Visual Studio 2010 lacks some functionality compared to Windows Forms.
With Windows Forms, the BindingNavigator offers a toolbar display as shown in the figure to navigate within records. The BindingNavigator class is connected to a BindingSource class. The BindingNavigator is responsible for the UI and allows the user to navigate through records, add new or delete existing records, and to save changes.
The BindingSource class has a data source associated that allows navigating with methods MoveFirst, MoveNext, MovePrevious, MoveLast, and an indexer, allows adding (AddNew), removing (Remove, RemoveAt, RemoveCurrent), and filtering items.
The WPF equivalence to the BindingSource class is the CollectionViewSource class. CollectionViewSource is defined in the namespace System.Windows.Data. CollectionViewSource has a source and view connected that can be accessed via Source and View properties. A view implements the ICollectionView interface with methods for navigating (MoveCurrentTo, MoveCurrentToFirst, MoveCurrentToLast, MoveCurrentToNext….
For this blog post I’m creating a sample of a WPF BindingNavigator. Contrary to the Windows Forms BindingNavigator, the BindingNavigator here has no UI but otherwise offers the functionality of the Windows Forms BindingNavigator. Any UI, for example a ribbon control, can be used that fires commands where the BindingNavigator reacts.
With a BindingNavigator defined as a custom control it would also be possible to define a default UI that can be customized with a template. Maybe I’m showing this in a future blog post.
BindingNavigator adapts CollectionViewSource
The BindingNavigator class receives a data source collection in the constructor and adapts the WPF CollectionViewSource. A CollectionViewSource is instantiated in the constructor, and uses it on navigation.
public class BindingNavigator : INotifyPropertyChanged { private CollectionViewSource cvs; public BindingNavigator(IEnumerable dataSource) { cvs = new CollectionViewSource(); cvs.Source = dataSource; InitializeCommands(); }
To access all the elements from the collection that is managed by the CollectionViewSource, the View property of the BindingNavigtor allows direct access to the properties of the ICollectionView interface. This makes it possible the retrieve and change values from objects just as the BindingNavigator wouldn’t be in between.
public ICollectionView View { get { return cvs.View; } }
Custom Commands
Instead of defining Click event handlers, the sample uses commands. This makes it possible to define the commands independent of the UI. I’ve defined custom commands for this sample. All the commands used by the BindingNavigator are defined by the DataCommands class.
DataCommands defines all the commands that should be used with navigation of records.
Instead of defining custom commands, it would be possible to use the WPF predefined NavigationCommands and ApplicationCommands. For example, NavigationCommands.NextPage and NavigationCommands.PreviousPage could be used instead of DataCommands.NextRecord and DataCommands.PreviousRecord.
public static class DataCommands { private static object syncRoot = new object(); private static RoutedUICommand[] cachedCommands = new RoutedUICommand[Enum.GetNames(typeof(CommandId)).Count()]; private enum CommandId : byte { NullRecord = 0, NextRecord = 1, PreviousRecord = 2, FirstRecord = 3, LastRecord = 4, GoToRecord = 5, AddRecord = 6, Save = 7, DeleteRecord = 8 }
The enumeration is used with each static property that represents a command. The code snippet just shows the Save property, all the other command properties are similar. The method EnsureCommand returns the command instance of type RoutedUICommand taking a cached version if the command instance was previously created.
public static RoutedUICommand Save { get { return EnsureCommand(CommandId.Save); } } private static RoutedUICommand EnsureCommand(CommandId idCommand) { RoutedUICommand command = null; lock (syncRoot) { command = cachedCommands[(int)idCommand] ?? (cachedCommands[(int)idCommand] = new RoutedUICommand(Enum.GetName(typeof(CommandId), idCommand), Enum.GetName(typeof(CommandId), idCommand), typeof(DataCommands))); } return command; }
Navigation
The functionality offered by the BindingNavigator mainly is for navigation – of course. For this functionality the BindingNavigator makes use of commands. The InitalizeCommands method is called from within the constructor and adds all CommandBindings to a CommandBindingCollection.
public void InitializeCommands() { var nextCommand = new CommandBinding(DataCommands.NextRecord, OnNextRecord); commandBindings.Add(nextCommand); var previousCommand = new CommandBinding(DataCommands.PreviousRecord, OnPreviousRecord); commandBindings.Add(previousCommand); var firstCommand = new CommandBinding(DataCommands.FirstRecord, OnFirstRecord); commandBindings.Add(firstCommand);
The CommandBindingCollection can be accessed from the outside via the CommandBindings property.
private readonly CommandBindingCollection commandBindings = new CommandBindingCollection(); public CommandBindingCollection CommandBindings { get { return commandBindings; } }
The sample BindingNavigator doesn’t have a user interface and cannot be used from XAML. If the BindingNavigator would be a custom control offering a user interface, the command can be added via the CommandManager to have it available for UI controls. In this scenario the bindings must be added from the outside which is done here via an attached property that is shown later.
With the constructor of the CommandBinding class a command is bound to a handler. For example, the DataCommands.NextRecord command is bound to the handler method OnNextRecord. OnNextRecord is a static method that finds the instance of the BindingNavigator from the DataContext property of the sender that fires the command. Then the OnNextRecord instance method is invoked.
public static void OnNextRecord(object sender, ExecutedRoutedEventArgs e) { Contract.Requires(sender is FrameworkElement, "sender must be a FrameworkElement"); Contract.Requires((sender as FrameworkElement).DataContext is BindingNavigator, "sender.DataContext must be a BindingNavigator"); BindingNavigator nav = (sender as FrameworkElement).DataContext as BindingNavigator; if (nav != null) nav.OnNextRecord(); }
The instance method OnNextRecord makes use of the CollectionViewSource to navigate to the next record. Also, the property CurrentPosition that can be used with data binding might change on moving to the next record, and thus the PropertyChanged event is fired via the RaisePropertyChanged method.
private void OnNextRecord() { this.cvs.View.MoveCurrentToNext(); if (cvs.View.IsCurrentAfterLast) cvs.View.MoveCurrentToLast(); RaisePropertyChanged("CurrentPosition"); }
Binding Commands via an Attached Property
As previously mentioned, for the commands to be active, they must be added to a XAML element. This is done with the attached property RegisterCommandBindings that is defined in a helper class.
public class CommandBindingsAttachedProperty : DependencyObject { public static readonly DependencyProperty RegisterCommandBindingsProperty = DependencyProperty.RegisterAttached( "RegisterCommandBindings", typeof(CommandBindingCollection), typeof(CommandBindingsAttachedProperty), new PropertyMetadata(null, OnRegisterCommandBindingChanged)); public static void SetRegisterCommandBindings(UIElement element, CommandBindingCollection value) { element.SetValue(RegisterCommandBindingsProperty, value); } public static CommandBindingCollection GetRegisterCommandBindings( UIElement element) { return (CommandBindingCollection)element.GetValue( RegisterCommandBindingsProperty); } private static void OnRegisterCommandBindingChanged(object sender, DependencyPropertyChangedEventArgs e) { UIElement element = sender as UIElement; if (element != null) { CommandBindingCollection bindings = e.NewValue as CommandBindingCollection; if (bindings != null) element.CommandBindings.AddRange(bindings); } } }
Summary
To get a BindingNavigator to WPF that already exists for a long time just a simple class is needed that adapts the CollectionViewSource and defines commands that navigates through the records by using the CollectionViewSource.
Now all what’s needed is to define a UI that fires the commands. This is shown in the next blog post by using the WPF ribbon control.
Christian
Do you have the full source for the binding navigator
Posted by: Gassy | 03/24/2011 at 10:12 PM
as you can add a feature to filter
Posted by: Alexander | 05/06/2011 at 09:01 PM
This is a poorly written article and it was written black box style. Reverse the code when you write for the web so we can type the code and follow along sequentially. No working example leads to a loss of confidence in you as a blogger and coder. Rick...
Posted by: Rick W. | 10/29/2011 at 08:14 AM
Rick W is totally right. Since I'm a beginner, I can't get it work. I've created additional methods for moving through records, but mu CVS doesn't have methods for manipulating -add, remove, delete.
Maybe complete code, plz?
A working BindingNavigator would be a big advantage to my application, and since I'm using Ribbon (Fluent) as well, this would be great!
The same for second part of this article...
Posted by: Adrian | 07/11/2012 at 07:47 PM