To get started on the WPF version of Conference Buddy (described here with an overview here), Carey Payette and I decided to pair program. Fortunately for me, she had a fundamental architecture in mind and lead me through much of what I’m about to report to you.
You will remember that Conference Buddy is a system for tracking people we meet at conferences, and which Telerik products they are using and which they are interested in.
Here’s the main events window, showing the events we’re tracking. Our goal for this week is to be able to click on an event and be taken to the list of contacts for that event, from where we can switch to seeing statistics for that event.
Key to our approach is that the MainPage consists of nothing more than a Content control
<Grid><ContentControlName="MainContentControl"Content="{Binding CurrentView}"/></Grid>
Note that we are binding to the CurrentView property. Our data context will be the associated view model, MainViewModel, which contains, among other things, this code:
privateobject_currentView;publicobjectCurrentView{get{returnthis._currentView;}set{this._currentView = value;this.RaisePropertyChanged("CurrentView");}}publicvoidNavigateToView(objectview ){CurrentView = view;}publicMainViewModel(){var view =newMainEvent();var vm =newMainEventViewModel();view.DataContext = vm;CurrentView = view;}
That is, specifically, it contains a property for CurrentView, a method NavigateToView which sets the CurrentView and a constructor which sets the ViewModel as the data context for the MainView.
So what are the views? We decided that the first view, the one to load on start up would be the MainEvent “page” (implemented as a user control) that lists all the events. This exists as a user control so that we can make it be the CurrentView and display it in MainView.
The MainEvent consists almost entirely of a RadTileList. There are two parts to creating this list; the header and the DataTemplate. The header sets the ItemsSource to bind to the Events collection. The data template provides bindings to the event’s name, city, state and id.
The MainEventViewModel class consists of a collection of Events (as a property) and an ICommand to go to the Event Detail when an event is clicked. The constructor gets the collection (currently “faked” by the EventRepository class) and sets the command to the NavigateToEventDetailCommand (which is created in the Command folder).
When you click on one of the tiles we go to the EventDetails page (that is, again, we load the EventDetails user control into MainPage).
To make this work, we add a trigger in the RadTileList in the XAML,
<telerik:RadTileListx:Name="EvntTileList"ItemsSource="{Binding Events}"ScrollViewer.HorizontalScrollBarVisibility="Visible"><i:Interaction.Triggers><i:EventTriggerEventName="SelectionChanged"><i:InvokeCommandActionCommand="{BindingGoToEventDetail}"CommandParameter="{BindingElementName=EvntTileList,Path=SelectedValue.id}" /></i:EventTrigger></i:Interaction.Triggers>
| Note that this will require a reference to System.Windows.Interactivity – a library provided by Blend and which you can obtain through NuGet. |
You will need to add the namespace at the top of your file:
xmlns:i=http://schemas.microsoft.com/expression/2010/interactivity
The Event trigger is followed by this code,
privateIEnumerable<Event> events;publicIEnumerable<Event> Events{get{returnevents;}set{events = value;RaisePropertyChanged("Events");}}publicICommand GoToEventDetail;publicMainEventViewModel(){var repo =newEventRepository();Events = repo.Get();GoToEventDetail =newNavigateToEventDetailCommand();}
The NavigateToEventDetailCommand derives from BaseCommand which in turn derives from ICommand and implements CanExecuteChanged and marks CanExecute and Execute as abstract methods. The CanExecute for NavigateToEventDetailCommand is hard coded to return true for now and the Execute command, shown below does all the interesting work,
publicoverridevoidExecute(objectparameter ){inteventID = (int) parameter;var repo =newEventRepository();var theEvent = repo.GetEventByID( eventID );var eventDetails =newEventDetails();var eventDetailVM =newEventDetailViewModel();eventDetailVM.TheEvent = theEvent;eventDetails.DataContext = eventDetailVM;App.MainVM.NavigateToView(eventDetails);}
Let’s look at that line by line. First we extract the eventID from the parameter passed in to the Execute command. We then get the repository object and use that to get the specific event using the eventID we received as a parameter. Next we instantiate the EventDetails page and the EventDetailViewModel. We set the TheEvent property of the EventDetailVM to theEvent that we retrieved from the repository. We then set the DataContext of EventDetails to the EventDetailVM and we tell the Main View Model to navigate to the EventDetails page.
The EventDetails page will follow on with our design, placing navigation at the top of the page and then a ContentControl below that, into which we can slot user controls (views) for the Contacts and the stats.
<Grid.RowDefinitions><RowDefinitionHeight="Auto"/><RowDefinition/></Grid.RowDefinitions><StackPanelOrientation="Horizontal"><Labelx:Name="Contacts"Content="Contacts"/><Labelx:Name="Stats"Content="Stats"/></StackPanel><ContentControlx:Name="EventContent"Grid.Row="1"Content="{Binding CurrentSubView}"/>
When the page is first loaded, the Contacts are displayed in the ContentControl by instantiating the user control, EventContacts.xaml. This in turn holds a RadGridView whose ItemsSource is set to bind to the Contacts property of its View Model.
In the constructor for that View Model we obtain the contacts as an IEnumerable<Contact> (and it is this collection to which the RadGridView binds), and thus display the contacts associated with the event.
We now have an architecture that supports slotting pages into the Main view and that responds to clicks using the command architecture. This allows us to keep the command logic out of the view.
To get the app to start properly, we override OnStartup in App.xaml.cs
protectedoverridevoidOnStartup( StartupEventArgs e ){base.OnStartup( e );var mainWindow =newMainWindow();MainVM =newMainViewModel();mainWindow.DataContext = MainVM;mainWindow.Show();}
That sets the mainWindow and MainViewModel and set the VM as the context for the MainWindow. With that in place, we remove the StartupUri property in App.xaml.
For the swapping of windows to work, we need consistent access to the MainWindow View Model so we add a static reference in App.xaml:
publicstaticMainViewModel MainVM;// hack?
We are now well positioned both to flesh out the pages we have and to add the additional pages we need. The command support in WPF makes using the MVVM pattern much easier and allows us to neatly tuck all our code into the VM classes rather than into code-behind. Next up: the Contacts and Stats page.
Jesse Liberty has three decades of experience writing and delivering software projects. He is the author of 2 dozen books and has been a Distinguished Software Engineer for AT&T and a VP for Information Services for Citibank and a Software Architect for PBS. You can read more on his personal blog or follow him on twitter