简体   繁体   中英

Applying MVVM pattern in a WPF application

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..

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();
    }
}

Have a look at this blog by Ryan Cromwell. Basically, you want to display a "list" of squares on a canvas. He explains how to do exactly want I think your asking for.

You have to come up with some kind of grid sructure (WPF Datagrid will do for u). Advantage of grid is that it can be used like rows can be treated as x co-ordinates and columns as y co-ordinates.. Before starting implementation, imagine what exactly you want to display on UI. WPF is just a technology to bring your imagination to reality. If your application is UI driven then first gather all actions that are possible from UI, create command for those actions in view model.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM