Silverlight

...now browsing by tag

 
 

Silverlight & Model-View-ViewModel

Quinta-feira, Maio 26th, 2011

pic1

Existem várias formas diferentes de definir a camada de apresentação, pode-se seguir uma aproximação tradicional, na qual toda a responsabilidade da camada de apresentação se encontra na View ou então podemos seguir um pattern para prodecer à separação de responsabilidades da camada de apresentção.

A aproximação mais tradicional torna-se mais simples e criar permite rapidamente uma camada de apresentação. É ideal quando se trata de aplicações de pequena dimensão .

O problema é que não existem aplicações de pequena dimensão, algumas podem começar dessa forma, mas muito rapidamente crescem e transforma-se em uma dor de cabeça para qualquer equipa de manutenção, dando origem a mais uma “Black Box” passados alguns anos.

Para ajudar a solucionar esse tipo de problema recorrente, nasceram algumas patterns que nos permitem estruturar de uma melhor forma a camada de apresentação das aplicações, entre elas estão o MVC (Model-View-Controller), MPV (Model-View-Presenter) e o MVVM (Model-View-ViewModel) que é o tema de este post.

Como não podemos deixar de falar em Silverlight ou WPF quando o tema é MVVM, vou apresentar um tutorial no qual demonstro como implementar MVVM no Silverlight.

O MVVM pretende estabelecer uma separação clara de responsabilidades na camada de apresentação, a View apenas é responsável pela User Interface, o Model detém as regras de negócio (classes de negócio, acesso a dados, etc) e o ViewModel é o responsável de fornecer à View apenas os dados que ela necessita e é também o ViewModel quem tem a responsabilidade de reagir aos eventos  gerados pela View.

Uma das grandes vantagens na utilização de um pattern como este, é que permite alterar completamente a UI apresentada ao utilizador sem que se torne necessário alterar mais do que a View, ou então, num caso mais extremo, partilhar o Model e o ViewModel e criar dois Front-End diferentes, como Silverlight e Windows Phone 7.

Vamos então ao que interessa, criar uma Demo de uma aplicação de gestão de Clientes, que apresenta a lista  de clientes de uma empresa e também permite adicionar um novos clientes à lista actual.

Vamos criar um novo projecto do tipo Silverlight Application no VS2010

image

 

Seleccionar a check-box “Host the Silverlight application in a new Web site”

image

 

Adicionar duas novas referências ao projecto Silverlight

image

 

Adicionar as referências à página MainPage.xaml

<UserControl x:Class="Silverlight_MVVM_Demo.MainPage"
    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="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"             

    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">

    </Grid>
</UserControl>

 

Vamos definir a View

image

Agora em XAML

<UserControl x:Class="Silverlight_MVVM_Demo.MainPage"
    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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="Gray" Width="800" Height="600">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--DataGrid Definition-->
        <sdk:DataGrid Height="300" Grid.Row="0" BorderThickness="1"/>

        <!--Data Input Definition-->
        <sdk:Label
            Grid.Row="1"
            Height="28"
            HorizontalAlignment="Left"
            Margin="10,10,0,0"
            VerticalAlignment="Top"
            Width="171"
            Content="Adicionar novo cliente"
            FontFamily="Verdana"
            FontSize="13.333"
            FontWeight="Bold" />
        <TextBlock
            HorizontalAlignment="Left"
            Margin="80,59,0,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Nome"
            VerticalAlignment="Top"/>
        <TextBox
            HorizontalAlignment="Left"
            Margin="80,79,0,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            VerticalAlignment="Top"
            Width="245"/>
        <TextBlock
            HorizontalAlignment="Left"
            Margin="80,120,0,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="NIF"
            VerticalAlignment="Top"/>
        <TextBox
            HorizontalAlignment="Left"
            Margin="80,140,0,136"
            Grid.Row="1"
            TextWrapping="Wrap"
            Width="245"
            d:LayoutOverrides="Height"/>
        <TextBlock
            HorizontalAlignment="Left"
            Margin="80,0,0,98"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Telemóvel"
            VerticalAlignment="Bottom"/>
        <TextBox
            HorizontalAlignment="Left"
            Margin="80,0,0,70"
            Grid.Row="1"
            TextWrapping="Wrap"
            Width="245"
            VerticalAlignment="Bottom"/>
        <TextBlock
            Margin="0,0,229,98"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Número de Cliente"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Right"
            Width="111"/>
        <TextBox
            Margin="0,0,96,70"
            Grid.Row="1"
            TextWrapping="Wrap"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Right"
            Width="245"/>
        <TextBlock
            Margin="0,120,321,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Fax"
            VerticalAlignment="Top"
            HorizontalAlignment="Right"
            RenderTransformOrigin="-1.579,0.438"/>
        <TextBox
            Margin="0,140,96,136"
            Grid.Row="1"
            TextWrapping="Wrap"
            d:LayoutOverrides="Height"
            HorizontalAlignment="Right"
            Width="245"/>
        <TextBlock
            Margin="0,59,293,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Telefone"
            VerticalAlignment="Top"
            HorizontalAlignment="Right"/>
        <TextBox
            Margin="0,79,96,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            VerticalAlignment="Top"
            HorizontalAlignment="Right"
            Width="245"/>
        <Button
            Content="Adicionar"
            HorizontalAlignment="Right"
            Margin="0,0,8,8"
            Grid.Row="1"
            VerticalAlignment="Bottom"
            Width="75"/>
    </Grid>
</UserControl>

 

Criar a estrutura de directórios

image

 

De seguida adicionar ao directório Model uma nova classe com o nome Cliente.cs com a seguinte estrutura

namespace Silverlight_MVVM_Demo.Model
{
    public class Cliente
    {
        public string ClienteID { get; set; }
        public string Nome { get; set; }
        public string NIF { get; set; }
        public string Telefone { get; set; }
        public string Telemovel { get; set; }
        public string Fax { get; set; }

    }
}

Vamos adicionar duas classe base que implementam os Interfaces ICommand e INotifyPropertyChanged.

Classe RelayCommand

using System;
using System.Windows.Input;

namespace Silverlight_MVVM_Demo.ViewModel
{
    public class RelayCommand : ICommand
    {
        private Action<Object> _handler;
        public RelayCommand(Action<Object> handler)
        {
            _handler = handler;
        }

        private bool _isEnabled = true;
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                if (value != _isEnabled)
                {
                    _isEnabled = value;
                    if (CanExecuteChanged != null)
                    {
                        CanExecuteChanged(this, EventArgs.Empty);
                    }
                }
            }
        }

        public bool CanExecute(object parameter)
        {
            return IsEnabled;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            _handler(parameter);
        }
    }
}

Se seguirmos uma abordagem simplista, podemos olhar para o ICommand como o interface que permite o ViewModel receber notificações de eventos da View.

 

Classe ViewModelBase

namespace Silverlight_MVVM_Demo.ViewModel
{
    using System.ComponentModel;
    /*TODOS OS VIEWMODEL HERDAM DESTA CLASSE*/
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler  PropertyChanged;
        public void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

Vamos herdar todos os ViewModel da classe ViewModelBase para simplificar as notificações quando existem alterações nos conteúdos das propriedades, assim apenas vai ser necessário chamar o método RaisePropertyChanged(“NomeDaPropriedade”) para notificar o ViewModel e a View caso tal seja necessário.

 

De seguida criamos a classe de ViewModel

namespace Silverlight_MVVM_Demo.ViewModel
{
    using Model;
    using System.Collections.ObjectModel;

    public class MainPageViewModel : ViewModelBase
    {
        #region View Data Collection
        private ObservableCollection<Cliente> _ClientList;
        public ObservableCollection<Cliente> ClientList
        {
            get { return _ClientList; }
            set
            {
                _ClientList = value;
                /*Notificar alteração de conteúdo*/
                RaisePropertyChanged("ClientList");
            }
        }
        #endregion

        /*Propriedades utilizadas na View*/
        #region View Properties
        private string _ClientID;
        public string ClientID
        {
            get { return _ClientID; }
            set
            {
                _ClientID = value;
                /*Notificar alteração de conteúdo*/
                RaisePropertyChanged("ClientID");
            }
        }

        private string _ClientName;
        public string ClientName
        {
            get { return _ClientName; }
            set
            {
                _ClientName = value;
                /*Notificar alteração de conteúdo*/
                RaisePropertyChanged("ClientName");
            }
        }

        private string _ClientNIF;
        public string ClientNIF
        {
            get { return _ClientNIF; }
            set
            {
                _ClientNIF = value;
                /*Notificar alteração de conteúdo*/
                RaisePropertyChanged("ClientNIF");
            }
        }

        private string _ClientPhone;
        public string ClientPhone
        {
            get { return _ClientPhone; }
            set
            {
                _ClientPhone = value;
                /*Notificar alteração de conteúdo*/
                RaisePropertyChanged("ClientPhone");
            }
        }

        private string _ClientFax;
        public string ClientFax
        {
            get { return _ClientFax; }
            set
            {
                _ClientFax = value;
                /*Notificar alteração de conteúdo*/
                RaisePropertyChanged("ClientFax");
            }
        }

        private string _ClientCell;
        public string ClientCell
        {
            get { return _ClientCell; }
            set
            {
                _ClientCell = value;
                /*Notificar alteração de conteúdo*/
                RaisePropertyChanged("ClientCell");
            }
        }
        #endregion

        #region Commands
        public RelayCommand LoadClientData { get; set; }
        public RelayCommand SubmitForm { get; set; }
        #endregion

        public MainPageViewModel()
        {
            /*Comandos que o ViewModel vai reconhecer da View*/
            this.LoadClientData = new RelayCommand(MockClientList);
            this.SubmitForm = new RelayCommand(Submit);
        }

        /// <summary>
        /// Adiciona um novo cliente
        /// </summary>
        /// <param name="parameter"></param>
        protected void Submit(object parameter)
        {
            /*
             Como utilizamos as notificações quando
             é alterado o conteúdo de uma propriedade
             da View, já temos no ViewModel todos os
             dados.
             */
            var c = new Cliente
            {
                ClienteID = ClientID,
                Fax = ClientFax,
                NIF = ClientNIF,
                Nome = ClientName,
                Telefone = ClientPhone,
                Telemovel = ClientCell
            };

            ClientList.Add(c);

            ClientID = string.Empty;
            ClientFax = string.Empty;
            ClientCell = string.Empty;
            ClientName = string.Empty;
            ClientPhone = string.Empty;
            ClientNIF = string.Empty;
        }

        /// <summary>
        /// Dados iniciais da DataGrid
        /// </summary>
        /// <param name="parameter"></param>
        public void MockClientList(object parameter)
        {
            ClientList = new ObservableCollection<Cliente>();

            for (int i = 0; i < 3; i++)
            {
                var c = new Cliente{
                 ClienteID=i.ToString(),
                 Fax="123456789",
                 NIF="789456123",
                 Nome="Client "+i,
                 Telefone="5556623",
                 Telemovel="3237656511"
                };

                ClientList.Add(c);
            }
        }
    }
}

Agora ja temos definidas as três componentes do MVVM, apenas falta ligar a View com o ViewModel, e isso é conseguido através do Databinding, que o Silverlight nos proporciona para facilitar essa operação. Vamos então completar a página MainPage.xaml com os bindings.

<UserControl x:Class="Silverlight_MVVM_Demo.MainPage"
    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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    xmlns:vm="clr-namespace:Silverlight_MVVM_Demo.ViewModel"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <!--DATACONTEXT DEFINITION-->
    <UserControl.DataContext>
        <!--Link entre o ViewModel e a View-->
        <vm:MainPageViewModel/>
    </UserControl.DataContext>

    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <!--Quando a página é carregada é executado o comando LoadClientData-->
            <i:InvokeCommandAction Command="{Binding LoadClientData}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>

    <Grid x:Name="LayoutRoot" Background="Gray" Width="800" Height="600">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--DataGrid Definition-->
        <sdk:DataGrid Height="300" Grid.Row="0" BorderThickness="1"/>

        <!--Data Input Definition-->
        <sdk:Label
            Grid.Row="1"
            Height="28"
            HorizontalAlignment="Left"
            Margin="10,10,0,0"
            VerticalAlignment="Top"
            Width="171"
            Content="Adicionar novo cliente"
            FontFamily="Verdana"
            FontSize="13.333"
            FontWeight="Bold" />
        <TextBlock
            HorizontalAlignment="Left"
            Margin="80,59,0,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Nome"
            VerticalAlignment="Top"/>
        <TextBox
            Text="{Binding ClientName, Mode=TwoWay}"
            HorizontalAlignment="Left"
            Margin="80,79,0,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            VerticalAlignment="Top"
            Width="245"/>
        <TextBlock
            HorizontalAlignment="Left"
            Margin="80,120,0,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="NIF"
            VerticalAlignment="Top"/>
        <TextBox
            Text="{Binding ClientNIF, Mode=TwoWay}"
            HorizontalAlignment="Left"
            Margin="80,140,0,136"
            Grid.Row="1"
            TextWrapping="Wrap"
            Width="245"
            d:LayoutOverrides="Height"/>
        <TextBlock
            HorizontalAlignment="Left"
            Margin="80,0,0,98"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Telemóvel"
            VerticalAlignment="Bottom"/>
        <TextBox
            Text="{Binding ClientCell, Mode=TwoWay}"
            HorizontalAlignment="Left"
            Margin="80,0,0,70"
            Grid.Row="1"
            TextWrapping="Wrap"
            Width="245"
            VerticalAlignment="Bottom"/>
        <TextBlock
            Margin="0,0,229,98"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Número de Cliente"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Right"
            Width="111"/>
        <TextBox
            Text="{Binding ClientID, Mode=TwoWay}"
            Margin="0,0,96,70"
            Grid.Row="1"
            TextWrapping="Wrap"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Right"
            Width="245"/>
        <TextBlock
            Margin="0,120,321,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Fax"
            VerticalAlignment="Top"
            HorizontalAlignment="Right"
            RenderTransformOrigin="-1.579,0.438"/>
        <TextBox
            Text="{Binding ClientFax, Mode=TwoWay}"
            Margin="0,140,96,136"
            Grid.Row="1"
            TextWrapping="Wrap"
            d:LayoutOverrides="Height"
            HorizontalAlignment="Right"
            Width="245"/>
        <TextBlock
            Margin="0,59,293,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            Text="Telefone"
            VerticalAlignment="Top"
            HorizontalAlignment="Right"/>
        <TextBox
            Text="{Binding ClientPhone, Mode=TwoWay}"
            Margin="0,79,96,0"
            Grid.Row="1"
            TextWrapping="Wrap"
            VerticalAlignment="Top"
            HorizontalAlignment="Right"
            Width="245"/>
        <Button
            Content="Adicionar"
            HorizontalAlignment="Right"
            Margin="0,0,8,8"
            Grid.Row="1"
            VerticalAlignment="Bottom"
            Width="75">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!--Quando chamado o evento click, é executado o commando SubmitForm no ViewModel -->
                    <i:InvokeCommandAction Command="{Binding SubmitForm}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </Grid>
</UserControl>


E Finalmente temos a aplicação a funcionar com MVVM e nenhum código na View a não ser a definição do User Interface.

image

 

Solução VS2010 do projecto executado neste tutorial disponível aqui para download.

Referências:

http://www.silverlight.net/learn/videos/silverlight-4-videos/mvvm-introduction/

http://www.silverlight.net/learn/tutorials/silverlight-4/using-the-mvvm-pattern-in-silverlight-applications/

http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090016

Visual Studio 2010 XAML Editor–Quick Tips #4

Sábado, Outubro 9th, 2010

xamllogo

O Visual Studio 2010 é sem dúvida alguma uma ferramenta excelente de trabalho. Já tive oportunidade de trabalhar com esta ferramenta em alguns projectos e até agora tem sido uma boa experiência.

Neste estou envolvido num projecto no qual que utilizo Visual Studio 2010, Silverlight e Expression Blend e tenho-me deparado com um problema em concreto, no Editor de XAML do Visual Studio 2010, cada vez que abro um ficheiro XAML, o editor abre por defeito em modo split (Editor/Preview).

xaml_screen_02

O problema é que o preview demora constantemente vários segundos a carregar o ficheiro e a executar o preview, quando na grande maioria das ocasiões não me interessa pré-visualizar o documento, apenas tenho interesse em editar o XAML.

É possível solucionar este problema, selecionar na barra de ferramentas do Visual Studio 2010 Tools –> Options e depois seleccionar como indica a imagem  Text Editor –> XAML –> Miscellaneous

xaml_options

 

 

 

 

 

 

 

 

 

Na Default View, selecionar “Always open documents in full XAML view” e a partir desse momento, sempre que abrir um documento XAML, este vai ser apresentado sempre em modo Markup.

Tech Days 2010

Terça-feira, Fevereiro 9th, 2010

techdays2010_logo

Nos dias 20 a 22 de Abril de 2010, a Microsoft vai realizar no Lagoas Park o Tech Days 2010.

O evento vai contar com a presença de cerca de 40 oradores portugueses e estrangeiros para assegurar as mais de 90 sessões e 40 laboratórios técnicos sobre XNA, Visual Studio 2010, Expression Studio, Sharepoint, Silverlight, WPF, Office, Exchange, SQL Server, Windows 7, Windows server 2008 e Windows Azure.

Também vão existir sessões direccionadas à arquitectura de software, Best Practices no desenvolvimento de software e User Exprerience (UX).

Este é o maior evento em Portugal na área das TI, um evento a não perder neste ano de 2010.

Como não podia deixar de ser, a comunidade NetPonto vai estar representada no evento.

Até dia 5 de Março existe um desconto de 75€ na inscrição.   

Mais informações em: http://www.techdays2010.com/