Este artigo foi traduzido por máquina.

Fronteiras da interface do usuário

A interface do usuário fluida do Silverlight 4

Charles Petzold

Baixe o código de exemplo

Charles PetzoldO termo “ fluido da interface do usuário ” tornou recentemente comum para descrever as técnicas de design da interface do usuário que evitar objetos visuais, de repente, o pop em modo de exibição ou saltar de um local para outro. Em vez disso, visualmente fluidos fazem mais elegante de entradas e as transições — às vezes, como se emergente de neblina ou deslizantes em modo de exibição.

Os últimos duas partes desta coluna, discuti algumas técnicas de implementação de fluido da interface do usuário por conta própria. Eu era parcialmente inspirou pela próxima introdução de um recurso da interface do usuário no Silverlight 4 de fluido. Agora que o Silverlight 4 foi oficialmente lançado, que é o que eu vai ser cobrindo aqui. Mergulho do Silverlight 4 em fluido da interface do usuário é confinada em vez disso, estritamente — é restrito para o carregamento e descarregamento dos itens em um ListBox — mas isso nos dá algumas dicas importantes sobre como estender as técnicas de interface do usuário fluidas com nossas próprias implementações. Comportamentos de interface do usuário mais fluidos estão disponíveis no Expression Blend 4.

Modelos e o VSM

Se você Don souber exatamente onde encontrar o novo recurso da interface do usuário de fluido 4 do Silverlight, você poderá pesquisar por várias horas. Não é uma classe. Não é uma propriedade. Não é um método. Não é um evento. Na verdade, ele é implementado como três novos estados visuais na classe ListBoxItem. A Figura 1 mostra a documentação dessa classe, com os itens de atributo TemplateVisualState ligeiramente reorganizados de acordo com os nomes de grupo.

Figura 1 da documentação da classe ListBoxItem

[TemplateVisualStateAttribute(Name = "Normal", GroupName =  
  "CommonStates")]
[TemplateVisualStateAttribute(Name = "MouseOver", GroupName = 
  "CommonStates")]
[TemplateVisualStateAttribute(Name = "Disabled", GroupName = 
  "CommonStates")]
[TemplateVisualStateAttribute(Name = "Unselected", GroupName = 
  "SelectionStates")]
[TemplateVisualStateAttribute(Name = "Selected", GroupName =  
  "SelectionStates")]
[TemplateVisualStateAttribute(Name = "SelectedUnfocused", GroupName = 
  "SelectionStates")]
[TemplateVisualStateAttribute(Name = "Unfocused", GroupName = 
  "FocusStates")]
[TemplateVisualStateAttribute(Name = "Focused", GroupName = 
  "FocusStates")]
[TemplateVisualStateAttribute(Name = "BeforeLoaded", GroupName = 
  "LayoutStates")]
[TemplateVisualStateAttribute(Name = "AfterLoaded", GroupName = 
  "LayoutStates")]
[TemplateVisualStateAttribute(Name = "BeforeUnloaded", GroupName =  
  "LayoutStates")]
public class ListBoxItem : ContentControl

O VSM (Visual State Manager) é uma das alterações mais significativas feitas no Silverlight, como estava sendo adaptado a partir do Windows Presentation Foundation. No WPF, um estilo ou um modelo (quase sempre definida em XAML) pode incluir elementos chamados de disparadores de . Esses disparadores definidos para detectar uma alteração de propriedade ou de um evento e, em seguida, iniciar uma animação ou uma alteração para outra propriedade.

Por exemplo, uma definição de estilo para um controle pode incluir um disparador para a propriedade IsMouseOver que define o plano de fundo do controle como um pincel azul, quando a propriedade é true. Ou um disparador de eventos MouseEnter e MouseLeave pode iniciar várias animações breves quando esses eventos ocorrerem.

No Silverlight, os disparadores foram amplamente banished e substituídos com o VSM parcialmente para fornecer uma abordagem mais estruturada para alterar dinamicamente as características de um controle em tempo de execução e parcialmente para evitar lidar com todas as combinações diferentes de possibilidades quando vários disparadores definidos. O VSM for considerado como um aperfeiçoamento através de disparadores que ele se tornou parte do WPF no Microsoft .NET Framework 4.

Como você pode ver no do Figura 1, o controle de ListBoxItem oferece suporte a estados visuais 11, mas eles estão apportioned em quatro grupos. Dentro de qualquer grupo, apenas um estado visual está ativo, a qualquer momento. Essa regra simples reduz significativamente o número de combinações possíveis. Por exemplo, você Don precisa descobrir como o ListBoxItem deve aparecer quando o mouse está focalizando um item selecionado, mas unfocused; cada grupo pode ser manipulado de forma independente dos outros.

A parte do código de ListBoxItem é responsável por alterar os estados visuais por meio de chamadas ao método estático VisualStateManager.GoToState. O modelo de controle de ListBoxItem é responsável por responder a esses estados visuais. O modelo responde a uma alteração de estado visual específico com um único storyboard que contém as animações de um ou mais elementos da árvore visual de destino. Se você desejar que o controle para responder a uma alteração de estado visual imediatamente sem uma animação, você pode simplesmente definir a animação com duração 0. Mas por que se preocupar com? É assim tão fácil de usar uma animação para ajudar a tornar visuais do controle mais fluida.

Os novos estados visuais para oferecer suporte a fluidos da interface do usuário são BeforeLoaded, AfterLoaded e BeforeUnloaded, parte do grupo LayoutStates. Associando animações a esses estados visuais, você pode fazer itens na sua atenuação de ListBox, ou crescer ou glide em modo de exibição quando estiver adicionados pela primeira vez ao ListBox e fazer algo quando eles são removidos da ListBox.

Adaptar o modelo de ListBoxItem

A maioria dos programadores provavelmente acessarão a recurso da interface do usuário de ListBoxItem fluida por meio do Expression Blend, mas vou mostrar como fazê-lo diretamente na marcação.

O modelo de controle padrão para o ListBoxItem tem animações de associadas os estados visuais no grupo LayoutStates. Esse é o seu trabalho. Infelizmente, você não pode simplesmente “ derivam ” ListBoxItem modelo existente e suplementá-la com seu próprio material. Você deve incluir o modelo inteiro no seu programa. Felizmente, é uma simples questão de copiar e colar. Na documentação do Silverlight 4, examine a seção controles e, em seguida, personalização de controle e estilos de controle e modelos e estilos de ListBox e modelos. Você encontrará a definição de estilo padrão para o ListBoxItem (que inclui a definição de modelo) na marcação, que começa:

<Style TargetType="ListBoxItem">

Sob o elemento setter da propriedade de modelo, você verá o ControlTemplate inteiro usado para construir uma árvore visual para cada ListBoxItem. A raiz da árvore visual é um Grid de célula única. A marcação VSM ocupe uma grande parte do modelo na parte superior da grade de definição. Na parte inferior são o conteúdo real do que a grade: três formas de retângulo (dois preenchido e um traçado apenas) e um ContentPresenter, da seguinte forma:

<Grid ... >
  ...  <Rectangle x:Name="fillColor" ... />
  <Rectangle x:Name="fillColor2" ... />
  <ContentPresenter x:Name="contentPresenter" ... />
  <Rectangle x:Name="FocusVisualElement" ... />
</Grid>

Os objetos de retângulo preenchidos duas primeiras são usados para fornecer o sombreamento de plano de fundo ao passar com o mouse e seleção (respectivamente). A terceira exibe um retângulo tracejado para indicar o foco de entrada. A visibilidade desses retângulos é controlada pela marcação VSM. Observe como cada grupo visual obtém seu próprio elemento para manipular. O ContentPresenter hospeda o item como ele é exibido na caixa de listagem. Em geral, o conteúdo do ContentPresenter é outra árvore visual definido em um DataTemplate definido para a propriedade ItemTemplate de ListBox.

A marcação VSM consiste nos elementos do tipo VisualStateManager.VisualStateGroups, VisualStateGroup e VisualState, tudo isso com um prefixo de namespace XML do “ vsm ”. Em versões anteriores do Silverlight, era necessário definir uma declaração de namespace para esse prefixo:

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

No entanto, no Silverlight 4 pode simplesmente excluir todos os prefixos do vsm e esquecer sobre esta declaração de namespace. Para fazer alterações a este modelo, você desejará copiar a seção inteira de marcação para uma seção de recursos de um arquivo XAML e dê a ele um nome de chave:

<Style x:Key="listBoxItemStyle" TargetType="ListBoxItem">
  ... </Style>

Defina este estilo para a propriedade ItemContainerStyle do ListBox:

<ListBox ... ItemContainerStyle="{StaticResource listBoxItemStyle}" ....

O contêiner de item “ ” é o objeto ListBox cria como um wrapper para cada item na caixa de listagem e que é um objeto do tipo ListBoxItem.

Depois que tiver esse estilo de ListBoxItem e o modelo no seu programa, você pode alterá-lo.

Fade in, desaparecer para fora

Let’s ver como isso funciona no contexto de um programa de demonstração simples. O código para download deste artigo é uma solução intitulada FluidUserInterfaceDemo. Ele consiste em dois programas, você pode executar de meu site da Web ao charlespetzold.com/silverlight/FluidUserInterfaceDemo de . Os dois programas estão na mesma página HTML, cada janela do navegador inteira que ocupam.

O primeiro programa é FluidListBox. Visualmente, ele consiste em um ListBox e dois botões Adicionar e remover itens. Usei o mesmo conjunto de produção de doces que usei na Minhas última duas colunas, para que MainPage.xaml também contém um DataTemplate chamado produceDataTemplate.

Decidi que queria começar simples e têm os itens de atenuação em modo de exibição quando adicionados à ListBox e fade out quando elas são removidas. Isso envolve animar a propriedade Opacity da grade de forma a raiz da árvore visual. Como o destino de uma animação, grade precisa de um nome:

<Grid Name="rootGrid" ...>

Primeiro, insira um novo VisualStateGroup nas marcas VisualStateManager.VisualStateGroups:

<VisualStateGroup x:Name="LayoutStates">  ...
</VisualStateGroup>

Isso é onde a marcação vai para os estados BeforeLoaded, AfterLoaded e BeforeUnloaded no grupo LayoutStates.

O fade in é mais fácil dos dois trabalhos. Quando um item é adicionado à árvore visual, ela afirmou ser “ carregados ” na árvore visual. Antes que está sendo carregado, o item tem um estado visual de BeforeLoaded, e, em seguida, o estado visual se torna AfterLoaded.

Há várias maneiras para definir o fade in. A primeira requer inicialização Opacity para 0 na grade tag:

<Grid Name="rootGrid" Opacity="0" ... >

Você então fornecer uma animação para o estado de AfterLoaded aumentar a propriedade Opacity para 1 no decorrer de 1 segundo:

<VisualState x:Name="AfterLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     To="1" Duration="0:0:1" />
  </Storyboard>
</VisualState>

Ou você pode deixar a opacidade de grade com seu valor padrão igual a 1 e fornecem as animações para BeforeLoaded e AfterLoaded:

<VisualState x:Name="BeforeLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     To="0" Duration="0:0:0" />
  </Storyboard>
</VisualState>
                                    
<VisualState x:Name="AfterLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     To="1" Duration="0:0:1" />
  </Storyboard>
</VisualState>

Observe que a duração no estado BeforeLoaded é 0, o que efetivamente apenas define a propriedade Opacity para 0. Usando o storyboard e DoubleAnimation inteiro apenas para definir uma propriedade pode parecer um exagero, mas ele também demonstra a flexibilidade das animações. A sobrecarga é na verdade, não muito.

A abordagem pessoalmente prefiro — principalmente porque é o mais simples — é deixar a propriedade Opacity de Grid com seu valor padrão igual a 1 e fornecer apenas uma animação para o estado AfterLoaded com um valor especificado, em vez de um valor para:

<VisualState x:Name="AfterLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     From="0" Duration="0:0:1" />
  </Storyboard>
</VisualState>

Agora a animação vai do valor de 0 para o seu valor de base, que é 1. Você pode usar essa técnica idêntica com o estado de BeforeLoaded. Mas cuidado: O estado BeforeLoaded ocorre depois que o ListBoxItem é criado e inicializado, mas antes que ele é adicionado à árvore visual, no ponto em que o estado AfterLoaded ocorre. Isso é apenas uma pequena lacuna de tempo. Você terá problemas se você define uma animação de BeforeLoaded, mas também define uma marca de VisualState vazia para AfterLoaded:

<VisualState x:Name="BeforeLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     From="0" Duration="0:0:1" />
  </Storyboard>
</VisualState>
                                    
<VisualState x:Name="AfterLoaded" />

Assim que o item é carregado, o storyboard para BeforeLoaded seja finalizado e você não terá nenhum efeito fade in. No entanto, você pode fazer com que a marcação de funcionar se você também adicionar o seguinte:

<VisualStateGroup.Transitions>
  <VisualTransition From="BeforeLoaded"
                    To="AfterLoaded"
                    GeneratedDuration="0:0:1" />
</VisualStateGroup.Transitions>

Isso define um período de transição de um segundo entre o BeforeLoaded e os estados AfterLoaded. Esse período de transição dá o tempo de animação BeforeLoaded seja concluída antes do estado AfterLoaded o desliga.

O processo de fade-out não é bem mais simples. Quando o item está prestes a ser removido da ListBox, o estado de BeforeUnloaded estiver definido, mas, em seguida, o item é removido imediatamente assim, qualquer animação começou não ficarão visível! Descobri duas abordagens funcionam. O primeiro define uma animação para o estado de BeforeUnloaded juntamente com uma transição de estado:

<VisualState x:Name="BeforeUnloaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     To="0" Duration="0:0:1" />
  </Storyboard>
</VisualState>

<VisualStateGroup.Transitions>
  <VisualTransition From="AfterLoaded" 
                    To="BeforeUnloaded" 
                    GeneratedDuration="0:0:1" />
</VisualStateGroup.Transitions>

A segunda abordagem define uma marca vazia para o estado de BeforeUnloaded e uma animação para o VisualTransition:

<VisualStateGroup.Transitions>
  <VisualTransition From="AfterLoaded" 
                    To="BeforeUnloaded" 
                    GeneratedDuration="0:0:1">
    <Storyboard>
      <DoubleAnimation Storyboard.TargetName="rootGrid"
                       Storyboard.TargetProperty="Opacity"
                       To="0" Duration="0:0:1" />
    </Storyboard>
  </VisualTransition>
</VisualStateGroup.Transitions>

A Figura 2 mostra a marcação concluída para os estados AfterLoaded e BeforeUnloaded conforme aparecem no modelo no arquivo do projeto FluidListBox MainPage.xaml ListBoxItem.

A Figura 2 um trecho do ListBoxItem modelo FluidListBox

    <ControlTemplate TargetType="ListBoxItem">
      <Grid Name="rootGrid" Background="{TemplateBinding Background}">
        <VisualStateManager.VisualStateGroups>
    
          <!-- Additions to standard template -->
          <VisualStateGroup x:Name="LayoutStates">
                                        
            <VisualState x:Name="AfterLoaded">
              <Storyboard>
                <DoubleAnimation Storyboard.TargetName="rootGrid"
                                 Storyboard.TargetProperty="Opacity"
                                 From="0" Duration="0:0:1" />
              </Storyboard>
            </VisualState>
                                        
            <VisualState x:Name="BeforeUnloaded" />
    
              <VisualStateGroup.Transitions>
                <VisualTransition From="AfterLoaded" 
                                  To="BeforeUnloaded" 
                                  GeneratedDuration="0:0:1">
                  <Storyboard>
                     <DoubleAnimation Storyboard.TargetName="rootGrid"
                                      Storyboard.TargetProperty="Opacity"
                                      To="0" Duration="0:0:1" />
                  </Storyboard>
                </VisualTransition>
              </VisualStateGroup.Transitions>
            </VisualStateGroup>
            <!-- End of additions to standard template -->
                ...
      </Grid>
    </ControlTemplate>
    One more warning: By default, the ListBox stores its items in a VirtualizingStackPanel. This means the actual items and their containers aren’t generated until they’re required to be visually displayed. If you define an animation for the After-Loaded state, and then fill the ListBox up with items, the items will fade in as they’re scrolled into view. This is probably undesirable. The easy solution is to replace the VirtualizingStackPanel with a regular StackPanel. The required markup on the ListBox is trivial:
    <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
        <StackPanel />
      </ItemsPanelTemplate>
    </ListBox.ItemsPanel>

Estendendo o ItemsControl

Como o recurso da interface do usuário de fluido é implementado como estados visuais em ListBoxItem, ele não está disponível em ItemsControl. Como você sabe, o ItemsControl simplesmente exibe uma coleção de itens e permite que o usuário navegue por eles. Não há nenhum conceito de seleção ou o foco de entrada entre os itens. Por esse motivo, ItemsControl não requer uma classe especial, como o ListBoxItem para hospedar os itens. Ela simplesmente usa um ContentPresenter. Porque o ContentPresenter deriva de FrameworkElement, em vez de controle, ele não tem um modelo no qual deseja definir o comportamento de estados visuais.

O que você pode fazer, no entanto, é derivar uma classe de ItemsControl utiliza ListBoxItem para hospedar seus itens. Isso é realmente muito mais fácil do que você pode supor. A Figura 3 mostra todo o código para FluidableItemsControl.

A Figura 3 da Classe FluidableItemsControl

using System.Windows;
using System.Windows.Controls;

namespace FluidItemsControl
{
  public class FluidableItemsControl : ItemsControl
  {
    public static readonly DependencyProperty ItemContainerStyleProperty =
      DependencyProperty.Register("ItemContainerStyle",
      typeof(Style),
      typeof(FluidableItemsControl),
      new PropertyMetadata(null));

    public Style ItemContainerStyle
    {
      set { SetValue(ItemContainerStyleProperty, value); }
      get { return (Style)GetValue(ItemContainerStyleProperty); }
    }

    protected override DependencyObject GetContainerForItemOverride()
    {
      ListBoxItem container = new ListBoxItem();

      if (ItemContainerStyle != null)
        container.Style = ItemContainerStyle;

      return container;
    }

    protected override bool IsItemItsOwnContainerOverride(object item)
    {
      return item is ListBoxItem;
    }
  }
}

O método crucial é GetContainerForItemOverride. Esse método retorna o objeto usado para encapsular a cada item. ItemsControl retorna ContentPresenter, mas ListBox retornará ListBoxItem e que é o que FluidableItemsControl também retorna. Este ListBoxItem deve ter um estilo aplicado e por esse motivo FluidableItemsControl define também a mesma propriedade ItemContainerStyle como ListBox.

O método deve ser implementado é IsItemItsOwnContainerOverride. Se o item em ItemsControl já é o mesmo tipo de seu contêiner (nesse caso, um ListBoxItem), e em seguida, não há motivo para colocá-lo em outro recipiente. Agora você pode definir uma definição de estilo ListBoxItem à propriedade ItemContainerStyle do FluidableItemsControl. O modelo de definição de estilo pode ser drasticamente simplificado. Ele não precisa de lógica de mouse-over, o foco de entrada ou a seleção, para que esses estados visuais podem ser eliminados, bem como os três objetos Rectangle.

O programa FluidItemsControl mostra o resultado. É quase a mesma FluidListBox mas com toda a lógica de seleção de ListBox esteja ausente. O painel de padrão de ItemsControl é um StackPanel, por isso que é outra simplificação. Para compensar essas simplificações, eu tenha melhorado as animações para carregar e descarregar os itens. Agora é uma animação na transformação PlaneProjection que faz com que ele apareça como se os itens são swiveling no e para fora do modo de exibição.

Limitações e sugestões

Mesmo com o recurso para definir animações de itens em um ListBox ou um ItemsControl, ainda existe uma limitação fundamental: Se o controle incorpora um ScrollViewer, não é possível definir transformações que se o item de imediato. O ScrollViewer impõe uma região de recorte graves que simplesmente não pode ser transgressed (contanto que eu tenha sido capaz de determinar). Isso significa que as técnicas, como aqueles que demonstrei na coluna do mês passado ainda válidas e importantes no Silverlight 4.

Mas o uso do VSM para implementar esse recurso da interface do usuário fluido 4 do Silverlight é uma boa indicação de que o VSM provavelmente desempenham um papel cada vez mais importante no futuro, para vincular o código e XAML. É o momento em que os desenvolvedores de aplicativos começamos considerar a implementação de nossos próprios estados visuais para o comportamento personalizado.

Charles Petzold é editor colaborador da há muito tempo para MSDN Magazine *.*Atualmente, ele está gravando “ Programming Windows telefone 7 Series, ” que será publicado como um download e-book gratuito no outono de 2010. Uma edição de visualização está disponível no momento por meio de seu site, de charlespetzold.com .