domingo, 27 de março de 2011

Classe base para ViewModels - Padrão MVVM - Silverlight

Para facilitar a utilização do padrão MVVM, devemos utilizar uma classe base para as viewmodels. Segue abaixo o código que uso para minha classe ViewModelBase (http://pastebin.com/h5WD4V6q):



Toda vez que alteramos um valor de uma propriedade ou comando em uma viewmodel, devemos disparar um método NotifyOfPropertyChanged (muitos frameworks chamam de RaiseProperyChanged ou nomes parecidos).

Isso serve para notificar à view que o valor da propriedade foi alterado e que o componente visual relacionado àquela propriedade deve ter seu conteúdo atualizado (Binding Mode=OneWay/TwoWay).

Uma classe simples que implementa essa classe base ficaria assim:



Código comentado

sábado, 19 de março de 2011

Continuando o artigo anterior – Utilizando MEF com Silverlight na prática

No artigo anterior, criamos dois novos UserControl’s com atributos [Export] e mostrei como visualizá-los dentro da MainPage usando MEF.

Uma vez que o MEF criou uma instância de uma classe que foi exportada, toda vez que solicitarmos novamente ao catálogo essa mesma classe, será retornada a mesma instância.

Para deixar isso bem claro, coloquei um textblock que informa a data e hora em que o MEF criou o objeto. No caso de precisarmos atualizar essa data sem criar uma nova instância, teremos que simular um evento que indica quando aquele usercontrol foi solicitado novamente ao Catálogo.

Uma solução para isso é criarmos uma interface que chama um método para realizar essa tarefa de atualizar a data/hora do textblock. Mãos à obra!

Vamos criar duas interfaces: uma interface chamada IMainPage que conterá um método MostrarTela(string contrato), e outra interface chamada ITela que conterá um método Activate().

Crie uma pasta chamada “Contratos” dentro do projeto silverlight e adicione essas duas interfaces da seguinte maneira:

//interface IMainPage
namespace SilverlightApplication1.contracts
{
    public interface IMainPage
    {
        void MostrarTela(string contrato);
    }
}

//interface ITela
namespace SilverlightApplication1.contracts
{
    public interface ITela
    {
        void Activate();
    }
}


Abra o arquivo MainPage.xaml.cs e implemente a interface IMainPage. Os eventos do Click dos botões também devem ser alterados para chamar o método MostrarTela passando a string do contrato desejado como parâmetro. O conteúdo da classe inteira ficará dessa forma:

[Export("MainPageExport", typeof(UserControl))]
public partial class MainPage : UserControl, IMainPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void Tela1_Click(object sender, RoutedEventArgs e)
    {
        MostrarTela("Tela1");
    }

    private void Tela2_Click(object sender, RoutedEventArgs e)
    {
        MostrarTela("Tela2");
    }

    #region Implementation of IMainPage
    public void MostrarTela(string contrato)
    {
        ITela tela = CatalogoMef.GetInstance<UserControl>(contrato) as ITela;
        if (tela != null)
        {
            mainPageContentControl.Content = tela;
            tela.Activate();
        }
    }
    #endregion
}

Repare na implementação do método MostrarTela que chamamos a tela com o contrato solicitado através do catálogo do MEF. Se o MEF encontrou a tela, a variável “tela” não será nula, então setamos o conteúdo do controle MainPageContentControl para o valor retornado pelo MEF e em seguida, chamamos o método Activate() da interface ITela. Esse método será implementado nos UserControl’s Tela1.xaml.cs e Tela2.xaml.cs. Veja como deve ficar:

[Export("Tela1", typeof(UserControl))]
public partial class Tela1 : UserControl, ITela
{
    public Tela1()
    {
        InitializeComponent();
    }

    #region Implementation of ITela

    public void Activate()
    {
        dataHoraCriacao.Text = DateTime.Now.ToString();
    }

    #endregion
}

Faça a mesma alteração no arquivo Tela2.xaml.cs:

[Export("Tela2", typeof(UserControl))]
public partial class Tela2 : UserControl, ITela
{
    public Tela2()
    {
        InitializeComponent();
    }

    #region Implementation of ITela

    public void Activate()
    {
        dataHoraCriacao.Text = DateTime.Now.ToString();
    }

    #endregion
}

Dessa forma, ao implementarmos a interface ITela que criamos na pasta “Contratos”, somos obrigados a implementar o método Activate. Nesse método Activate, será feita a atualização da Data e Hora do textblock.

Execute a aplicação e você verá que agora a data/hora sempre estarão atualizadas.

Neste artigo podemos concluir que através do auxílio de interfaces, podemos controlar e manter o estado das telas que foram instanciadas.

Para finalizar vamos implementar um método DeActivate() que será chamado quando a tela ficar invisível.
Para isto, altere a interface ITela e adicione um método DeActivate() da mesma forma como declarado o método Activate():

//interface ITela
namespace SilverlightApplication1.contracts
{
    public interface ITela
    {
        //método chamado quando a tela ficar vísivel
        void Activate();

        //método chamado quando a tela ficar invisível
        void DeActivate();
    }
}

Como adicionamos um método novo na interface ITela, agora precisamos implementar esse método na Tela1 e Tela2:

public void DeActivate()
{
    dataHoraCriacao.Text = "";
}

Pra concluir essa funcionalidade, vamos adicionar uma propriedade na interface IMainPage, que será uma referência à tela que está Ativa no momento:

//interface IMainPage
namespace SilverlightApplication1.contracts
{
    public interface IMainPage
    {
        //propriedade que vai guardar a referência à tela que estiver ativa
        ITela TelaAtiva { get; set; }
        void MostrarTela(string contrato);
    }
}

Em MainPage.xaml.cs, a classe toda ficará dessa maneira:

[Export("MainPageExport", typeof(UserControl))]
public partial class MainPage : UserControl, IMainPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void Tela1_Click(object sender, RoutedEventArgs e)
    {
        MostrarTela("Tela1");
    }

    private void Tela2_Click(object sender, RoutedEventArgs e)
    {
        MostrarTela("Tela2");
    }

    #region Implementation of IMainPage

    public ITela TelaAtiva { get; set; }

    public void MostrarTela(string contrato)
    {
        if (TelaAtiva != null) TelaAtiva.DeActivate();
        TelaAtiva = CatalogoMef.GetInstance<UserControl>(contrato) as ITela;
        if (TelaAtiva == null) return;
        mainPageContentControl.Content = TelaAtiva;
        TelaAtiva.Activate();
    }
    #endregion
}

Toda vez que uma nova tela for chamada, a tela ativa atual irá executar o método DeActivate(), nesse método poderemos fazer o que acharmos necessário quando uma tela ficar invisível. Em seguida, a nova tela passa a ser ativa e então o método Activate() é chamado.


Código fonte do projeto aqui

Utilizando o MEF com Silverlight na prática

Utilizo o MEF constantemente para instanciar e apresentar as telas (UserControls/XAML) dentro de uma aplicação Silverlight. Dessa forma não preciso me preocupar em controlar instâncias de objetos (usando o new) pois o MEF por convenção trabalha no modo Singleton.

Ao final deste artigo, teremos uma aplicação simples que nos mostrará como visualizar diferentes telas que estão disponíveis em um Catálogo MEF:

printscreen da aplicação silverlight

Para ver a aplicação funcionando, clique aqui: http://dl.dropbox.com/u/14150556/ArtigoMef/SilverlightApplication1TestPage.html

Há muito material na internet para explicar o que é o MEF, então neste artigo vou apenas mostrar na prática como utilizar o MEF para criar e apresentar novas telas.

Crie uma nova aplicação Silverlight 4 e adicione as seguintes referências aos assemblies do MEF: 
  • System.ComponentModel.Composition  
  • System.ComponentModel.Composition.Initialization
Criando um Serviço de Catálogo

Para auxiliar a criação do catálogo MEF costumo optar por criar uma classe estática que irá nos auxilixiar com alguns métodos. Para isso, crie uma nova classe dentro do projeto Silverlight. Vou chamar essa classe de “CatalogoMef.cs”. Segue o conteúdo da classe com comentários:

public static class CatalogoMef
{
    private static readonly AggregateCatalog Catalog;
    private static readonly CompositionContainer Container;

    public static void InicializaCatalogo()
    {
        //esse método apenas força a execução do construtor static
        //e deve ser chamado na inicialização em App.xaml.cs
    }

    static CatalogoMef()
    {
        //cria um novo catálogo agregado
        Catalog = new AggregateCatalog();
        //deploymentCatalog representa o próprio arquivo XAP da aplicação Silverlight
        var deploymentCatalog = new DeploymentCatalog();
        //adiciona o deploymentCatalog ao catálogo agregado
        Catalog.Catalogs.Add(deploymentCatalog);
        Container = new CompositionContainer(Catalog);
        CompositionHost.Initialize(Container);
    }

    public static T GetInstance<T>(string contract = null)
    {
        //esse método será utilizado quando for solicitada uma instância de um determinado tipo
        return !string.IsNullOrEmpty(contract) ?
            Container.GetExportedValueOrDefault<T>(contract) :
            Container.GetExportedValueOrDefault<T>();
    }
}

Temos agora uma classe que irá nos auxiliar com o MEF.

MEF na prática

Abra o arquivo MainPage.xaml.cs e decore a classe com um atributo [Export]:

[Export("MainPageExport", typeof(UserControl))]
public partial class MainPage : UserControl

Esse atributo fará com que a classe MainPage do tipo UserControl seja exportada para o catálogo com uma string identificando-a, nesse caso, “MainPageExport”. Essa string é importante para identificar exclusivamente uma classe exportada.

O catálogo do MEF agora vai conter esse UserControl para ser utilizado a qualquer momento quando solicitado. Nesse caso, vamos atribuir o RootVisual da aplicação com o MEF. Abra o arquivo App.xaml.cs e no evento StartUp, altere para ficar da seguinte maneira:

private void Application_Startup(object sender, StartupEventArgs e)
{
     CatalogoMef.InicializaCatalogo();
     this.RootVisual = CatalogoMef.GetInstance<UserControl>("MainPageExport");
}

Passamos o parâmetro genérico <UserControl> para o método GetInstance, pois queremos uma instância do tipo UserControl que foi exportada com a string “MainPageExport”.

Repare que não precisamos mais criar um novo UserControl com a palavra reservada new. O MEF mantém o controle sobre as instâncias de seu catálogo, e caso já exista uma instância do tipo solicitado, ele devolve apenas a mesma referência, evitando que fique na memória duas instâncias iguais, e caso não haja nenhum instância criada, será criada uma nova instância e sua referência será retornada.

Agora vamos criar uma região dentro de MainPage que será usaremos para importar UserControl.

Em  MainPage.Xaml coloque o seguinte conteúdo:

<Grid x:Name="LayoutRoot" Background="White">
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="MainPage" />
            <Button Content="Tela1" Click="Tela1_Click" />
            <Button Content="Tela2" Click="Tela2_Click" />
        </StackPanel>

        <ContentControl Name="mainPageContentControl" />
    </StackPanel>
</Grid>

Repare que agora temos dois botões com eventos Click Tela1_Click e Tela2_Click. Precisamos adicionar os manipuladores de evento no codebehind (MainPage.xaml.cs):

private void Tela1_Click(object sender, RoutedEventArgs e)
{
    mainPageContentControl.Content = CatalogoMef.GetInstance<UserControl>("Tela1");
}

private void Tela2_Click(object sender, RoutedEventArgs e)
{
    mainPageContentControl.Content = CatalogoMef.GetInstance<UserControl>("Tela2");
}

Quando o usuário clicar em um botão, setaremos o conteúdo interno do componente chamado mainPageContentControl para o valor de um outro userControl. Nesse momento ao rodar a aplicação, nada irá acontecer, pois não criamos ainda um UserControl com o atributo [Export] com a string “Tela1”. É o que faremos agora ! Adicione um novo “Silverlight User Control” com o nome Tela1. No arquivo Tela1.xaml, coloque o seguinte conteúdo:

<Grid x:Name="LayoutRoot" Background="Green">
    <StackPanel>
        <TextBlock Text="Esta é a tela 1!!" FontSize="18" />
        <TextBlock Name="dataHoraCriacao" />
    </StackPanel>
</Grid>

Repare que coloquei um TextBlock nomeado “dataHoraCriacao”. Vamos setar o texto desse controle para a data e hora em que a tela foi criada. Para isso no codebehind Tela1.xaml.cs coloque o seguinte código:

[Export("Tela1", typeof(UserControl))]
public partial class Tela1 : UserControl
{
    public Tela1()
    {
       InitializeComponent();
       dataHoraCriacao.Text = DateTime.Now.ToString();
    }
}

A razão pela qual coloquei esse textblock com o valor da data e hora de criação é para provar que o MEF retornará sempre a mesma instância que foi criada quando for apresentada novamente a Tela1.

Vamos adicionar mais um usercontrol que será a tela2, faça da mesma maneira anterior mas com o seguinte conteúdo agora em Tela2.xaml:

<Grid x:Name="LayoutRoot" Background="Aqua">
    <StackPanel>
        <TextBlock Text="Esta é a tela 2!!" FontSize="18" />
        <TextBlock Name="dataHoraCriacao" />
    </StackPanel>
</Grid>

Para diferenciar a Tela1 da Tela2, repare as cores de background são diferentes.

No codebehind Tela2.xaml.cs coloque o seguinte código:

[Export("Tela2", typeof(UserControl))]
public partial class Tela2 : UserControl
{
   public Tela2()
   {
       InitializeComponent();
       dataHoraCriacao.Text = DateTime.Now.ToString();
   }
}

Agora compile e execute a aplicação. Você verá que a mainpage possui 2 botões, sendo que ao clicar no primeiro a Tela1 será apresentada, e o segundo chamará a Tela2. Clique alternadamente nos botões e repare na data e hora de criação, que permanece sempre a mesma data e hora no momento em que foi criado pelo MEF.

Espero que tenham gostado do artigo. Estou preparando um próximo artigo em que mostrarei como utilizar interfaces com MEF e aplicar o conceito de MVVM para transições de telas.


Código fonte do projeto usado neste artigo: