Entendendo DependencyProperty

Olá pessoal! Hoje vamos ver um pouco sobre DependencyProperty, um recurso muito interessante de ser explorado pelas ferramentas da Microsoft que fornecem recursos para atribuição de propriedades nos seus controles visuais através de Binding – como WPF, Silverlight e WF.

Grosso modo, a função da DependencyProperty é prover o mesmo mecanismo de uma propriedade CLR comum, porém de forma visual e enriquecida com funcionalidades que são muito uteis. Dentro estas funcionalidades, temos desde atribuição de valor padrão, como validação em tempo real e notificação de alteração de valor.

Além disto, uma propriedade comum trabalha como um intermediário que configura (set) ou resgata (get) um membro privado da própria classe – que armazena estes valores. Já uma DependencyProperty varia o local onde seu valor será armazenado dependendo do estado atual. O modo como este local será definido é chamado de Value Precedence, que é uma espécie de polimorfismo de atribuição de valores – onde a precedência de maior nível tem sempre a prioridade para armazenar o valor. Vamos a um exemplo?

Dentro de uma xaml, observe a seguinte sintaxe:

<UserControl.Resources>
    <Style TargetType="TextBlock">
 <Setter Property="Foreground" Value="Blue" />
     </Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
 <TextBlock x:Name="NomeTextBlock" Foreground="Black">
    <TextBlock.Style>
      <Style TargetType="TextBlock">
        <Setter Property="Foreground" Value="Green" />
      </Style>
    </TextBlock.Style>
  </TextBlock>
</Grid> 

Qual será a cor do TextBlock? Será azul, preto ou verde?

Conforme a regra da precedência de valor, o nível mais alto tem prioridade sobre o mais baixo. Neste caso estamos falando no valor atribuído na declaração do objeto TextBlock, ou seja, a sua cor será preta (Local Value).

Mas e se arrancarmos esta propriedade, será que a cor ficará verde ou azul? Acertou quem respondeu que é verde – afinal na precedência, o estilo definido no próprio objeto é mais alto do que o definido fora do objeto.

Para saber qual é a ordem dos níveis precedências, basta seguir a seguinte tabela (ordenada do nível mais alto para o mais baixo):

  1. Por coerção e animação: Os dois operam em nível de Base Value. Durante uma animação, por razões óbvias, a sua precedência deve ser respeitada. Imagine se você tem uma animação que altera o Background de uma propriedade em um determinado período de tempo: se a animação não tivesse prioridade, o fundo não seria alterado, logo não haveria animação.
  2. Por Local Value
  3. Por Template: para propriedades criadas dentro de um template ou controle. Dentro deles também há uma ordem de precedência: a) por trigger (mesma razão da animação); b) por configuração direta de propriedade;
  4. Por estilos (Style Setters)
  5. Por temas
  6. Por herança
  7. Por valor padrão

Entendido este ponto, é importante frisar o seguinte: só é interessante utilizar DependencyProperty se você for fornecer uma propriedade pública para servir de alvo para o preenchimento através de Binding ou através de uma animação. Caso contrário, não há utilidade para sua utilização.

Como nestas tecnologias praticamente não há como escapar do Binding, seu entendimento é quase que essencial.

Mas vamos ver como isto funciona na prática. Crie um projeto do tipo Silverlight Application e adicione um Silverlight User Control chamado NomeCompletoControl.

No XAML, inclua um TextBlock da seguinte maneira:

<Grid x:Name=”LayoutRoot” Background=”White”>
    <TextBlock x:Name=”MeuTextBlock” FontSize=”20″ Foreground=”Blue” />
</Grid>

Vá agora no NomeCompletoControl.cs e adicione as seguintes propriedades:

public partial class NomeCompletoControl : UserControl {
        public NomeCompletoControl() {
         InitializeComponent();
          this.Loaded += (s, ea) => {
              this.MeuTextBlock.Text = String.Concat(Nome, " ", Sobrenome);
         };
     }
       private string  _nome = default(string),
         _sobrenome = default(string);
       public string Nome {
           get { return this._nome; }
           set { this._nome = value; }
      }
       public string Sobrenome {
           get { return this._sobrenome; }
           set { this._sobrenome = value; }
      }
 } 

A intenção é clara: queremos apenas um controle que concatene as informações de Nome e Sobrenome.

Vá no XAML da MainPage, abra a ToolBox e veja que o controle que acabamos de criar já está nela (se não estiver, basta dar um Build no projeto que ele irá aparecer). Arraste ele para o meio da Grid (automaticamente o Visual Studio adiciona o namespace no elemento raiz do XAML – chamado de UserControl neste contexto. Provavelmente o nome que ele criou para o namespace é “my”).

Depois configure as propriedades Nome e Sobrenome, de modo que seu documento deverá ficar mais ou menos assim:

<Grid x:Name="LayoutRoot" Background="White">
      <my:NomeCompletoControl Nome="José" Sobrenome="Saramago" />
</Grid> 

Se você executar o código, você terá sucesso (conforme o esperado).

Agora vamos passar os mesmos parâmetros como Binding do controle. Para isto vamos adicionar uma nova classe chamada “MeuRepositorio.cs” e vamos adicionar as seguintes propriedades:

public class MeuRepositorio {
       public string MeuNome { getset; }
       public string MeuSobrenome { getset; } 
} 

Agora vamos voltar o MainPage.xaml e alterar as propriedades do nosso controle, do modo que fique da seguinte maneira:

<Grid x:Name="LayoutRoot" Background="White">
    <my:NomeCompletoControl
         x:Name="MeuControle"
         Nome="{Binding MeuNome}"
         Sobrenome="{Binding MeuSobrenome}" /> 
</Grid> 

Agora vamos ao MainPage.xaml.cs (ou seja, no code behind) e vamos adicionar o seguinte:

public MainPage() {
     InitializeComponent();
     var repositorio = new MeuRepositorio {
          MeuNome = "José",
          MeuSobrenome = "Saramago" };
     this.MeuControle.DataContext = repositorio;
} 

Se você executar o código da forma como está irá ocorrer uma exceção. Isto por que nós estamos fazendo Data Binding, entretanto o nosso controle (NomeCompletoControl) não suporta Data Bound. É aí que entram as propriedades do tipo DependencyProperty. Vamos alterar o code behind de nosso controle. Para isto entre na classe NomeCompletoControl.xaml.cs e deixe do seguinte modo.

public partial class NomeCompletoControl : UserControl {
     public NomeCompletoControl() {
         InitializeComponent();
         this.Loaded += (s, ea) => {
             this.MeuTextBlock.Text = String.Concat(Nome, " ", Sobrenome);
         };
     }
     private static readonly DependencyProperty
        NomeProperty = DependencyProperty.Register(
             "Nome",
             typeof(string),
             typeof(NomeCompletoControl),
             null),
         SobrenomeProperty = DependencyProperty.Register(
             "Sobrenome",
             typeof(string),
             typeof(NomeCompletoControl),
             null);
     public string Nome {
         get { return this.GetValue(NomeProperty).ToString(); }
         set { this.SetValue(NomeProperty, value); }
     }
     public string Sobrenome {
         get { return this.GetValue(SobrenomeProperty).ToString(); }
         set { this.SetValue(SobrenomeProperty, value); }
     }
 } 

Execute novamente o código e veja que agora dá certo. Isto por que acabamos de adicionar suporte à Binding em nosso controle. A sintaxe parece um pouco complexa, mas não é nada do outro mundo. Vamos tentar esclarecer o que ocorreu.

Criamos duas propriedades do tipo DependencyProperty, uma chamada NomeProperty e outra chamada SobrenomeProperty. Para criar ambas as Dependency Properties nós não criamos uma nova instância do mesmo objeto – ao contrário, utilizamos o método estático Register da mesma classe. Assim podemos registrar a propriedade com alguns atributos: o nome da propriedade (Nome), o tipo da propriedade (string), o tipo proprietário da propriedade (no caso a própria classe) e o metadata da propriedade (aqui deixamos null).

Veja que as propriedades são declaradas de forma estática, logo o gerenciamento dos valores se dá internamente e somente uma instância do objeto é criada (diferente das propriedades CLR comuns). Logo é a propriedade framework que fará o gerenciamento dos valores – caso exista um valor padrão, o valor será o primeiro a ser armazenado, caso seja atribuído um novo valor o padrão será substituído por este e assim por diante.

Também criamos propriedades públicas que intermediam o acesso as Dependency Properties através do método GetValue e do método SetValue, que fazem parte da classe DependencyObject. Uma vez nossa classe herda de UserControl está tudo certo – uma vez que ela está lá dentro (sendo mais específico: UserControl herda de Control que herda de FrameworkElement que herda de UIElement que herda de DependencyObject!).

Quando em nossa MainPage.cs criamos uma nova instância de MeuRepositorio e definimos que o nosso UserControl receberia esta instância como fonte de dados (DataContext), indicamos ao Silverlight que as propriedades do UserControl seriam preenchidas por Binding (por isto havia dado erro quando você executou o código).

Lembra-se daquele quarto argumento da Dependency Properties que está nulo em nosso exemplo? Ele se refere ao PropertyMetada e tem duas funções bastante interessantes: atribuir um valor padrão para a propriedade (ou seja, enquanto nada for atribuído, este valor é o que passará a existir) e/ou um callback para quando houver alteração no valor da propriedade (útil em situações onde você precisa atribuir o valor alterado para outras propriedades da classe).

Vamos ver um exemplo tolo, mas que vale para ilustrar o funcionamento. Vamos alterar a dependency property do Nome deste modo:

private static readonly DependencyProperty
    NomeProperty = DependencyProperty.Register(
         "Nome",
         typeof(string),
         typeof(NomeCompletoControl),
         new PropertyMetadata("Nome"new PropertyChangedCallback((d, ea)=>{
             MessageBox.Show("Old Value: " + ea.OldValue.ToString());
             MessageBox.Show("New Value: " + ea.NewValue.ToString());
         }))); 

Veja que configuramos o valor padrão da propriedade para “Nome” e interceptamos o callback para exibir o valor antigo e o novo valor da propriedade.

Assim que executarmos o código, surgirá uma MessageBox escrito “Old Value: Nome”! Ao clicar em Ok abrirá uma nova MessageBox com “New Value: José”! Isto prova que havia um valor atribuído chamado “Nome” e que foi alterado depois para “José”, e quando isto aconteceu foi disparado o callback para notificar a alteração da propriedade.

Para finalizar, através de DependencyProperty você também pode registrar algo conhecido como Attached Property – que é uma propriedade que pode ser acessada pelos objetos filhos, ainda que os mesmos não os herdem. Sabe quando você utiliza uma Grid para posicionar os seus objetos? Se não sabe, veja abaixo (um simples layout com 3 linhas e 2 colunas):

<Grid x:Name=”LayoutRoot” Background=”White”>
    <Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>

    <TextBlock 
Grid.Column=”0″ Grid.Row=”0″ Text=”Nome” />
    <TextBlock 
Grid.Column=”1″ Grid.Row=”0″ Text=”Sobrenome” />

    <TextBlock 
Grid.Column=”0″ Grid.Row=”1″ Text=”José” />
    <TextBlock 
Grid.Column=”1″ Grid.Row=”1″ Text=”Saramago” />

    <TextBlock 
Grid.Column=”0″ Grid.Row=”2″ Text=”Fiódor” />
    <TextBlock 
Grid.Column=”1″ Grid.Row=”2″ Text=”Dostoiévski” />

</Grid>

Estas propriedades em destaque (Grid.Column e Grid.Row) são Attached Properties do objeto Grid – ou seja, os objetos filhos conseguem configurá-las sem precisar criar uma nova instância do objeto e também ser herdá-las.

Caso você decida criar, por exemplo, uma Panel customizada, você pode valer deste recurso para saber como deverá organizar os seus objetos dentro da Panel.

A sintaxe é bem parecida, veja:

private static readonly DependencyProperty
TopPositionProperty = DependencyProperty.RegisterAttached(
        “TopPosition”,
        typeof(double),
        typeof(NomeCompletoControl),
        new PropertyMetadata(0d, new PropertyChangedCallback((d, ea)=>{
            MessageBox.Show(ea.NewValue.ToString());
})));

public static void SetTopPosition(UIElement element, double value) {
element.SetValue(TopPositionProperty, value);
}

public static double GetTopPosition(UIElement element) {
return (double)element.GetValue(TopPositionProperty);
}

Aí no nosso XAML podemos ter um código assim

<Grid x:Name=”LayoutRoot” Background=”White”>
    <my:NomeCompletoControl 
    x:Name=”MeuControle” 
    Nome=”{Binding MeuNome}” 
    Sobrenome=”{Binding MeuSobrenome}”>            
        <Button Content=”Blah” my:NomeCompletoControl.TopPosition=”5″ />     
    </my:NomeCompletoControl>
</Grid>

A principal diferença – além do método RegisterAttached – é a forma como a propriedade é configurada, através de dois métodos (um para set e um para get), cujo argumento UIElement é passado como argumento para que sirva como hospedeiro da Propriedade (algo também intuitivo e que faz todo sentido).

Importante salientar que os métodos seguem um padrão de nomenclatura: Set(nome da propriedade) e Get(nome da propriedade). Caso o nome seja diferente disto, como SetNomeQualquer, a coisa ficará um pouco estranha, visto que o XAML teria que ficar mais ou menos assim my:NomeCompletoControl.NomeQualquer=”5″.

Legal, não?

Espero o artigo tenha sido esclarecedor.

Dúvidas, críticas ou sugestões, aproveitem o espaço do blog e comentem!

Etiquetado , , , ,

2 pensamentos sobre “Entendendo DependencyProperty

  1. […] colocando o conteúdo dentro de TRs eTDs, você define antes as colunas e as linhas e através de Attached Properties você diz a qual linha/coluna o objeto deve ser […]

  2. Rodrigo disse:

    Estou criando um controle para Silverlight. Nele eu tenho uma Dependency Property que oculta alguns itens do meu controle, de acordo com a opção selecionada. Esta dependency property tem um valor padrão definido. Quando consumo este controle e seleciono o valor padrão para esta dependency property, ela retira do xaml as informações desta propriedade, por se tartar do valor padrão. O problema é que o setter não é disparado e, consequentemente eu não manipulo o controle, ocultando os itens que eu preciso. Como fazer para contornar este problema?

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: