Estos últimos días he estado checando Caliburn (MVVM framework) debido a la posibilidad de trabajar con el en un futuro cercano, así que he decidido postear un muy parecido ejemplo al que publiqué 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 tecnología 🙁 tal vez en el Silverlight Toolkit venga uno pero ese será un tema para después..
Caliburn me ha dejado impresionado con la cantidad de cosas que puedes hacer con tan poco código debido a la manera que esta diseñado el framework prácticamente te permite enlazar controles y eventos y un montón de cosas que hace por ti detrás del telón. Lo único malo es que la documentación no es muy extensa y no existen tantas aplicaciones como ejemplo o quickstarts (como PRISM por ejemplo) así que habrá que contribuir con un granito de arena en la promoción de este excelente framework.
Para empezar debemos crear un nuevo proyecto de tipo Silverlight. Yo estoy utilizando el VS2010 y el Silverlight 4 que es una instalación separada. Una vez creado este proyecto y configurado automáticamente por Visual Studio removemos el control MainWindow.xaml y lo reemplazamos por ShellView.xaml que será nuestro contenedor de contenido, a continuación 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 continuación 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 aplicación como un aplicación Caliburn y a continuación 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 aplicación, cabe mencionar que caliburn es orientado a modelos y maneja mucho el concepto de convenciones por lo que el framework detrás del telón hará 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 deberán estar situadas en una carpeta llamada Views, los ViewModels al igual deberán estar en su carpeta llamada ViewModels, siendo todo esto configurable, además con el código 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 aplicación luce casi igual que la del post pasado solo que utilizando Caliburn y Silverlight y a excepción de que no se tuvo que utilizar código para especificar la relación entre vista y ViewModel ya que caliburn se encarga de todo esto y muchísimo más.
Más adelante si tengo tiempo publicaré algo relacionado con los Comandos, Triggers, Efectos y un montón 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 hice algo mal ya que apenas son mis inicios con Caliburn.
Puedes bajar el código fuente completo y el proyecto del siguiente enlace: ExpanderAndSplitter.Caliburn
¡Saludos!
-Yohan
hey yo apenas estoy aprendiendo, y me interesa el MVVM pero usando Oracle, tu lo has manejado asi?
saludos
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!
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!
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!