Feeds:
Posts
Comments

Posts Tagged ‘MVVM’

Hi,

Now that I could see a visual representation of my project, next is sharpening the architecture and design of the application.

One design pattern that comes  very often when reading about WPF is MVVM (http://en.wikipedia.org/wiki/Model_View_ViewModel). I used this pattern and separate my GUI business from my domain objects, I struggled a bit at the beginning but finally managed to get something out.

Before moving to the steps taken to get an MVVM architecture out of my application, let’s have a quick look at the current implementation. In my previous post I directly used the map object from SharpMap and displayed it in the GUI as an image:

1- Load a shape file using SharpMap API

2- Render the shape file into an image

3- Use the resulting image and display it directly in the main window

Here is the pieces of code used for this purpose

// Event handler for button click
public void OpenShapeFileClicked(object sender, EventArgs e){
      // ... ask the user to provide a shape file using a file open dialog control
      LoadShapeFile(path_provided_by_user);
}
public void LoadShapeFile(string fileName){
      SharpMap.Layers.VectorLayer myLayer = new SharpMap.Layers.VectorLayer("ShapeFiles");
      myLayer.DataSource = new SharpMap.Data.Providers.ShapeFile("path_to_file", true);
      myLayer.Style.Fill = System.Drawing.Brushes.GreenYellow;
      myLayer.Style.Outline = new System.Drawing.Pen(System.Drawing.Brushes.Gray);
      myLayer.Style.Line.Width = 2;
      myLayer.Style.Line = new System.Drawing.Pen(System.Drawing.Brushes.Black);
      myMap.Layers.Add(myLayer);
      myMap.ZoomToExtents();
      DrawImage();
}
private void DrawImage(){
      // Convert System.Drawing.Image to ImageSource to wich an image is bound
      using (System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(myMap.GetMap())){
             IntPtr hBitmap = bitmap.GetHbitmap();
             // Use System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
             BitmapSource bitmapSource = CreateBitmapSourceFromHBitmap(
                hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
             image1.Source = bitmapSource;
      }
}

This is the usual way most programmers will do and especially those writing WinForms applications. Obviously not  the way to go as the domain code is linked to the GUI which makes it very hard to read and maintain.

What we want here is certainly not coupling the GUI with the business domain layer. In order to achieve separation of concerns, we will make use of the MVVM pattern and data binding.

Before looking on how to bind the map to the main GUI, let’s first have a look at the different artifacts of the application and where do they fit in MVVM:

Model : the map data structure, contains GIS data organized in layers as per the GIS simple feature specifications

View : a very thin layer communicating directly to the WPF main window

ViewModel: A new class that is introduced to separate the view from the model

Let’s see this pattern in action in a real life example: loading shape files! Let’s look at how shape files are loaded after moving the application’s architecture to MVVM.

The main control worth mentioning here is the Canevas that will hold the map. This control is updated upon model change notifications.

In order to be aware of all domain objects data changes, the main window will subscribe to change notifications coming from the map model (domain objects):

// Listen to model changes
mapView.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(MapViewModelPropertyChanged);

This bit of code is placed on the main window constructor. This is a simple way of subscribing to all model data changes, albeit not a perfect one ( WPF encourages binding to the model directly on the XAML)

The ‘Load shape file’ button on the main window is bound to a command on the view class that will take care of loading all needed data

public ICommand LoadShapeFileCommand {
 get {
   if (loadShapeFileCommand == null) {
        // Use Lambda calculus to run the proper method when this command is applied
        loadShapeFileCommand = new GenericCommand(param => LoadShapeFile(), param => (mapViewModel != null));
   }
   return loadShapeFileCommand;
 }
}

The command patter used relies on Josh Smith’s relayCommand implementation (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx)

The shape file is loaded as previously done, using SharpMap API. The map rendering is however now done in a different way using WPF rendering services instead of GDI+.
public void LoadShapeFile(string shapeFileName) {
      ...
      // The model has changed, notify the GUI of the change
      PropertyChangedEventArgs args = new PropertyChangedEventArgs("MapSnapshot");
      PropertyChanged(this, args);
}

The PropertyChanged event will end up in the main window.

        private void MapViewModelPropertyChanged(object sender, PropertyChangedEventArgs args) {
            string propertyName = args.PropertyName;
            if (propertyName == "MapSnapshot") {
                // Map snapshot has changed, reload it
                canvas1.Children.Clear();
                canvas1.ClipToBounds = true;
                canvas1.Children.Add(mapView.MapDrawing);
            }
        }
When doing this, watch out to properly setup the property name that has changed in the PropertyChangedEventArg. This is very important as it helps identify which part of the model did change (needs redrawing)
As you can see from the above code snapshot, the Canvas is updated by adding “mapView.Drawing” visual elements into its children.
public class MapView : FrameworkElement, INotifyPropertyChanged, IDisposable {
        private System.Windows.Media.VisualCollection children;
        ....
        public UIElement MapDrawing {
            get {
                return GetMapDrawing(mapViewModel.map);
            }
        }
        private UIElement GetMapDrawing(Map map) {
            children.Clear();
            VectorRenderer.map = map;
            foreach (SharpMap.Layers.ILayer layer in map.Layers) {
                // Render layer into layer drawing
                System.Windows.Media.DrawingVisual layerDrawing = VectorRenderer.Render(layer, map.Envelope);
                // Add rendered layer into drawing group
                AddVisualLayer(layerDrawing);
            }
            return this;
        }
        private void AddVisualLayer(System.Windows.Media.DrawingVisual visualLayer) {
            children.Add(visualLayer);
        }

As the rendering engine I’m working on these days is still “work in progress” only vector data can be rendered.

In this post, we have been through the main steps taken to put the MVVM pattern in action.

I also made some progress on writing my own WPF rendering engine which seems to do the work as expected. (see screenshot below)

Next, I’ll try to improve the rendering engine to support drawing other layer types, stay tuned…

Advertisements

Read Full Post »