Sony Arouje

a programmer's log

Posts Tagged ‘MVVM

An Introduction to Caliburn Micro

with 22 comments

 
Introduction

This post gives some brief introduction to develop Silverlight applications using Caliburn Micro which gives the basic features are:

  • BootStrapping
  • Conventions to write ViewModels
  • Event Aggregation
  • Action Message

Once you got the above basic ideas, it’s very easy to develop scalable applications in Silverlight. You can download the latest Caliburn Micro source from CodePlex.

When MVVM pattern introduced into my Silverlight development, initially it’s not digesting for me. But after I got the clear picture of MVVM pattern, I felt It’s very easy to develop Silverlight application that can adaptable to changes very easily. We choose Caliburn Micro as our MVVM framework, one reason it’s very light weight, another reason is the code base is very less. As the code base is very less we can go through it understand it better.

I hope every one who reads this post has some idea of MVVM pattern. In short MVVM separates View (presentation) and the code manipulates the view separately, and called it as View Model. One of the main advantage of MVVM is we can modify View or ViewModel without affecting each other. There is no event handlers for any control in the code behind, so it’s easy to replace one control with another. We can even reuse the same view model for different platform, let say we are developing an app for both Silverlight and WP7, here only difference in view is it’s base classes but UI is same. So we can share the same View Model thus by increase the maintainability and decrease the effort.

How the View and View Model’s will communicate? here the MVVM framework like Caliburn Micro will comes into picture. The MVVM Framework will bind the View and View Model’s. There are several MVVM frameworks in the market like Prism, MVVM Light, etc. but am not going to cover those.

Caliburn Micro uses conventions to bind View and View Model’s, I feel the conventions are better than wiring through code or attributes. You will get the conventions of Caliburn Micro as we progress through this post.

There are two approach in MVVM model, Code first and View first. Which one is better? I am not the person to comment on, their is an age old debate is going on. Personally I prefer Code First approach. But here to simplify things I will do it in View First approach.

Caliburn Micro In Action

Let’s do some small application in Silverlight using Caliburn Micro. The functionality of this app is pretty simple, display a list of customer and allow the user to edit it.

I am going to create a user control to display the details of individual customer. In the listing page we will bind this User control to a List. Let’s create the control.

IndividualResultView.xaml

    <Grid x:Name="LayoutRoot" Background="White">
        <Border BorderThickness="3" BorderBrush="Black" Margin="3">
            <Grid Margin="5">
                <Grid.RowDefinitions>
                    <RowDefinition Height="30"></RowDefinition>
                    <RowDefinition Height="60"></RowDefinition>
                    <RowDefinition Height="30"></RowDefinition>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="120"></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
        
                <TextBlock x:Name="lblName" Text="Name" Grid.Row="0" Grid.Column="0"></TextBlock>
                <TextBlock x:Name="Name" Grid.Row="0" Grid.Column="1"></TextBlock>
                
                <TextBlock x:Name="lblAddress" Text="Address" Grid.Row="1" Grid.Column="0" ></TextBlock>
                <TextBlock x:Name="Address" Grid.Row="1" Grid.Column="1"></TextBlock>
                
                <TextBlock x:Name="lblPhoneNumber" Text="Phone#" Grid.Row="2" Grid.Column="0"></TextBlock>
                <TextBlock x:Name="PhoneNumber" Grid.Row="2" Grid.Column="1"></TextBlock>
                
                <HyperlinkButton x:Name="EditCustomer" Grid.Row="3" Content="Edit"></HyperlinkButton>
            </Grid>
        </Border>
    </Grid>

 

IndividualResultView.xaml.cs

Let’s see the IndividualResultView.xaml.cs, as per the MVVM pattern it should not contain any View related code, other than initializing the UI controls. You can see that our code behind is clean.

using System.Windows.Controls;

namespace WCFEnabledSilverlightApp.Views
{
    public partial class IndividualResultView : UserControl
    {
        public IndividualResultView()
        {
            InitializeComponent();
        }
    }
}

We need to create a ViewModel for the above view, let’s do it. I named my ViewModel as IndividualResultViewModel.cs. Below is the code

using DataModel;
using Caliburn.Micro;
namespace WCFEnabledSilverlightApp.ViewModels
{
    public class IndividualResultViewModel:PropertyChangedBase
    {
        string _name;
        public string Name
        {
            get { return _name; }
            private set
            {
                _name = value;
                NotifyOfPropertyChange(() => Name);
            }
        }

        string _address;
        public string Address
        {
            get { return _address; }
            private set
            {
                _address = value;
                NotifyOfPropertyChange(() => Address);
            }
        }

        public string PhoneNumber { get; private set; }

        private Customer _customer = null;
        public IndividualResultViewModel(Customer customer)
        {
            this._customer = customer;

            this.Name = customer.CustomerName;
            this.Address = customer.Address;
            this.PhoneNumber = customer.PhoneNumber;
        }

        public void EditCustomer()
        {
            EditCustomerViewModel editCustomer = new EditCustomerViewModel(_customer);
            Screen scrn = new Screen();
            WindowManager winMngr = new WindowManager();
            winMngr.ShowDialog(editCustomer);
        }
    }
}

Convention 1

We learned our first convention of Caliburn Micro. The UI should suffix with “View”. The View Model should suffix with “ViewModel” and prefix with ViewName

<ViewName>View.xaml

<ViewName>ViewModel.cs

Eg.

IndividualResultView.xaml

IndividualResultViewModel.cs

Convention 2

You might have noticed that in the view and viewmodel we used some convention. The name of TextBlock that display the customer details have a corresponding property in ViewModel. For E.g. the TextBlock name for displaying Customer Name is “Name” and in ViewModel you can see a property “Name”. The Caliburn Micro will bind the Property in the ViewModel to the View. That means what ever value we set for property “Name” will be displayed in the View. Pretty simple.

You might have one question now how to handle click event or those kind of events raised by your view in our View model. It’s pretty easy create a function with same name as your button. Just go back to the View and check the name of the hyperlink button, also check whether their is method with same name in View model. Hyperlink button name is EditCustomer and I have one method in View model EditCustomer. The method will create an instance of another view model.

Later in this post you can see how we can pass parameters from your view to view model.

NotifyOfPropertyChange

In our properties the setter calls a function called NotifyOfPropertyChange, what is the use of this function call? We call this function to notify the UI that the value has changed in the view model. We can notify UI without calling NotifyOfPropertyChange by implementing INotifyPropertyChanged interface and pass the property name. Caliburn Micro wraps the  implementation of INotifyPropertyChanged in PropertyChangedBase and our view model is inherited from it. NotifyOfPropertyChange we are not passing any string value instead we pass the Property itself. The advantage of this method is we can use VS refactoring feature on properties. If we pass it as string then refactoring will not take into consideration.

Event Handling

When I implemented MVVM using Caliburn Micro I implemented the communication between View Models using normal event driven mechanism. That means If I want to handle an event occurred in a child view model in the parent. Then child view model raise an event and the parent will handle it. I didn’t like this approach as it creates a dependency between View models.  So I start exploring Caliburn Micro to figure out the event handling features and my search ends in EventAggregator.

Caliburn Micro’s Event Aggregator functionality is a very powerful implementation of Observer pattern. We can write View Models with less coupling using EventAggregators. Let’s see how can we establish communication between View Models using Event Aggregator.

In the sample code I created a static property to give access to EventAggregator as shown below

public class EventAggregationProvider
{
    static EventAggregator _eventAggregator = null;
    public static EventAggregator EventAggregator 
    { 
        get 
        {
            if (_eventAggregator == null)
                _eventAggregator = new EventAggregator();

            return _eventAggregator; 
        } 
    }
}

 

In the sample app I have a popup window to edit the Customer Details, when the user clicks Save/Ok I need to notify the Parent View Model to initiate the Save process. Let’s see how to implement it.

In my CustomerEditViewModel.cs there is a method called SaveCustomer which will get called when the user clicks Ok from the View. You can see that in SaveCustomer I am publishing the Customer object. See how I am publishing it.

EventAggregationProvider.EventAggregator.Publish<Customer>(customer);

 

This ViewModel done his job, it’s job of the parent view model who can subscribe to this message and get the notification. Now let’s see how the parent view model is subscribing to this event.

public class CustomerListingViewModel : PropertyChangedBase, IHandle<Customer>
{
    public CustomerListingViewModel()
    {
       Messages.EventAggregationProvider.EventAggregator.Subscribe(this);
    }
    public void Handle(Customer message)
    {
       //do the save process
    }

}

 

To Subscribe to Customer message the CustomerListingViewModel is implemented by IHandle<Customer>. That means this CustomerListingViewModel is capable of handling any message with type Customer. One more line of code need to added to get notification. You can see in the constructor of CustomerListingViewModel to subscribe to EventAggregator.

Your View Model can subscribe to any number of messages by implementing IHandle with the respective type. For e.g

public class CustomerListingViewModel : PropertyChangedBase, IHandle<Customer>,IHandle<Order>
{
    public CustomerListingViewModel()
    {
       Messages.EventAggregationProvider.EventAggregator.Subscribe(this);
    }
    public void Handle(Customer message)
    {
       //do the save process
    }
    public void Handle(Order message) 
    { 
       //do the save process 
    } 

}

In the above e.g this View Model will get notified if any view model publish Customer or Order message.

Action Message

The Action Message is one of the functionality of Caliburn Micro to call parameterized function from View. Let’s see how we can do it. To do this we need to refer system.windows.interactivity assembly.

In the below e.g. a message box will show when a LostFocus happened in View EditCustomerView. Let’s go through the View

           xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
           xmlns:ca="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"

                <TextBox x:Name="Name"  Grid.Row="0" Grid.Column="1">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="LostFocus">
                            <ca:ActionMessage MethodName="NameLostFocus">
                                <ca:Parameter Value="{Binding ElementName=Name,Path=Text}"></ca:Parameter>
                            </ca:ActionMessage>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </TextBox>
 

I referenced System.Windows.Interactivity and Caliburn Micro in the View. As you can see in the above view I mentioned “LostFocus” to the EventName. The ActionMessage takes the MethodName that we created in the ViewModel. Parameter’s Value take the parameter to the method. So what’s this means, it’s very simple, whenever the lost focus happened in Name text box then the view should call NameLostFocus method with TextBox’s Text as the parameter. Below is the NameLostFocus method in the ViewModel

public void NameLostFocus(string text)
{
    MessageBox.Show(text);
}

 

You can see more about Action Message in Rob’s blog

Bootstrapping

Bootstrapping is the method of booting our application and allow Caliburn Micro to take the control. So how to create a bootsrapper. It’s very simple as shown below

namespace WCFEnabledSilverlightApp
{
    public class BootStrapper:Bootstrapper<ViewModels.CustomerListingViewModel>
    {

    }
}

 

It’s tells caliburn that CustomerListingViewModel is the first page to load. It’s same as setting RootVisual to the some page say MainPage in non MVVM model. We need to provide this BootStrapper in App.xaml as shown below

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             x:Class="WCFEnabledSilverlightApp.App"
             xmlns:bs="clr-namespace:WCFEnabledSilverlightApp"
             >
    <Application.Resources>
        <bs:BootStrapper x:Name="bootStrapper"></bs:BootStrapper>
    </Application.Resources>
</Application>

 

Below is code behind of App.xaml. No code just clean.

using System.Windows;

namespace WCFEnabledSilverlightApp
{
    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
        }
    }
}

This post is just an introduction to Calibun Micro, just use it and get more comfortable with it. Thanks for reading this long post.

Introduction to Caliburn Micro Part 2

Download the source code.

Advertisements

Written by Sony Arouje

November 7, 2010 at 11:05 pm

Persisting Combo box SelectedItem – Caliburn Micro

with 7 comments

In this post I am going to explain how to persist Selected Item in a Combo box. In my application I have several screens and the user can navigate between them. I need to persist the Combobox’s Selected Item and bind it back when user navigate back to the screen. Here persisting is not db level persistence.

My application is based on Caliburn Micro, a wonderful lightweight MVVM framework.

To persist the selections, I wont create new ViewModel when ever the user clicks on the Navigation button. Only once the instance of the view model will get created. The following code shows the implementation, very simple approach.

FontPropertiesViewModel _fontPropertiesViewModel;
public void GetFontProperties()
{
    if (_fontPropertiesViewModel == null)
        _fontPropertiesViewModel = new FontPropertiesViewModel();
    ActiveScreen = _fontPropertiesViewModel;
}

Let’s examine the xaml file

<UserControl x:Class="MVVMTracer.Pages.Views.FontPropertiesView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:ca="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
    mc:Ignorable="d"
    d:DesignWidth="640" d:DesignHeight="480">
    
    <Grid x:Name="LayoutRoot" Background="Transparent" Margin="0,0,10,0">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition Height="35"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="35"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150"/>
                <ColumnDefinition Width="0.555*"/>
            </Grid.ColumnDefinitions>
            <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Text" VerticalAlignment="Center" FontSize="13.333" Margin="10,0,0,0" Foreground="#FF0F5DA7" Grid.ColumnSpan="2" Width="297"/>
            <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Font Type" Margin="10,0,0,0" Grid.Row="2" FontSize="11" VerticalAlignment="Center"/>
            <ComboBox x:Name="FontType" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" FontSize="11" SelectedItem="{Binding SelectedFontType}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SelectionChanged">
                        <ca:ActionMessage MethodName="FontTypeSelectionChanged">
                            <ca:Parameter Value="{Binding ElementName=FontType,Path=SelectedItem}"></ca:Parameter>
                        </ca:ActionMessage>
                    </i:EventTrigger>
                </i:Interaction.Triggers>                
            </ComboBox>
            
            <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Font Size" Margin="10,0,0,0" Grid.Row="3" FontSize="11" VerticalAlignment="Center"/>
            <ComboBox x:Name="FontSize" Margin="0,0,0,0" Grid.Row="3" FontSize="11" HorizontalAlignment="Left" VerticalAlignment="Center" Width="53" Grid.Column="1" SelectedItem="{Binding SelectedFontSize}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SelectionChanged">
                        <ca:ActionMessage MethodName="FontSizeSelectionChanged">
                            <ca:Parameter Value="{Binding ElementName=FontSize,Path=SelectedItem}"></ca:Parameter>
                        </ca:ActionMessage>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </ComboBox>
                        
            <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Font Style" Margin="10,0,0,0" Grid.Row="4" VerticalAlignment="Center" FontSize="11"/>
            <ComboBox x:Name="FontStyle" Grid.Row="4" Grid.Column="1" FontSize="11" VerticalAlignment="Center" SelectedItem="{Binding SelectedFontStyle}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SelectionChanged">
                        <ca:ActionMessage MethodName="FontStyleSelectionChanged">
                            <ca:Parameter Value="{Binding ElementName=FontStyle,Path=SelectedItem}"></ca:Parameter>
                        </ca:ActionMessage>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </ComboBox>
            <Button x:Name="Done" Content="Done"  Grid.Row="5" Grid.Column="1" Width="70" HorizontalAlignment="Right" d:LayoutOverrides="Height" VerticalAlignment="Bottom"/>
        </Grid>
    </Grid>
</UserControl>

If you examine the xaml you can see that the selected item of the combo boxes are binded to different properties, also I use ActionMessaging to notify the view model when ever a selection change happen in the Comboboxes. You might wonder why am using ActionMessaging rather than I could bind SelectedItem in two way mode. Yes I tried it first but unfortunately the Selected Item is not showing when user navigates back to the page.

Time to go through the view model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Caliburn.Micro;
using MVVMTracer.MockServices;
using EmblemDesigner.Controls;

namespace MVVMTracer.Pages.ViewModels
{
    public class FontPropertiesViewModel:PropertyChangedBase
    {
        public FontPropertiesViewModel()
        {
            this.FontType = FontServices.GetFontFamily();
            this.FontStyle = FontServices.GetFontStyle();
        }

        private List<string> _fontType;
        public List<string> FontType
        {
            get { return _fontType; }
            private set 
            {
                _fontType = value;
                NotifyOfPropertyChange(() => FontType);
            }
        }
        private string _selectedFontType;
        public string SelectedFontType
        {
            get { return _selectedFontType; }
            private set
            {
                _selectedFontType = value;
            }
        }
        public void FontTypeSelectionChanged(object selectedItem)
        {
            this.SelectedFontType = selectedItem as string;
        }

        private List<string> _fontSize;
        public List<string> FontSize
        {
            get { return _fontSize; }
            private set
            {
                _fontSize = value;
                NotifyOfPropertyChange(() => FontSize);
            }
        }
        private string _selectedFontSize;
        public string SelectedFontSize
        {
            get { return _selectedFontSize; }
            private set
            {
                _selectedFontSize = value;
            }
        }
        public void FontSizeSelectionChanged(object selectedItem)
        {
            this.SelectedFontSize = selectedItem as string;
        }


        private List<string> _fontStyle;
        public List<string> FontStyle
        {
            get { return _fontStyle; }
            private set
            {
                _fontStyle = value;
                NotifyOfPropertyChange(() => FontStyle);
            }
        }
        private string _selectedFontStyle;
        public string SelectedFontStyle
        {
            get { return _selectedFontStyle; }
            private set
            {
                _selectedFontStyle = value;
            }
        }
        public void FontStyleSelectionChanged(object selectedItem)
        {
            this.SelectedFontStyle = selectedItem as string;
        }
    }
}

Let’s take an eg. of FontType to understand what I am doing here. In the View one of my Combo box name is FontType. Caliburn Micro is intelligent enough to bind the FontType properties in the view model to the Combo box with name FontType in the view. Really great. If you run the application, you can see the FontType combo box is populated with the fonts we added to the FontType properties.

But it’s not enough to persist the user selection. To do so we need to add some more code to the view model. You might have noticed in xaml that the ActionMessage of FontType combo is attached to FontTypeSelectionChanged. When ever the user change selection it will get notified to FontTypeSelectionChanged function in the view model. This function will intern update the SelectedFontType property. The selected SelectedFontType property is binded to FontType’s Combobox’s SelectedItem.

Below diagram shows the communication between the view and the view model what I explained above.

image

One thing I noticed here is the Auto Implemented or Automatic Properties wont work here.

If any better approach pls update me.

Written by Sony Arouje

September 21, 2010 at 12:15 pm

An Experiment In Caliburn Micro

In this blog I will explain my experiment with Caliburn Micro. The business logic of this app is

  • It should display a list of images
  • Add the image to the canvas when the user selected an image
  • Drag the Image and Position the Image where ever the user required
    For dragging/rotation/resize the image I used an Adorner control. You can get more details about the control here.

I have to add Images displayed in a list box to a canvas. Pretty simple 🙂 and helped me a lot to learn Caliburn Micro. Before going further I wanted to say that the app may not be a full MVVM model, you will come to know as you proceed. I am working on it to make it completely MVVM complaint.

Below is the screen shot of the Silverlight app.

image

Two buttons are there on the top.

  • Show Text: button will load User control that will have a UI with some text box and combo box.
  • Image Controls: Button will load the user control displayed above.

Bare with my naming, I am very poor in it.

When the user clicks on any of these button we need to show the respective View in the left hand side. I used Caliburn Micro as my MVVM frame work.

My project structure is below

image

  • Views: Contains my XAML files
  • ViewModels: contains my view model of the views
  • ValueObject: contains the value object class I used in this app
  • Messages: I used EventAggregator provided by Caliburn Micro to enable messaging between ViewModels. This folder contains the Class I used for messaging between ViewModels.
  • Controls: contains custom controls I used in this app.
  • Adorner: contains the Adorner controls
    Let’s jump into the code

      TabView

    This is the main view which calls other other user controls

    XAML
<navigation:Page x:Class="MVVMTracer.Views.TabView" 
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
           xmlns:tl="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit"
           xmlns:we="clr-namespace:MVVMTracer.Controls"
           mc:Ignorable="d"
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           d:DesignWidth="640" d:DesignHeight="480"
           Title="TabView Page">
    <UserControl.Resources>
        <DataTemplate x:Key="shapeTemplate">
            <we:ControlBase Width="{Binding Width}" Height="{Binding Height}"/>
        </DataTemplate>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        
        <StackPanel Grid.Row="0" Orientation="Horizontal" Height="30">
            <Button x:Name="ShowTextControls" Content="Show Text"></Button>
            <Button x:Name="ShowImageControls" Content="Image Controls"></Button>
        </StackPanel>
        
        <tl:TransitioningContentControl Grid.Row="1" Grid.Column="0" x:Name="ActiveScreen"></tl:TransitioningContentControl>

        <we:CustomItemsCollection x:Name="DisplayControls"  Grid.Row="1" Grid.Column="1">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas  Background="AliceBlue"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </we:CustomItemsCollection>
    </Grid>
</navigation:Page>

In the above xaml you might notice that I used TransitioningContentControl. This is the control I am going to use to show different UserControl based on the user request. By the way TransitioningContentControl is part of Silverlight Tool kit,

Here I used a CustomItemsCollection to add my dynamically added controls. CustomItemsCollection is a control derived from ItemsControl. I will show the implementation of it later.

TabViewModel.cs

 

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Caliburn.Micro;
using MVVMTracer.Adorners;
using MVVMTracer.Controls;
using MVVMTracer.Messages;
namespace MVVMTracer.ViewModels
{
    public class TabViewModel:PropertyChangedBase,IHandle<Messages.ImageSelectionChanged>
    {
        public TabViewModel()
        {
            EventAggregatorContainer.EventAggregator.Subscribe(this);
        }
        private object _activeScreen;
        private Adorner _adorner = new Adorner();
        private Canvas _parentCanvas = new Canvas();
        public object ActiveScreen
        {
            get { return _activeScreen; }
            set
            {
                _activeScreen = value;
                NotifyOfPropertyChange(() => ActiveScreen);
            }
        }

        public void ShowTextControls()
        {
            ActiveScreen = new ShowTextViewModel();
        }

        public void ShowImageControls()
        {
            ShowImageControlViewModel imgViewModel = new ShowImageControlViewModel();
            ActiveScreen = imgViewModel;
        }
        public void Handle(Messages.ImageSelectionChanged message)
        {
            ControlBase ctrl = ShapeFactory.GetShape(ShapeType.Image, _parentCanvas, _adorner);

            ctrl.Source = message.CurrentImage;
            this.AddToUICollection(ctrl);
        }
        private void AddToUICollection(ControlBase control)
        {
            if (_displayControls == null)
            {
                _displayControls = new System.Collections.ObjectModel.ObservableCollection<UIElement>();
                _adorner.Height = 0;
                _adorner.Width = 0;
                _parentCanvas.Children.Add(_adorner);
                _displayControls.Add(_parentCanvas);
            }
            control.DrawControl();
            control.Height = 100;
            control.Width = 100;
            _parentCanvas.Children.Add(control);
            NotifyOfPropertyChange(() => DisplayControls);
        }
        private System.Collections.ObjectModel.ObservableCollection<UIElement> _displayControls;
        public System.Collections.ObjectModel.ObservableCollection<UIElement> DisplayControls
        {
            get { return _displayControls; }
            private set
            {
                _displayControls = value;
                NotifyOfPropertyChange(() => DisplayControls);
            }
        }




    }
}

The above code explains the ViewModel of TabView. As I mentioned in the beginning it may not completely satisfies MVVM pattern. I will explain the reason. As per my requirement I wanted to add Images to my Canvas and allow the user to reposition it.

To achieve the requirement I initially binded list of UIElement that consist of my Images to the CustomItemsCollection. But the issue I faced is I couldn’’t drag the image to different location. So I find out a work around. The work around is create a Canvas in Viewmodel and add the canvas to DisplayControls. Then add the images to the canvas object. By adding Canvas to DisplayControls will internally bind the Canvas object and its children to CustomItemsCollection in the View.

In this app I used EventAggregator to enable Messaging between ViewModels. To enable Messaging in CaliburnMicro the subscriber should Implement IHandle interface. You can see in the above code I implemented IHandle interface and subscribed to ImageSelectionChanged. Any publisher who publish ImageSelectionChanged message will be notified to TabViewModel.

The custom Control to display Image is not in MVVM structure.

You can find the Image listing control and other piece of codes in the uploaded source code.

You can download the code here

Written by Sony Arouje

September 9, 2010 at 7:06 pm

%d bloggers like this: