Telerik blogs

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.

image

 

Key to our approach is that the MainPage consists of nothing more than a Content control

<Grid>
  <ContentControl Name="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:

private object _currentView;
public object CurrentView
{
   get
   {
      return this._currentView;
   }
   set
   {
      this._currentView = value;
      this.RaisePropertyChanged( "CurrentView" );
   }
}
 
public void NavigateToView( object view )
{
   CurrentView = view;
}
 
public MainViewModel()
{
   var view = new MainEvent();
   var vm = new MainEventViewModel();
   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:RadTileList x:Name="EvntTileList"
                     ItemsSource="{Binding Events}"
                     ScrollViewer.HorizontalScrollBarVisibility=
                                             "Visible">
      <i:Interaction.Triggers>
          <i:EventTrigger EventName="SelectionChanged">
              <i:InvokeCommandAction Command="{Binding
                             GoToEventDetail}"
                 CommandParameter="{Binding
                ElementName=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,

private IEnumerable<Event> events;
public IEnumerable<Event> Events
{
   get
   {
      return events;
   }
   set
   {
      events = value;
      RaisePropertyChanged( "Events" );
   }
}
 
public ICommand GoToEventDetail;
 
public MainEventViewModel()
{
   var repo = new EventRepository();
   Events = repo.Get();
   GoToEventDetail = new NavigateToEventDetailCommand();
}

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,

public override void Execute( object parameter )
{
   int eventID = (int) parameter;
   var repo = new EventRepository();
   var theEvent = repo.GetEventByID( eventID );
   var eventDetails = new EventDetails();
   var eventDetailVM = new EventDetailViewModel();
   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.

Adding Event Details

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>
   <RowDefinition Height="Auto" />
   <RowDefinition />
</Grid.RowDefinitions>
   <StackPanel Orientation="Horizontal">
   <Label x:Name="Contacts"
       Content="Contacts" />
   <Label x:Name="Stats"
          Content="Stats" />
</StackPanel>
<ContentControl x: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.

What Have We Learned, Dorothy?

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

protected override void OnStartup( StartupEventArgs e )
{
   base.OnStartup( e );
   var mainWindow = new MainWindow();
   MainVM = new MainViewModel();
   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:

public static MainViewModel 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.


WPF
jesseLiberty
About the Author

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

Related Posts

Comments

Comments are disabled in preview mode.