Welcome to WindowsClient.net | Sign in | Join

photoSuru Application Architecture

This document describes the overall architecture of photoSuru; read it for details on how photoSuru was built and gain some insight on how to build great WPF applications of your own.

Solution Structure

The photoSuru solution contains the following projects:

  • ScePhoto, the base data model and controls for a photo viewing application
  • ScePhotoViewer, the photo viewer application
  • ScePhotoFeed, the feed creation features
  • EffectLibrary, the shader effects used on photos
  • TransitionEffects, the shader effects used to transition between elements in the slide show

You’ll also find additional folders for Setup and a References folder of compiled DLLs used by photoSuru, but most of what happens in photoSuru happens from the ScePhotoViewer solution.

Data Model Architecture

For photoSuru, the Model contains all of the data for the application – particularly, the photo, gallery, and album metadata – as well as all of the methods needed to load and synchronize it. Specifically, the following tasks are performed in the model:

  • Feed processing and synchronization
  • Image and document retrieval
  • Data storage

It also contains all of the class definitions for the core data types used in the application – Photo, PhotoAlbum, PhotoGallery, HomePhotoAlbum (Gallery Home), PhotoAlbumCollection, PhotoGalleryCollection, SearchPhotoAlbum, FeedItem, Channel, etc.

The data classes reside in ScePhoto.Data and ScePhoto.Feed; children of these classes also reside in ScePhotoViewer namespace (Services folder). The entry point to the Model is through the static property ScePhoto.ServiceModel.DataManager, which contains the Gallery/Album/Photo tree, the tag store, and synchronization and retrieval methods.

photoSuru’s ViewModel layer contains all of the generic application functionality that describes the *context* upon which the data is built. Specifically, the ViewModel controls:

  • The ‘active’ items – items currently in view – including the current gallery, album (if showing an album or photo), and photo (if a specific photo is being displayed)
  • Navigation between items
  • Searching of items
  • Theming
  • Synchronization state
  • Global commands
  • Application dialogs
  • Command line argument processing

In some sense, the ViewModel is the workhorse of photoSuru – it synchronizes and maintains context amongst multiple user interface elements; while the Model layer is somewhat abstracted away from the presentation of the data it contains, the ViewModel drives what is presented on the UI, and therefore is tied to it. This dependency is structurally one way, however; the controls of the View layer bind to the services exposed by the ViewModel, but the ViewModel does not depend on the existence of certain controls in the View layer to operate. The ViewModel layer resides in the ScePhoto.View namespace, with some of its children in ScePhotoViewer namespace (Services folder). The entry point to the ViewModel is through the static property ScePhoto.ServiceModel.ViewManager.

For easy use and binding throughout the application, the Model and ViewModel are exposed as static properties on the ScePhoto.ServiceModel class; this allows them to be accessed using code (ScePhoto.ServiceModel.ViewManager.ActivePhoto) or via XAML ({Binding Source={x:Static ScePhoto:ServiceProvider.ViewManager}, Path=ActivePhoto}) without requiring a reference to be passed.

The View contains all of the user elements shown on the screen, both the XAML interface definitions as well as their related code-behind files. That means that the View contains:

  • All of the ResourceDictionaries
  • User Controls
  • Custom Controls
  • Windows
  • Converters
  • Images, Fonts, and other resources

Most of the View functionality is contained within the ScePhotoViewer and ScePhoto.Controls namespaces, but because of the wide variety of components it contains, it is broken up into several different folders:

  • ScePhoto\Controls – Reusable, generic photo controls
  • ScePhotoViewer\Controls – photoSuru-specific controls
  • ScePhotoViewer\Converters – data converters for use on WPF data bindings
  • ScePhotoViewer\Pages – photoSuru Windows
  • ScePhotoViewer\Resources – all "resources", incuding images, fonts, ResourceDictionaries, and themes

For more information on the Model-View-ViewModel pattern, see John Gossman’s blog at http://blogs.msdn.com/johngossman/archive/2005/10/08/478683.aspx

Controls and Resource Dictionaries

Every control in photoSuru is "look-less", that is, it does not define a specific user interface, just a particular set of functionality. To style the control, simply provide a default template – a Style for the control that has no key and whose TargetType is set to the control’s type – inside one of the application’s resource dictionaries; WPF will automatically display the template whenever the control is rendered. Since we can switch resource dictionaries at runtime, this allows us to theme the control dynamically, changing its look as the application theme changes.

Controls in photoSuru are located in two places:

  1. ScePhoto\Controls – generic, reusable controls like CommandButton, BreadcrumbBox, SizeTemplateControl, and PhotoThumbnailControl that are useful in many different WPF/photo applications
  2. ScePhotoViewer\Controls – controls more specific to photoSuru, like PhotoViewerControl, GalleryHomeControl, FilmStripControl, and PhotoSlideShowControl

Commands

Since the controls and their UI are separate, control events cannot be consumed directly. Instead, we use commands to bind a specific visual element to the functionality we want it to perform, exposed on the control the UI is templating or on the ViewManager for application wide commands (like Navigation or Sync).

Binding to commands is incredibly flexible – you can have multiple controls fire the same command (e.g. buttons, context menus, and key gestures) that synchronously enable/disable with the command, without creating separate functions or event handlers for each of these controls.

Controls expose commands – of type RoutedCommand – for the functions they perform that need to be invoked by the UI. For example, PhotoDisplayControl exposes a RoutedCommand with name ZoomPhotoInCommand; the UI button that binds to it is simply <Button Command="ScePhotoViewer:PhotoDisplayControl.ZoomPhotoInCommand" >. The command is registered with the control by using a CommandBinding that provides handlers that execute when the control executes or is interrogated to see if it can be executed. For the ZoomPhotoInCommand above, that statement is this.CommandBindings.Add(new CommandBinding(ZoomPhotoInCommand, new ExecutedRoutedEventHandler(OnZoomPhotoInCommand))). The OnZoomPhotoInCommand handler is executed whenever the command is invoked, whether from the button above, through a keyboard gesture, or another control. We could also add a CanExecuteRoutedEventHandler to automatically and enable and disable the command and its related UI; this handler simply needs to set e.CanExecute to true or false whenever it is called.

Commands that aren’t specific to a particular control – like those used for navigation or content syncing – are exposed on the ViewManager instead. Most of these commands are located at ServiceProvider.ViewManager.NavigationCommands or ServiceProvider.ViewManager.ViewCommands, but work the same way as if they were part of a control.

Navigation

Journaling and navigation are handled by WPF with a bit of help from photoSuru.

We construct navigators that contain all of the information WPF needs to put the application into a known state, for example, to show a particular photo or perform a search. These navigators, residing in the ScePhoto.View namespace, get journaled instead of the application UI itself (WPF’s default behavior when KeepAlive is set to true). This allows us to save a small amount of context when a navigation occurs – primarily, the details needed to get back to the view we just left – without saving the entire UI control tree into the journal (a memory intensive strategy).

We also use these navigators to move around the application in normal scenarios. Many of these navigators are smart; the PhotoNavigator, for instance, knows how to locate itself in its album and generate the navigator for the navigator that came before it as well as the one that came afterwards. Once a navigator has been generated, we can pass it to the same navigation logic that handles the back/forward case to actually move to that photo. Since everywhere we move throughout the application uses these navigators, everything is automatically saved to the journal for later back/forward operations.

We also listen application-wide for certain keys, like the arrow keys. When an arrow key is pressed and *not* handled by the control currently in focus (as is the case when a user is in "tabbed" mode and browsing around thumbnails), the event bubbles up the tree to ScePhotoViewer\ApplicationInputHandler.cs. ApplicationInputHandler then locates the current navigator on ViewManager, and performs the requested operation (next/previous/up/down/home) by generating the navigator in that "direction" from the current navigator and executing the navigation. This allows us to have a great browsing experience, anywhere in the application, without coding this navigation explicitly on every control.

Note

This document is also available from within photoSuru; simply open the ‘Application Architecture’ album in the ‘How To photoSuru’ subscription that installs with photoSuru.

Featured Item