Este artículo proviene de un motor de traducción automática.

Las fronteras de las UI

La interfaz de usuario de Silverlight 4 líquido

Charles Petzold

Descargar el ejemplo de código

Charles PetzoldEl término “ fluido de interfaz de usuario ” recientemente ha convertido en común para describir las técnicas de diseño de la interfaz de usuario que se evite tener objetos visuales de repente, se extrae en la vista o saltar de una ubicación a otra. En su lugar, asegúrese de objetos visualmente líquidos correcta más entradas y las transiciones, en ocasiones, como si emergentes de niebla o variable en la vista.

En los últimos dos entregas de esta columna, he tratado algunas técnicas de implementación de líquido de la interfaz de usuario en su propio. Me inspiró parcialmente mediante el establecimiento futuros de un función de la interfaz de usuario en el 4 de Silverlight de líquido. Ahora que se ha lanzado oficialmente el 4 de Silverlight, es lo que va ser sobre aquí. La incursión de Silverlight 4 en líquido de interfaz de usuario se limita de forma más restringida, se limita a la carga y descarga de los elementos de un control ListBox, pero nos proporciona algunas sugerencias importantes acerca de cómo ampliar el líquido técnicas de interfaz de usuario con nuestras propias implementaciones. Comportamientos de interfaz de usuario más fluidos están disponibles en Expression Blend 4.

Las plantillas y los VSM

Si no sabe exactamente dónde se puede encontrar la nueva característica de interfaz de usuario de líquido de 4 de Silverlight, puede buscar el número de horas. No es una clase. No es una propiedad. No es un método. No es un evento. Se implementa realmente como tres nuevos estados visuales en la clase de ListBoxItem. La figura 1, se muestra la documentación de esa clase, con los elementos de atributo TemplateVisualState que ligeramente se reorganizan de acuerdo con los nombres de grupo.

Figura 1 de la documentación de la clase de 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

El Administrador de estado visual (VSM) es uno de los cambios más importantes realizados en Silverlight, tal como lo que es una adaptación de Windows Presentation Foundation. En WPF, un estilo o una plantilla (casi siempre se define en XAML) puede incluir elementos que se llama a los desencadenadores de . Estos desencadenadores se definen para detectar un cambio de propiedad o a un evento y, a continuación, iniciar una animación o un cambio a otra propiedad.

Por ejemplo, una definición de estilo para un control puede contener un desencadenador para la propiedad IsMouseOver que el fondo del control se establece en un pincel de color azul cuando la propiedad es true. O un desencadenador para los eventos MouseEnter y MouseLeave puede iniciar un par de animaciones breves, cuando se produzcan estos eventos.

En Silverlight, los desencadenadores han sido en gran medida banished y reemplazado por el VSM, parcialmente con el fin de proporcionar un enfoque más estructurado para las características de un control que cambian dinámicamente en tiempo de ejecución y parcialmente para evitar tratar con todas las combinaciones diferentes de las posibilidades cuando se definen varios desencadenadores. El VSM se considera una mejora de este tipo a través de desencadenadores que se ha convertido en parte de WPF en el 4 de Microsoft .NET Framework.

Como puede ver en de figura 1, el control de ListBoxItem admite 11 estados visuales, pero se distribuirá en cuatro grupos. Dentro de cualquier grupo, el estado visual de un único está activo en cualquier momento. Esta regla sencilla reduce en gran medida el número de combinaciones posibles. Por ejemplo, no es necesario saber cómo va a aparecer el ListBoxItem cuando el mouse se sitúa sobre un elemento seleccionado, pero unfocused; cada grupo se puede controlar independientemente del resto.

El código de ListBoxItem es responsable de cambiar los estados visuales mediante llamadas al método estático VisualStateManager.GoToState. La plantilla de control de ListBoxItem es responsable de responder a estos estados visuales. La plantilla se responde a un cambio de estado visual determinado con un solo Storyboard que contiene uno o más animaciones destinadas a los elementos del árbol visual. Si desea que el control para responder a un cambio de estado visual inmediatamente sin necesidad de una animación, puede definir simplemente la animación con una duración de 0. Pero, ¿por qué preocuparse? Es sólo tan fácil de utilizar una animación para que los elementos visuales del control más fluido.

Los nuevos estados visuales para la compatibilidad de líquido de la interfaz de usuario son BeforeLoaded, AfterLoaded y BeforeUnloaded, parte del grupo LayoutStates. Al asociar las animaciones para que estos estados visuales, puede hacer que los elementos de la desaparición de ListBox en, o crecer o deslizamiento a la vista cuando se agregan en primer lugar para el control ListBox y hacer algo más cuando está eliminados del control ListBox.

Adaptación de la plantilla de ListBoxItem

La mayoría de los programadores probablemente tendrá acceso al característica de interfaz de usuario de ListBoxItem de líquido a través de Expression Blend, pero voy a mostrarle cómo hacerlo directamente en el marcado.

La plantilla de control predeterminado de ListBoxItem no tiene animaciones asociadas a los estados visuales en el grupo LayoutStates. Es su trabajo. Por desgracia, simplemente no se puede “ derivar ” la plantilla de ListBoxItem existente y se complementan con su propio material. Debe incluir la plantilla completa en el programa. Afortunadamente, es una simple cuestión de copiar y pegar. En la documentación de Silverlight 4, busque en la sección de controles y, a continuación, personalización del control y los estilos de control y plantillas y ListBox estilos y plantillas. Encontrará la definición de estilo predeterminada de ListBoxItem (que incluye la definición de plantilla) en el marcado que se inicia:

<Style TargetType="ListBoxItem">

En el elemento Setter para la propiedad Template, verá todo ControlTemplate utilizado para generar un árbol visual para cada ListBoxItem. La raíz del árbol visual es una sola celda cuadrícula. El marcado VSM ocupa una gran parte de la plantilla en la parte superior de la definición de la cuadrícula. En la parte inferior es el contenido real de la cuadrícula: tres formas de Rectangle (dos rellena y uno que sólo se traza) y un ContentPresenter, por ejemplo:

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

Los dos primeros objetos rellenos de rectángulo se utilizan para proporcionar el sombreado de fondo para pasar el mouse y la selección (respectivamente). La tercera muestra un rectángulo con trazo para indicar el foco de entrada. La visibilidad de los rectángulos se controla mediante el marcado VSM. Observe cómo cada grupo visual obtiene su propio elemento a manipular. ContentPresenter aloja el elemento tal y como se muestra en el cuadro de lista. Por lo general, el contenido de ContentPresenter es otro árbol visual definido en una plantilla de datos que se establece en la propiedad ItemTemplate del control ListBox.

El marcado VSM consta de los elementos de tipo VisualStateManager.VisualStateGroups, VisualStateGroup y VisualState, todos con un prefijo de espacio de nombres XML de “ vsm. ” En versiones anteriores de Silverlight, era necesario definir una declaración de espacio de nombres para ese prefijo:

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

Sin embargo, en el 4 de Silverlight puede simplemente eliminar todos los prefijos de vsm y olvidarse de esta declaración de espacio de nombres. Para realizar cambios en esta plantilla, querrá copiar dicha sección completa de marcado en una sección de recursos de un archivo XAML y darle un nombre de clave:

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

A continuación, establece este estilo en la propiedad ItemContainerStyle del ListBox:

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

El contenedor de elementos de “ ” es el objeto ListBox que se crea como un contenedor para cada elemento en el cuadro de lista, y que es un objeto de tipo ListBoxItem.

Una vez que tenga este estilo de ListBoxItem y la plantilla en el programa, puede realizar cambios en él.

Atenuar, fundido de salida

Let’s ver cómo funciona en el contexto de un programa de demostración sencilla. El código descargable de este artículo es una solución denominada FluidUserInterfaceDemo. Se compone de dos programas, que puede ejecutar desde Mi sitio Web en charlespetzold.com/silverlight/FluidUserInterfaceDemo de . Ambos programas están en la misma página HTML, que ocupa la ventana del explorador completa.

El primer programa es FluidListBox. Visualmente, compuesta de un control ListBox y dos botones para agregar y quitar elementos. He utilizado la misma colección de los productos de ultramarinos que he utilizado en los últimos dos columnas, por tanto, MainPage.xaml también contiene una plantilla de datos denominado produceDataTemplate.

Decidí que quería iniciar fuera sencilla y que los elementos de fundido de entrada en la vista cuando se agregan a los controles ListBox y desaparezca cuando se va a quitar. Esto implica que la animación de la propiedad Opacity de la cuadrícula que forma la raíz del árbol visual. Para ser el destino de una animación, que la cuadrícula necesita un nombre:

<Grid Name="rootGrid" ...>

En primer lugar, inserte un nuevo VisualStateGroup dentro de las etiquetas de VisualStateManager.VisualStateGroups:

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

Es donde se pasa el marcado para los Estados BeforeLoaded, AfterLoaded y BeforeUnloaded en el grupo LayoutStates.

La intensificación es más fácil de las dos tareas. Cuando un elemento se agrega por primera vez en el árbol visual, se dice que cargarse “ ” en el árbol visual. Antes de que se está cargando, el elemento tiene un estado visual de BeforeLoaded y, a continuación, el estado visual se convierte en AfterLoaded.

Hay varias formas de definir la intensificación. La primera requiere la inicialización de la opacidad de 0 en la etiqueta de cuadrícula:

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

A continuación, proporcione una animación para el estado de AfterLoaded aumentar la propiedad de opacidad en 1 en el transcurso de un segundo:

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

O bien, puede dejar la opacidad de la cuadrícula con su valor predeterminado de 1 y proporcionar las animaciones de BeforeLoaded y 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 la duración del estado de BeforeLoaded es 0, lo que efectivamente sólo establece la propiedad Opacity en 0. Uso de un guión gráfico de enteros y DoubleAnimation sólo para establecer una propiedad puede parecer una exageración, pero también se demuestra la flexibilidad de las animaciones. La sobrecarga es en realidad no muy similar.

El enfoque que le identifique personalmente prefiero, principalmente debido a que es la forma más sencilla, consiste en dejar la propiedad Opacity de la cuadrícula con su valor predeterminado de 1 y proporcionar sólo una animación para el estado de AfterLoaded con un valor desde que se especifica, en lugar de un valor para:

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

Ahora la animación se pasa el valor de 0 a su valor de base, que es 1. Puede utilizar esta técnica idéntica con el estado de BeforeLoaded. Pero tenga cuidado: El estado de BeforeLoaded se produce después de la ListBoxItem se crean e inicializan, pero antes de agregarlo al árbol visual, momento en que el estado de AfterLoaded. Esto es sólo un intervalo pequeño de tiempo. Tendrá problemas si define una animación de BeforeLoaded pero también se define una etiqueta de VisualState vacía 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" />

En cuanto se carga el elemento, se termina el guión gráfico para BeforeLoaded y no obtendrá ningún efecto intensificación. Sin embargo, puede hacer que el formato funciona si también agregar lo siguiente:

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

Define un período de transición de un segundo entre el BeforeLoaded y los Estados de AfterLoaded. Este período de transición proporciona el tiempo de animación BeforeLoaded para completarlo antes de que el estado de AfterLoaded lo apaga.

El proceso de fin de transición no es bastante igual de sencillo. Por lo tanto, cuando el elemento va a quitar en el control ListBox, se establece el estado de BeforeUnloaded, pero, a continuación, el elemento se elimina inmediatamente cualquier animación que se ha iniciado no será visible. He encontrado dos métodos que funcionan. El primero define una animación para el estado de BeforeUnloaded junto con una transición 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>

El segundo método define una etiqueta vacía para el estado de BeforeUnloaded y una animación de la 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>

La figura 2, se muestra el marcado completado de los Estados AfterLoaded y BeforeUnloaded tal y como aparecen en la plantilla de ListBoxItem en el archivo MainPage.xaml del proyecto FluidListBox.

La figura 2 un extracto de la la plantilla de ListBoxItem en 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>

Ampliación de ItemsControl

Debido a que el líquido de la función de la interfaz de usuario se implementa como estados visuales en ListBoxItem, no está disponible en ItemsControl. Como sabe, ItemsControl simplemente muestra una colección de elementos y permite al usuario desplazarse por ellos. No hay ningún concepto de selección o foco de entrada entre los elementos. Por este motivo, ItemsControl no requiere una clase especial como ListBoxItem para alojar los elementos. Sólo se utiliza un ContentPresenter. Puesto que ContentPresenter se deriva de FrameworkElement en lugar de control, no tiene una plantilla en el que se va a definir el comportamiento de los estados visuales.

Qué hacer, sin embargo, es derivar una clase de ItemsControl que ListBoxItem se utiliza para alojar sus elementos. Esto es en realidad mucho más sencillo que se puede asumir. La figura 3, se muestra todo el código de FluidableItemsControl.

La figura 3 de La clase 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;
    }
  }
}

El método fundamental es GetContainerForItemOverride. Este método devuelve el objeto que se utiliza para ajustar cada elemento. ItemsControl devuelve el elemento ContentPresenter, pero ListBox devuelve ListBoxItem, y eso es lo que también devuelve FluidableItemsControl. Este elemento ListBoxItem debe tener aplicado un estilo y, por ese motivo FluidableItemsControl también define la misma propiedad ItemContainerStyle como ListBox.

El otro método que se debe implementar es IsItemItsOwnContainerOverride. Si el elemento de ItemsControl ya es el mismo tipo que su contenedor (en este caso, un elemento ListBoxItem), no hay ningún motivo para incluirlo en otro contenedor. Ahora se puede establecer una definición de estilo de ListBoxItem a la propiedad ItemContainerStyle de FluidableItemsControl. Se puede simplificar considerablemente la plantilla en la definición de estilo. No es necesario lógica para pasar el mouse, foco de entrada o de selección, por lo que se pueden eliminar todos los estados visuales, además de los tres objetos Rectangle.

El programa FluidItemsControl muestra el resultado. Es prácticamente el mismo que FluidListBox pero con toda la lógica de selección de ListBox está ausente. El panel de la predeterminada para ItemsControl es un panel StackPanel, por lo es la simplificación de otro. Para compensar estas simplificaciones, han mejorado las animaciones de la carga y descarga los elementos. Ahora hay una animación en la transformación PlaneProjection que hace que parezca que los elementos se swiveling en y fuera de la vista.

Limitaciones y sugerencias

Incluso con la posibilidad de definir las animaciones en los elementos de un control ListBox o un ItemsControl, todavía existe una limitación fundamental: Si el control incorpora un ScrollViewer, no se puede definir las transformaciones que toman el elemento de forma inmediata. El ScrollViewer impone una región de recorte graves que simplemente la no puede ser transgressed (lo que me he sido capaz de determinar). Esto significa que las técnicas como las que he demostrado en la columna del mes pasado siguen siendo válidos y importante en el 4 de Silverlight.

Aunque el uso de la VSM para implementar esta característica de interfaz de usuario fluida en Silverlight 4 es una buena indicación de que el VSM es probable que desempeñan un papel cada vez más importante en el futuro para vincular el código y XAML. Es el momento en que los desarrolladores de aplicaciones comenzamos pensando en implementar nuestros propio estados visuales para un comportamiento personalizado.

Charles Petzold es editor colaborador de larga a MSDN Magazine *.*Actualmente, es escribir “ Programming Windows teléfono 7 Series, ” que se publicará como un libro electrónico gratuito que se pueden descargar en el otoño de 2010. Una edición de la vista previa está actualmente disponible a través de su sitio Web, de charlespetzold.com .