Desenvolvimento - Silverlight

Criando controles customizados em Silverlight

Neste artigo irei abordar como fazer um botão customizado simples, porém com todas suas funcionalidades necessárias.

por Lucas Defacio



Introdução

Uma das grandes vantagens do Silverlight 2 é a criação de controles customizados. Estes controles podem herdar suas propriedades a partir de outro controle (como um botão, por exemplo). A customização de componentes ajuda na padronização de sua aplicação e até mesmo na utilização de um recurso para várias outras aplicações. Neste artigo irei abordar como fazer um botão customizado simples, porém com todas suas funcionalidades necessárias. Este conceito também poderá ser utilizado para criação de componentes complexos.

Criando a Classe

Inicialmente criaremos um novo projeto do tipo Silverlight Class Library pelo Visual Studio 2008. Para isto siga os passos File > New Project > Silverlight > Silverlight Class Library. Para este exemplo, nomearei o novo projeto como SL_CustomControl.

Automaticamente será gerada uma classe nomeada como Class1.cs. Podemos renomear esta classe para CustomButton.cs (o Visual Studio deverá perguntar se você deseja atualizar as referências desta classe, clique em Sim).

Está classe deverá ser derivada da ContentControl. Desta forma faremos nosso controle CustomButton herdar propriedades de um controle que permita um conteúdo (no caso, um botão). O Quadro 1 ilustra este procedimento.

namespace SL_CustomControl

{

public class CustomButton : ContentControl

{

}

}

Quadro 1: Classe CustomButton derivando da ContentControl

Aplicando temas

Com nossa classe criada, criaremos uma estrutura para poder atribuir como o estilo do nosso CustomButton. Para isto, adicionarei uma pasta nomeada como Themes e, dentro desta pasta, criarei um novo item do tipo Silverlight User Control nomeado como generic.xaml. É importante respeitar esta nomenclaruta, pois, como padrão, o Silverlight buscará o estilo definido para seu controle dentro deste arquivo. A estrutura do nosso projeto será representada pela Figura 1.

Figura 1: Estrutura do nosso projeto

Será dentro da generic.xaml que definiremos todos os estilos e efeitos para nosso CustomButton. Será necessário mudar sua estrutura base de UserControl para ResourceDictionary. Devemos referenciar o nosso próprio projeto SL_CustomControl para poder definir a propriedade TargetType de nosso estilo como nosso botão customizado.

Criarei um estilo simples para nosso botão composto por um Grid, um Rectangle e um TextBlock. O Quadro 2 ilustra a estrutura do nosso arquivo generic.xaml.

<ResourceDictionary

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:custom="clr-namespace:SL_CustomControl;assembly=SL_CustomControl">

<Style TargetType="custom:CustomButton">

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="custom:CustomButton">

<Grid x:Name="RootElement">

<Grid Width="150" Height="100" Cursor="Hand" Margin="0">

<Rectangle x:Name="RecBackground" RadiusX="15" RadiusY="15" Stroke="#FF666666" StrokeThickness="2" Fill="DarkGray" />

<TextBlock x:Name="txtContent" Text="My button" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="12" />

</Grid>

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</ResourceDictionary>

Quadro 2: generic.xaml

Nota: Será necessário remover o método InitializeComponent(); gerado automaticamente pelo VisualStudio.

Com o estilo criado, precisamos defini-lo como DefaultStyleKey em nosso CustomButton. Isto deverá ser feito dentro da classe CustomButton.cs (Quadro 3).

namespace SL_CustomControl

{

public class CustomButton : ContentControl

{

public CustomButton()

{

this.DefaultStyleKey = typeof(CustomButton);

}

}

}

Quadro 3: definindo o estilo criado em nosso generic.xaml como padrão utilizado pelo botão.

Neste ponto do projeto, devemos definir como serão compilados nossa classe e nosso arquivo generic.xaml. Na aba Solution Explorer selecione a classe e certifique-se que a propriedade Build Action esteja definida como Compile. Repita este mesmo passo para o arquivo generic.xaml, porém a mesma propriedade deverá ser definida como Resource.

Referenciando os controles customizados

Com estes passos feitos, já podemos testar o resultado de nosso botão customizado. Porém não conseguiremos testar neste mesmo projeto por termos definido como Class Library. Será necessário, então, compilar (Build) nosso projeto e criar um novo projeto do tipo Silverlight Application. Nomearei este novo projeto como SilverlightTestControl.

O próximo passo será adicionar referência à nossa dll gerada após a compilação do projeto SL_CustomControl. Para isto, clique com o botão direito em cima da pasta References dentro do nosso projeto Silverlight e selecione a opção Add Reference. Na aba Browse procure a dll gerada e clique em Ok.

Nota: por padrão a dll será gerada dentro da pasta Bin > Debug.

Figura 2: Referenciando nossa dll gerada pelo projeto SL_CustomControl.

Com a dll adicionada como referência em nosso projeto, podemos trabalhar com o nosso controle customizado CustomButton. Em nosso Page.xaml será necessário referenciar a dll adicionada ao nosso projeto. Utilizarei a tag custom para poder acessar os controles contidos dentro da nossa assembly. Desta forma, para acessar nosso CustomButton devemos iniciar com a tag control. O Quadro 4 representa este procedimento.

<UserControl x:Class="SilverlightTestControl.Page"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:custom="clr-namespace:SL_CustomControl;assembly=SL_CustomControl"

Width="400" Height="300">

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

<custom:CustomButton />

</Grid>

</UserControl>

Quadro 4: Representação do nosso UserControl Page.xaml

Customizando atributos

Neste ponto, nosso controle já está pronto, porém você verá que existem limitações nos atributos. Não é possível alterar algumas propriedades como width, height, text. Uma solução é definir estas propriedades pelo TemplateBinding e definir uma padrão, caso eles não sejam definidos pelo desenvolvedor. Este procedimento deverá ser realizado dentro do generic.xaml. O Quadro 5 representa esta implementação.

<Style TargetType="custom:CustomButton">

<Setter Property="Width" Value="150" />

<Setter Property="Height" Value="100" />

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="custom:CustomButton">

<Grid x:Name="RootElement">

<Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Cursor="Hand" Margin="0">

<Rectangle x:Name="RecBackground" RadiusX="15" RadiusY="15" Stroke="#FF666666" StrokeThickness="2" Fill="DarkGray" />

<TextBlock x:Name="txtContent" Text="My button" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="12" />

</Grid>

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

Quadro 5: implementando as propriedades width e height de forma que possam ser alteradas pelo desenvolvedor.

Desta forma, dentro do projeto SilverlightTestControl você poderá definir para o CustomButton as propriedades Width e Height de acordo com sua necessidade. Quando não definidas, ela assume o valor padrão apontado dentro da generic.xaml.

A propriedade Text deverá ser tratada de forma diferente. Precisamos habilitar nosso CustomButton para receber esta propriedade para poder definir como TemplateBinding. Este procedimento deverá ser realizado na classe CustomButton.cs.

Para isto, precisaremos criar uma propriedade do tipo string, aplica-la em nosso CustomButton e registra-la como Text. Então basta apenas receber este valor e aplica-lo na propriedade TextProperty. O Quadro 6 ilustra este procedimento.

public class CustomButton : ContentControl

{

public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CustomButton), null);

public CustomButton()

{

this.DefaultStyleKey = typeof(CustomButton);

}

public string Text

{

get

{

return (string)GetValue(TextProperty);

}

set

{

SetValue(TextProperty, value);

}

}

}

Quadro 6: Implementação da propriedade text em nosso CustomButton.

Agora o podemos definir um texto padrão e aplica-lo como TemplateBinding em nosso arquivo generic.xaml. (Quadro 7).

<Style TargetType="custom:CustomButton">

<Setter Property="Width" Value="150" />

<Setter Property="Height" Value="100" />

<Setter Property="Text" Value="Custom Button" />

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="custom:CustomButton">

<Grid x:Name="RootElement">

<Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Cursor="Hand" Margin="0">

<Rectangle x:Name="RecBackground" RadiusX="15" RadiusY="15" Stroke="#FF666666" StrokeThickness="2" Fill="DarkGray" />

<TextBlock x:Name="txtContent" Text="{TemplateBinding Text}" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="12" />

</Grid>

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

Quadro 7: implementando a propriedade Text

Para testar basta compilar novamente o projeto SL_CustomControl que automaticamente a dll será atualizada. Assim, podemos definir a propriedade Width, Height e Text em nosso CustomButton de forma que, quando não identificadas, assumem um valor padrão.

<custom:CustomButton Width="100" Height="30" Text="Silverlight" />

Quadro 8: definindo valores para as propriedades customizadas.

Eventos Customizados

O próximo passo deste tutorial será a criação de um evento customizado para o nosso CustomButton. Este evento será criado dentro da nossa classe CustomButton.cs e poderá ser acessado como os demais eventos através da aplicação Silverlight. Neste exemplo criarei um evento Click a partir do evento MouseLeftButtonUp. Em nossa classe primeiro devo criar o evento do tipo RoutedEventHandler e o nomearei como Click. Depois basta implementar a ação deste evento juntamente com a ação do MouseLeftButtonUp. O Quadro 9 ilustra nossa classe com o evento.

public class CustomButton : ContentControl

{

public event RoutedEventHandler Click;

public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CustomButton), null);

public CustomButton()

{

this.DefaultStyleKey = typeof(CustomButton);

this.MouseLeftButtonUp += new MouseButtonEventHandler(CustomButton_MouseLeftButtonUp);

}

void CustomButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

{

if(Click != null)

Click(this, new RoutedEventArgs());

}

public string Text

{

get

{

return (string)GetValue(TextProperty);

}

set

{

SetValue(TextProperty, value);

}

}

}

Quadro 9: Implementando o evento Click em nossa classe

Desta forma, podemos programar este evento da mesma forma padrão em nosso CustomButton. O Quadro 10 e o Quadro 11 apresentam o acesso ao evento nos arquivos .xaml e .xaml.cs.

<custom:CustomButton Click="CustomButton_Click" Width="100" Height="30" Text="Silverlight" />

Quadro 10: .xaml

private void CustomButton_Click(object sender, RoutedEventArgs e)

{

HtmlPage.Window.Alert("Silverlight...");

}

Quadro 11: .xaml.cs

Aplicando VisualStateManager

Outro recurso que podemos utilizar em nosso botão é o VisualStateManager. Este recurso possui como finalidade gerenciar o estado visual de nosso componente a partir de eventos. Em nosso exemplo irei aplicar um efeito de cor quando o usuário passar o mouse sobre nosso CustomButton. Para começar a tralhar com o VisualStateManager devemos referenciar o System.Windows em nossa generic.xaml (Quadro 12). Depois será necessário criar os VisualStates que serão acessados pelos eventos. Trabalharemos com dois tipos de estados visuais diferentes: Over (quando o usuário passar o mouse sobre o nosso botão) e Normal (quando o mouse não estiver sobre o botão). Dentro do estado Over devemos criar nossa Storyboard responsável em manipular a cor do retângulo de fundo do nosso CustomButton. O Quadro 13 representa como implementei o VisualState na generic.xaml.

xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"

Quadro 12: referência ao System.Windows para utilizar o recurso VisualStateManager.

<Style TargetType="custom:CustomButton">

<Setter Property="Width" Value="150" />

<Setter Property="Height" Value="100" />

<Setter Property="Text" Value="Custom Button" />

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="custom:CustomButton">

<Grid x:Name="RootElement">

<vsm:VisualStateManager.VisualStateGroups>

<vsm:VisualStateGroup x:Name="CommonStates">

<vsm:VisualStateGroup.Transitions>

<vsm:VisualTransition To="Normal"

GeneratedDuration="0:0:0.3" />

<vsm:VisualTransition To="Over"

GeneratedDuration="0:0:0.3" />

</vsm:VisualStateGroup.Transitions>

<vsm:VisualState x:Name="Normal" />

<vsm:VisualState x:Name="Over">

<Storyboard>

<ColorAnimation To="LightGray" Duration="0" Storyboard.TargetName="RecBackground" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" />

</Storyboard>

</vsm:VisualState>

</vsm:VisualStateGroup>

</vsm:VisualStateManager.VisualStateGroups>

<Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Cursor="Hand" Margin="0">

<Rectangle x:Name="RecBackground" RadiusX="15" RadiusY="15" Stroke="#FF666666" StrokeThickness="2" Fill="DarkGray" />

<TextBlock x:Name="txtContent" Text="{TemplateBinding Text}" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="12" />

</Grid>

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

Quadro 13: implementação do VisualStateManager na generic.xaml

Nota: A propriedade Storyboard.TargetName em nosso ColorAnimation deverá receber o mesmo nome do retângulo.

Para finalizar, devemos criar os eventos MouseEnter e MouseLeave na classe CustomButton.cs para poder atribuir o seu estado visual. Este passo será representado pelo Quadro 14.

public CustomButton()

{

this.DefaultStyleKey = typeof(CustomButton);

this.MouseLeftButtonUp += new MouseButtonEventHandler(CustomButton_MouseLeftButtonUp);

MouseEnter += new MouseEventHandler(CustomButton_MouseEnter);

MouseLeave += new MouseEventHandler(CustomButton_MouseLeave);

}

void CustomButton_MouseLeave(object sender, MouseEventArgs e)

{

VisualStateManager.GoToState(this, "Normal", true);

}

void CustomButton_MouseEnter(object sender, MouseEventArgs e)

{

VisualStateManager.GoToState(this, "Over", true);

}

Quadro 14: eventos MouseEnter e MouseLeave na classe CustomButton.cs

Conclusão

Apesar de inicialmente parecer complicado, a customização de componentes não possui segredo nenhum. O conceito será o mesmo sempre, a única diferença é a lógica utilizada a partir da sua necessidade. O Silverlight 2 nos possibilita uma customização simples e com uma sofisticação típica de controles Rich Interface Application.

Lucas Defacio

Lucas Defacio - Estudante de Sistemas de Informação, atua como designer e desenvolvedor em Rich Interface Applications (RIA) no Flextronics Institute of Technology (FIT) em Sorocaba/SP. É Moderador do fórum de Silverlight no Portal MSDN Brasil e fundador do portal Brasilverlight (http://www.brasilverlight.com.br/), busca aprimorar a experiência dos usuários com sites interativos e promover a tecnologia Silverlight.