Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying MVVM pattern in a WPF application

Tags:

c#

mvvm

wpf

xaml

I'm making a simple WPF application with shapes that are drawn on a canvas. The view consists of a map that has a somewhat complex sequence of several squares on different, static positions on the map.

The MapView is a UserControl that contains a viewbox and canvas. The squares are represented by a UserControl with a simple canvas and a shape (ellipse in the code):

<Canvas>
    <Canvas.Resources>
        <BooleanToVisibilityConverter x:Key="boolToVisibility" />
    </Canvas.Resources>

    <Ellipse Stroke="Black" Fill="{Binding Color}" Width="{Binding Dimension}" Height="{Binding Dimension}" />
    <Ellipse Stroke="Black" Fill="Black" Canvas.Top="15" Canvas.Left="15" Width="20" Height="20" Visibility="{Binding IsOccupied, Converter={StaticResource boolToVisibility}}" />

</Canvas>

The views obviously all have a ViewModel (binded through the DataContext property of the view), backed up by a model.

My questions:

  • The SquareViews on my map all have a mousedown event, and every view represents a model, I'm confused on how to implement this in an elegant way in my application (with regards to the MVVM pattern). Should I predefine the SquareViews in XAML, and generate the models afterwards, or generate the models beforehand, and dynamically create the view based on the changes made to my model at runtime.

  • How to distinguish the SquareViews? Based on (view)model reference? Position coordinates? I'd like to avoid giving a seperate name to every separate square...

  • Other way of setting the DataContext of a view to it's corresponding viewmodel (without having to use a framework), instead of adding it to the code-behind of the view.

  • Is there a better way of positioning the squares on my map? (I know a canvas is not very flexible when it comes to scaling, different resolutions, dpi etc, but supposedly the viewbox should improve this, though I haven't completely tested that out yet)

PS Please let me know if my description/questions are to vague/abstract..

like image 373
tman Avatar asked Mar 09 '11 00:03

tman


1 Answers

If I'm understanding your question....

I think the approach you could take is by using DataTemplates, ItemsControl and perhaps ContentPresentor.

In effect what you want to do is tell WPF to "display" your view models. Because your view models are just plain classes, WPF does not know how to "render" them. This is where DataTemplates come in. The bonus of this approach is that the DataContext for the DataTemplate content will automatically be set to the view model. DataTemplates are defined in your Window or UserControl resources:

<Window.Resources>
    <DataTemplate DataType="{x:Type ViewModels:SquareViewModel}">
        <Views:SquareView />
    </DataTemplate>
</Window.Resources>

The above code will render a SquareView user control when WPF comes across a SquareViewModel (and set the DataContext on the SquareView to the SquareViewModel).

To place your view models in a view you can use a ContentPresenter (for a single ViewModel):

<ContentPresenter Content="{Binding SingleSquare}" />

In your case you will be wanting to have a collection of SquareViewModel items displayed so you will want to use the ItemsControl:

<ItemsControl ItemsSource="{Binding Squares}" />

This however won't give you your desired result as this will act like a ListBox by default. You will need apply a template to the ItemsControl to use an underlying Canvas. See this blog by Pete Brown for a possible implementation.

Good Luck!

Edit: Additional Code Example


View Models:

public class MainViewModel
{
    public IEnumerable<SquareViewModel> Squares { get; set; }

    public MainViewModel()
    {
        var squares = new List<SquareViewModel>();
        squares.Add(new SquareViewModel(15, 15,100,100, Brushes.CadetBlue, "Square One"));
        squares.Add(new SquareViewModel(75,125, 80, 80, Brushes.Indigo, "Square Two"));
        Squares = squares;
    }
}

public class SquareViewModel
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public Brush Color { get; set; }
    public string Name { get; set; }

    public SquareViewModel(int x, int y, int width, int height, Brush color, string name)
    {
        X = x;
        Y = y;
        Width = width;
        Height = height;
        Color = color;
        Name = name;
    }
}

Views

<UserControl x:Class="MapView.Views.SquareView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    >
    <Grid Background="{Binding Color, FallbackValue=Azure}">
        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Name, FallbackValue=None}" />     
    </Grid>
</UserControl>

<Window x:Class="MapView.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:ViewModels="clr-namespace:MapView.ViewModels" 
    xmlns:Views="clr-namespace:MapView.Views" Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <DataTemplate DataType="{x:Type ViewModels:SquareViewModel}">
            <Views:SquareView />
        </DataTemplate>
    </Window.Resources>
    <ItemsControl ItemsSource="{Binding Squares}" >
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Beige" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left"
                    Value="{Binding X}" />
                <Setter Property="Canvas.Top"
                    Value="{Binding Y}" />
                <Setter Property="Width"
                    Value="{Binding Width}" />
                <Setter Property="Height"
                    Value="{Binding Height}" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>
</Window>

And in the Window1 Constructor:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}
like image 184
Mike Rowley Avatar answered Sep 27 '22 23:09

Mike Rowley