In my previous blog post I’ve shown a sample how to create a BindingNavigator for WPF that fulfills the functionality of the BindingNavigator that is available with Windows Forms. Part 2 of this series demonstrates how the BindingNavigator can be used.
User Interface
I’ve created a user interface that looks akin to the Windows Forms BindingNavigator by using the ribbon control. Of course the ribbon control allows for a much more enhanced UI, but this is another topic. The new look is shown in this screenshot:
For navigating through the records and adding, deleting, and saving I’ve defined one RibbonTab data with two RibbonGroup elements. The first RibbonGroup is for navigation, the second RibbonGroup for changing.
The elements within a RibbonGroup are mainly represented by RibbonButton elements, e.g. the buttons to move to the first and the previous record. For the UIs of the elements either the LargeImageSource or SmallImgeSource properties are set depending on the representation in the UI. The images are taken from the VS2010ImageLibrary that is part of Visual Studio 2010.
<ribbon:RibbonButton x:Name="firstButton" LargeImageSource="Images\MoveFirst.png" Label="First" Command="utils:DataCommands.FirstRecord" /> <ribbon:RibbonButton x:Name="prevButton" LargeImageSource="Images\MovePrevious.png" Label="Previous" Command="utils:DataCommands.PreviousRecord" />
Instantiating the BindingNavigator
A parent element of both the ribbon control and the controls that show the content of the record has a BindingNavigator set as the DataContext. Here, the property RacersNavigator returns a BindingNavigator.
<Grid DataContext="{Binding RacersNavigator}" utils:CommandBindingsAttachedProperty.RegisterCommandBindings= "{Binding CommandBindings}">
The property RacersNavigator returns a cached BindingNavigator if an instance is already created, otherwise a new object is instantiated where a data source in form of a Entity Framework ObjectQuery is assigned to the constructor.
private BindingNavigator racersNavigator; public BindingNavigator RacersNavigator { get { if (racersNavigator == null) { var data = GetRacersQuery(formula1Entities).Execute( MergeOption.AppendOnly); racersNavigator = new BindingNavigator(data); } return racersNavigator; } } private ObjectQuery<Racer> GetRacersQuery(Formula1Entities formula1Entities) { return formula1Entities.Racers; }
With the sample code I’m using the MVVM pattern with an implementation of the MVVM Light Toolkit from Laurent Bugnion. The root window has the DataContext set to a property of a view model locator which in turn returns a view model. The property RacersNavigator is the property of a view model. In case code-behind is preferred instead of MVVM, the RacersNavigator can be a implemented as a property of the code-behind code.
Commands
The BindingNavigator defines command bindings that define what command maps to what event handler. The commands used by the BindingNavigator are defined in the DataCommands class. Mapping UI actions to commands is done by setting the Command property as shown in the previous code snippet.
What also needs to be done is to map the command bindings to a UI element. As the BindingNavigator is not declared within XAML, this needs to be done manually. For mapping the command bindings to UI elements an attached property is used. This attached property gets the value from the CommandBindings property (of the BindingNavigator) that returns a CommandBindingCollection and adds the command bindings to the element where it is applied to (here to the Grid).
<Grid DataContext="{Binding RacersNavigator}" utils:CommandBindingsAttachedProperty.RegisterCommandBindings= "{Binding CommandBindings}">
Content
The container of the content controls needs to bind to the View property of the BindingNavigator. This View property returns the CollectionViewSource and thus allows content controls to bind to one element of the collection, and list controls to the complement list represented by the view.
<Grid DataContext="{Binding View}" HorizontalAlignment="Left" Margin="5,5,0,0" Name="grid1" VerticalAlignment="Top" Grid.Row="1">
To display the content no special action is needed. The controls as they are created from the data source window from Visual Studio can be used as they are.
<Label Content="Id:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" /> <TextBox Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" Margin="3" Name="idTextBox" Text="{Binding Path=Id, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" /> <Label Content="First Name:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" /> <TextBox Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="3" Name="firstNameTextBox" Text="{Binding Path=FirstName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" /> <Label Content="Last Name:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" /> <TextBox Grid.Column="1" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="3" Name="lastNameTextBox" Text="{Binding Path=LastName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
Summary
As soon as the BindingNavigator is defined as was shown in the blog entry to create a BindingNavigator for WPF, it is easy to use it.
A UI for the commands must be implemented (e.g. by using the ribbon), the commands need to be fired, and the BindingNavigator takes the action.
Of course all the work of implementing the BindingNavigator is not necessary if Visual Studio LightSwitch is used
Christian
could you post the entire source code?
Posted by: Gassy | 03/04/2011 at 09:03 PM
This is a great post and I have it working but it breaks as soon as I have a control that utilizes multiple BindingNavigators (e.g. Tab Control that contains two UserControls each with a Binding Navigator). The behavior I am experiencing, is that the sender parameter on the OnAddRecord handler is always the parent grid of the BindingNavigator whose Add button was selected first. If I choose another tab exposing a different UserControl with BindingNavigator and select the Add button, the Sender is still the grid of the first tab and hence my DataContext is not what I am expecting. Any thoughts? Seems the static/cached RoutedUICommands are the issue.
Posted by: Stem | 05/01/2012 at 12:41 AM