Caliburn y Silverlight ejemplo sencillo..

Estos ultimos dias he estado checando Caliburn (MVVM framework) debido a la posibilidad de trabajar con el en un futuro cercano, asi que he decidido postear un muy parecido ejemplo al que publique hace poco donde trabajaban un expander y un splitter de manera conjunta, ahora como estoy utilizando Silverlight pues voy a deberles el expander ya que al parecer ese control no es soportado por esta tecnologia :( tal vez en el Silverlight Toolkit venga uno pero ese sera un tema para despues..

Caliburn me ha dejado impresionado con la cantidad de cosas que puedes hacer con tan poco codigo debido a la manera que esta diseniado el framework practicamente te permite enlazar controles y eventos y un monton de cosas que hace por ti detras del telon, lo unico malo es que la documentacion no es muy extensa y no existen tantas aplicaciones como ejemplo o quickstarts (como PRISM por ejemplo) asi que habra que contribuir con un granito de arena en la promocion de este excelente framework.

Bueno para empezar debemos crear un nuevo proyecto de tipo Silverlight, yo estoy utilizando el VS2010 y el Silverlight 4 que es una instalacion separada, una vez creado este proyecto y configurado automaticamente por Visual Studio removemos el control MainWindow.xaml y lo reemplazamos por ShellView.xaml que sera nuestro contenedor de contenido, a continuacion lo configuramos de la siguiente manera en el App.xaml principal para soportar Caliburn,

App.xaml

<am:CaliburnApplication
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:am="clr-namespace:Caliburn.PresentationFramework.ApplicationModel;assembly=Caliburn.PresentationFramework"
    x:Class="ExpanderAndSplitter.Caliburn.App">

    <!--This is a caliburn application -->
    <Application.Resources>
    </Application.Resources>
</am:CaliburnApplication>

Y a continuacion modificamos el code behind para que derive de la clase CaliburnApplication,
App.xaml.cs

using ExpanderAndSplitter.Caliburn.ViewModels;
using Caliburn.PresentationFramework.ApplicationModel;

namespace ExpanderAndSplitter.Caliburn
{
     public partial class App :  CaliburnApplication
    {

        public App()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Creating the root view model of the app, the shell
        /// </summary>
        /// <returns>Root view model (shell)</returns>
        protected override object CreateRootModel()
        {
            return new ShellViewModel();
        }

     }
}

En los pasos anteriores definimos la applicacion como un applicacion Caliburn y a continuacion hacemos Override al CreatRootModel() donde le diremos a Caliburn cual es el objeto de la vista que queremos como Root object o sea como contenedor principal de la aplicacion, cabe mencionar que caliburn es orientado a modelos y majena mucho el concepto de convenciones por lo que el framework detras del telon hara una serie de comprobaciones para ubicar la vista que pertenece al “ViewModel” que especificamos como contenedor principal en el Override por lo que no tendremos que hacer nada especial para que la vista de tal modelo este bindeada al modelo, MAGIA PURA!!!

ShellViewModel.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.Core;
using System.Collections.ObjectModel;
using ExpanderAndSplitter.Caliburn.Models;

namespace ExpanderAndSplitter.Caliburn.ViewModels
{
    public class ShellViewModel : PropertyChangedBase
    {

        #region Fields

        private GridLength _masterRowHeight = new GridLength(1, GridUnitType.Star);
        private GridLength _splitterRowHeight = new GridLength(0);
        private GridLength _detailRowHeight = new GridLength(1, GridUnitType.Auto);

        public ObservableCollection<ItemViewModel> _masterItems = null;
        public ObservableCollection<ItemViewModel> _detailItems = null;

        #endregion // Fields

        public ShellViewModel()
        {
            MasterRowHeight = new GridLength(1, GridUnitType.Star);
            SplitterRowHeight = new GridLength(20);
            DetailRowHeight = new GridLength(1, GridUnitType.Star);

            MasterItems = new ObservableCollection<ItemViewModel>();
            DetailItems = new ObservableCollection<ItemViewModel>();

            for (int i = 0; i < 1000; i++)
            {
                ItemModel masterIM = new ItemModel("Master Item Dummy " + i.ToString(), i.ToString());
                ItemModel detailIM = new ItemModel("Detail Item Dummy " + i.ToString(), i.ToString());

                ItemViewModel masterIVM = new ItemViewModel(masterIM);
                ItemViewModel detailIVM = new ItemViewModel(detailIM);

                MasterItems.Add(masterIVM);
                DetailItems.Add(detailIVM);
            }

        }

        #region Properties

        public GridLength MasterRowHeight
        {
            get { return _masterRowHeight; }
            set
            {
                _masterRowHeight = value;
                NotifyOfPropertyChange("MasterRowHeight");
            }
        }

        public GridLength SplitterRowHeight
        {
            get { return _splitterRowHeight; }
            set
            {
                _splitterRowHeight = value;
                NotifyOfPropertyChange("SplitterRowHeight");
            }
        }

        public GridLength DetailRowHeight
        {
            get { return _detailRowHeight; }
            set
            {
                _detailRowHeight = value;
                NotifyOfPropertyChange("DetailRowHeight");
            }
        }

        public ObservableCollection<ItemViewModel> MasterItems
        {
            get { return _masterItems; }
            set
            {
                _masterItems = value;
                NotifyOfPropertyChange("MasterItems");
            }
        }

        public ObservableCollection<ItemViewModel> DetailItems
        {
            get { return _detailItems; }
            set
            {
                _detailItems = value;
                NotifyOfPropertyChange("DetailItems");
            }
        }

        #endregion // Properties

    }
}

Siguiendo las convenciones definidas por el framework por default sabemos que las vistas deberan estar situadas en una carpeta llamada Views, los ViewModels al igual deberan estar en su carpeta llamada ViewModels, siendo todo esto configurable, ademas con el codigo fuente podemos definir nuestras propias convenciones :)

ShellView.xaml

<UserControl x:Class="ExpanderAndSplitter.Caliburn.Views.ShellView"
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    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"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="MainContainer" Margin="5,5,5,5" VerticalAlignment="Stretch"
          HorizontalAlignment="Stretch" Background="Transparent">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <sdk:Label x:Name="DummyLabel" Content="Dummy Label" />
        <ComboBox x:Name="DummyComboBox" Grid.Column="1" MinWidth="100"
                      Margin="0,0,5,0"/>

        <Button Grid.Column="2" Width="100" Content="Dummy Button" />
        <sdk:Label x:Name="Label" Content="Dummy Label 2" Grid.Column="4" HorizontalAlignment="Right" />

        <Grid Grid.Row="1" Grid.ColumnSpan="5" Margin="0,5,0,0"
              VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Transparent">
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition x:Name="MasterRow" Height="{Binding MasterRowHeight, Mode=TwoWay}" />
                <RowDefinition x:Name="SplitterRow" Height="{Binding SplitterRowHeight, Mode=TwoWay}" />
                <RowDefinition x:Name="DetailRow" Height="{Binding DetailRowHeight, Mode=TwoWay}" />
            </Grid.RowDefinitions>

            <Grid Background="Transparent">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>

                <sdk:DataGrid x:Name="MasterGrid"
                            AutoGenerateColumns="False"
                            ColumnWidth="*"
                            HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                            ItemsSource="{Binding MasterItems}">

                    <sdk:DataGrid.Columns>
                        <sdk:DataGridTextColumn Binding="{Binding Path=Name}" Header="Master Name" />
                        <sdk:DataGridTextColumn Binding="{Binding Path=ID}" Header="ID" />
                    </sdk:DataGrid.Columns>
                </sdk:DataGrid>
            </Grid>

            <sdk:GridSplitter Grid.Row="1" Background="Gray"
                              BorderThickness="1,1,1,1" Width="Auto" HorizontalAlignment="Stretch"
                              Height="6" VerticalContentAlignment="Top"
                              Padding="0,0,0,0" Margin="5,0,5,0" VerticalAlignment="Center"/>

            <Grid Background="Transparent" Grid.Row="2" Margin="0,5,0,0" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>

                <sdk:DataGrid x:Name="DetailGrid"
                            AutoGenerateColumns="False"
                            ColumnWidth="*"
                            HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                            ItemsSource="{Binding DetailItems}">

                    <sdk:DataGrid.Columns>
                        <sdk:DataGridTextColumn Binding="{Binding Path=Name}" Header="Detail Name" />
                        <sdk:DataGridTextColumn Binding="{Binding Path=ID}" Header="ID" />
                    </sdk:DataGrid.Columns>
                </sdk:DataGrid>
            </Grid>
        </Grid>
    </Grid>
</UserControl>

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

namespace ExpanderAndSplitter.Caliburn.Models
{
    public class ItemModel
    {

        public ItemModel(string name, string id)
        {
            this.Name = name;
            this.ID = id;
        }

        public string Name
        {
            get;
            set;
        }

        public string ID
        {
            get;
            set;
        }

    }
}

Al final la aplicacion luce casi igual que la del post pasado solo que utilizando Caliburn y Silverlight y a excepcion que no se tuvo que utilizar codigo para especificar la relacion entre vista y view model ya que caliburn se encarga de todo esto y muchisimo mas.

Mas adelante si tengo tiempo publicare algo relacionado con los Comandos, Triggers, Efectos y un monton de cosas que puedes aprovechar con caliburn de una manera sencilla, espero les haya gustado y no duden en dejar un comentario pues tal vez hize algo mal ya que apenas son mis inicios con Caliburn.

Puedes bajar el codigo fuente completo y el proyecto del siguiente enlace:  ExpanderAndSplitter.Caliburn

Saludos!
YR

  • Share/Bookmark

Comments (4)

franciscoJuly 6th, 2010 at 8:02 am

hey yo apenas estoy aprendiendo, y me interesa el MVVM pero usando Oracle, tu lo has manejado asi?

saludos

yohan.jasdidJuly 6th, 2010 at 9:48 am

Que onda compa, pues el MVVM es independiente del acceso a datos, solo es un patron que utiliza el bindeo como una forma de separar las vistas y los datos que en ellas se presentan, puedes reutilizar tu DAL y tus business objects.

pd. no he tenido chansa de checar el proyecto de telerik que me enviaste..

Saludos!

pepepacoSeptember 2nd, 2010 at 10:37 am

hey q onda aqui de nuevo…

despues de tanto batallar, quedo el proyecto usando Ado Dataservices con OpenAccess y todo porq estoy usando oracle con silverlight

… ahora estoy usando el entity framework y es mucho mas facil, si hubiera comenzado alrevez no hubiera batallado tanto…

oye no tienes la opcion de subscribirse a las respuestas del post…

saludos!

yohan.jasdidSeptember 2nd, 2010 at 11:20 am

Que onda mi estimado,

Pues no, no tenia esa opcion pero creo que es de bastante utilidad asi que ya la agregue, es un plugin de wordpress que se llama “Subscribe to comments” y agrega la opcion de subscribirte a los comentarios via e-mail cuando haces algun comentario, gracias por la recomendacion y que bueno que haya quedado al fin suena interesante, estaria bien si haces un post sobre eso en tu blog para poner un enlace desde aqui..

Saludos!

Leave a comment

Your comment