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

Las fronteras de las UI

Toque y respuesta

Charles Petzold

Descargar el ejemplo de código

Charles PetzoldLa programación es una disciplina de ingeniería más que una ciencia o una rama de las matemáticas, por lo que rara vez existe una única solución correcta para un problema. Variedades y variaciones son la norma y, a menudo es reflectante para explorar estas alternativas, en lugar de centrarse en un enfoque determinado.

En mi artículo “Multi-Touch de eventos de manipulación de WPF” ” en el número de agosto de de MSDN Magazine , empecé a explorar la compatibilidad de multi-touch interesante en la versión 4 de Windows Presentation Foundation (WPF). Los eventos de manipulación sirven principalmente para consolidar multi-touch en transformaciones geométricas útiles y para ayudar a implementar la inercia.

En este artículo, he mostrado dos relacionados con métodos para controlar los eventos de manipulación en la colección de elementos de la imagen. En ambos casos, los eventos reales procesados por la clase de ventana. Un programa definido por los controladores para los eventos de manipulación de los elementos que se manipule. El otro enfoque ha mostrado cómo reemplazar los métodos OnManipulation para obtener los mismos eventos enrutados a través del árbol visual.

El método de la clase personalizada

Un tercer enfoque también tiene sentido: Puede definir una clase personalizada para los elementos manipulables que reemplaza sus propios métodos OnManipulation en lugar de dejar este trabajo para un elemento contenedor. La ventaja de este enfoque es que puede realizar la clase personalizada un poco más atractivo decorando, con un borde o de otro elemento; también se pueden utilizar los adornos para proporcionar comentarios visuales cuando el usuario toca un elemento manipulable.

Cuando los programadores veteranos de WPF que necesitan para realizar cambios visuales para un control basado en eventos, que probablemente se creen de EventTrigger, pero los programadores WPF necesitan para empezar a realizar la transición para el Administrador de estado visual. Incluso cuando se deriva de UserControl (la estrategia que va a usar), es bastante fácil de implementar.

Una aplicación mediante los eventos de manipulación debe basar probablemente comentarios visuales en los mismos eventos en lugar de los eventos TouchDown y retocar de Low nivel. Al utilizar los eventos de manipulación, desea comenzar la información visual con el evento o la ManipulationStarting ManipulationStarted. (Realmente no suponer una diferencia que se selecciona para este trabajo.)

Sin embargo, cuando experimente con estos comentarios, una de las primeras cosas que se dará cuenta es que los eventos ManipulationStarting y ManipulationStarted no se desencadenan cuando un elemento en primer lugar se toca, pero sólo cuando se inicia el movimiento. Este comportamiento es una holdover desde la interfaz de lápiz, y desea cambiarla, establezca la siguiente propiedad adjunta en el elemento que se manipule:

Stylus.IsPressAndHoldEnabled="False"

Ahora, los eventos ManipulationStarting y ManipulationStarted se activan cuando un elemento en primer lugar se toca. Tendrá que desactivar la información visual con cualquiera de los ManipulationInertiaStarting o manipulación ­ evento completado, en función de si desea que los comentarios al final cuando se eleva el dedo del usuario de la pantalla o después del elemento ha dejado de moverse debido a la inercia. Si no utiliza inercia (como no estará en este artículo), no importa qué evento utilice.

El código descargable de este artículo está en una única solución de Visual Studio denominada TouchAndResponseDemos con dos proyectos. El primer proyecto se denomina FeedbackAndSmoothZ, que incluye un derivado de UserControl personalizado denominado ManipulablePictureFrame que implementa la lógica de manipulación.

ManipulablePictureFrame define una única propiedad de tipo secundario y su constructor estático se utiliza para volver a definir los valores predeterminados para las tres propiedades: HorizontalAlignment, VerticalAlignment y el IsManipulationEnabled importantes. El constructor de instancia llama a InitializeComponent (como es habitual), pero, a continuación, establece RenderTransform del control a un tipo de MatrixTransform si no es así.

Durante el evento OnManipulationStarting, llama a Manipulable ­ PictureFrame clase:

VisualStateManager.GoToElementState(this, "Touched", false);

y durante el evento OnManipulationCompleted, llama al:

VisualStateManager.GoToElementState(this, "Untouched", false);

Se trata de la contribución de mi archivo código único para la implementación de estados visuales. El código que realiza la manipulación real le resultarán familiar desde el código en la columna del mes pasado, con dos cambios significativos:

  • En el método OnManipulationStarting, la ManipulationContainer se establece a primario su elemento.
  • El método OnManipulationDelta es sólo un poco más sencillo porque el elemento que se ha manipulado es el propio objeto PictureFrame Manipulable ­.

La figura 1 muestra el archivo ManipulablePictureFrame.xaml completo.

Figura 1 del Archivo ManipulablePictureFrame.xaml  

<UserControl x:Class="FeedbackAndSmoothZ.ManipulablePictureFrame"
             xmlns=
       "https://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
             Stylus.IsPressAndHoldEnabled="False"
             Name="this">
    
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="TouchStates">
            <VisualState x:Name="Touched">
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="maskBorder"
                                     Storyboard.TargetProperty="Opacity"
                                     To="0.33" Duration="0:0:0.25" />
                    
                    <DoubleAnimation Storyboard.TargetName="dropShadow"
                                     Storyboard.TargetProperty=          
                  "ShadowDepth"
                                     To="20" Duration="0:0:0.25" />
                </Storyboard>
            </VisualState>
            <VisualState x:Name="Untouched">
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="maskBorder"
                                     Storyboard.TargetProperty="Opacity"
                                     To="0" Duration="0:0:0.1" />
                    <DoubleAnimation Storyboard.TargetName="dropShadow"
                                     Storyboard.TargetProperty=     
                      "ShadowDepth"
                                     To="5" Duration="0:0:0.1" />
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    
    <Grid>
        <Grid.Effect>
            <DropShadowEffect x:Name="dropShadow" />
        </Grid.Effect>
        
        <!-- Holds the photo (or other element) -->
        <Border x:Name="border" 
                Margin="24" />
        
        <!-- Provides visual feedback -->
        <Border x:Name="maskBorder" 
                Margin="24" 
                Background="White" 
                Opacity="0" />
        
        <!-- Draws the frame -->
        <Rectangle Stroke="{Binding ElementName=this, Path=Foreground}" 
                   StrokeThickness="24" 
                   StrokeDashArray="0 0.9" 
                   StrokeDashCap="Round" 
                   RadiusX="24" 
                   RadiusY="24" />
        
        <Rectangle Stroke="{Binding ElementName=this, Path=Foreground}" 
                   StrokeThickness="8" 
                   Margin="16" 
                   RadiusX="24" 
                   RadiusY="24" />
    </Grid>
</UserControl>

El borde con el nombre del "borde" se utiliza para alojar al elemento secundario de la clase ManipulablePictureFrame. Esto probablemente será un elemento de la imagen, pero no tiene por qué ser. Los dos elementos Rectangle dibujar un tipo de trama “ festones ” alrededor del borde y el borde de la segunda se utiliza para la información visual.

Mientras se mueve un elemento, las animaciones en ManipulablePictureFrame.xaml “ aclarar ” la imagen un poco: en realidad, es más de un efecto “ lavado ” y aumentar la sombra del sistema, como se muestra en de figura 2.

Figure 2 A Highlighted Element in the FeedbackAndSmoothZ Program

La figura 2 de un elemento resaltado en el programa FeedbackAndSmoothZ

Prácticamente cualquier tipo de resaltado sencillo puede proporcionar información visual durante los sucesos de contacto. Sin embargo, si está trabajando con pequeños elementos que pueden ser utilizados y manipulados, deseará que los elementos más grandes cuando se han utilizado, de modo que no se oculta completamente finger del usuario. (Al otro lado, no desea que un elemento más grande de información visual si también va a permitir al usuario cambiar el tamaño del elemento. Es muy desconcertante para manipular un elemento en un tamaño que desee y, a continuación, lo que reducir un poco al levantar los dedos de la pantalla.)

Observe que, a medida que las imágenes más pequeñas y grandes, el marco se reduce o expande en consecuencia. ¿Es éste comportamiento correcto? Es posible. Tal vez no. Una alternativa a este comportamiento al final de este artículo va a mostrar.

Transiciones de Z suaves

En los programas he mostrado el mes pasado, haría que tocar una foto para saltar al primer plano. Esto era casi el enfoque más sencillo se puede considerar y es necesario establecer las propiedades de Panel.ZIndex adjuntado nuevas para todos los elementos de la imagen.

Un actualizador breve: Normalmente cuando se superponen a los elementos secundarios de un control Panel, se organizan de fondo al primer plano según su posición en la colección Children del panel. Sin embargo, la clase Panel define una propiedad adjunta denominada ZIndex que realiza el reemplazo con eficacia el índice secundario. (El nombre alludes para el gonal potasio ­ del eje z del plano XY convencional de la pantalla, que incluye conceptualmente fuera de la pantalla). Los elementos con un valor ZIndex más bajo en segundo plano; los valores más altos de ZIndex colocan un elemento en el primer plano. Si dos o más elementos superpuestos tienen el mismo valor ZIndex (que es el caso de forma predeterminada), su hijo se utilizan los índices de la colección Children para determinar cuál es la parte superior de la otra.

En los programas anteriores, utiliza el siguiente código para establecer los valores de Panel.ZIndex nuevos, donde el elemento de la variable es el elemento que se han utilizado y pnl (de tipo panel) es el elemento primario de dicho elemento y sus nodos relacionados:

for (int i = 0; i < pnl.Children.Count; i++)
     Panel.SetZIndex(pnl.Children[i],
        pnl.Children[i] == element ? pnl.Children.Count : i);

Este código garantiza que el elemento toca obtiene la ZIndex más elevado y aparece en primer plano.

Por desgracia, el elemento que se toca se salta al primer plano en un movimiento repentino, más bien poco natural. A veces otros elementos cambiar lugares al mismo tiempo. (Si dispone de cuatro elementos superpuestos y tocar el primero, obtiene una ZIndex de 4 y el resto tienen el valor ZIndex 1, 2 y 3. Ahora si pulsa el cuarto, el primer vuelve a un ZIndex de 0 y de repente se insertará detrás de todos los demás.)

Mi objetivo era evitar el ajuste repentina de elementos para el primer y segundo plano. Quería un efecto más suave que simular el proceso de deslizarse una foto de debajo de una pila y, a continuación, se retrase en la parte superior. En mi opinión, comencé a pensar en estas transiciones como “ suave Z. ” No hay nada saltaría para situarse en el primer o segundo plano, pero que movió a un elemento a ambos lados, finalmente, encontraría por sí mismo en la parte superior de todas las demás. (Se implementa un método alternativo en el control de ScatterView puede descargar de CodePlex en scatterview.codeplex.com/releases/view/24159 de . ScatterView es preferible sin duda, cuando se trabaja con un gran número de elementos.)

En la implementación de este algoritmo, establecen unos criterios para mí. En primer lugar, no deseo mantener la información de estado de un evento de desplazamiento a la siguiente. En otras palabras, no deseo analizar si el elemento manipulables intersectan otro elemento anteriormente pero ya no era. En segundo lugar, no es necesario realizar las asignaciones de memoria durante los eventos ManipulationDelta porque puede haber muchos de ellos. En tercer lugar, para evitar la complejidad excesiva, deseaba restringir los cambios de la ZIndex relativa al elemento manipulables.

De figura 3 muestra el algoritmo completado. Crucial al enfoque consiste en determinar si dos elementos del mismo nivel se intersecan visualmente. Hay varias formas de hacer esto, pero el código que he utilizado (en el método AreElementsIntersecting) parece la más sencilla. Vuelve a utilizar dos objetos de RectangleGeometry almacenadas como campos.

La figura 3 T , el algoritmo de suavizado Z

// BumpUpZIndex with reusable SortedDictionary object
SortedDictionary<int, UIElement> childrenByZIndex = new 
SortedDictionary<int, UIElement>();
void BumpUpZIndex(FrameworkElement touchedElement, UIElementCollection siblings)
{
  // Make sure everybody has a unique even ZIndex
  for (int childIndex = 0; childIndex < siblings.Count; childIndex++)
  {
        UIElement child = siblings[childIndex];
        int zIndex = Panel.GetZIndex(child);
        Panel.SetZIndex(child, 2 * (zIndex * siblings.Count + childIndex));
  }
  int zIndexNew = Panel.GetZIndex(touchedElement);
  int zIndexCantGoBeyond = Int32.MaxValue;
  // Don't want to jump ahead of any intersecting elements that are on top
  foreach (UIElement child in siblings)
        if (child != touchedElement && 
            AreElementsIntersecting(touchedElement, (FrameworkElement)child))
        {
            int zIndexChild = Panel.GetZIndex(child);
            if (zIndexChild > Panel.GetZIndex(touchedElement))
                zIndexCantGoBeyond = Math.Min(zIndexCantGoBeyond, zIndexChild);
        }
  // But want to be in front of non-intersecting elements
  foreach (UIElement child in siblings)
        if (child != touchedElement && 
            !AreElementsIntersecting(touchedElement, (FrameworkElement)child))
        {
            // This ZIndex is odd, hence unique
            int zIndexNextHigher = 1 + Panel.GetZIndex(child);
            if (zIndexNextHigher < zIndexCantGoBeyond)
                zIndexNew = Math.Max(zIndexNew, zIndexNextHigher);
        }
  // Now give all elements indices from 0 to (siblings.Count - 1)
  Panel.SetZIndex(touchedElement, zIndexNew);
  childrenByZIndex.Clear();
  int index = 0;
  foreach (UIElement child in siblings)
        childrenByZIndex.Add(Panel.GetZIndex(child), child);
  foreach (UIElement child in childrenByZIndex.Values)
        Panel.SetZIndex(child, index++);
    }
// Test if elements are intersecting with reusable //       
RectangleGeometry objects
RectangleGeometry rectGeo1 = new RectangleGeometry();
RectangleGeometry rectGeo2 = new RectangleGeometry();
bool AreElementsIntersecting(FrameworkElement element1, FrameworkElement         
 element2)
{
 rectGeo1.Rect = new 
  Rect(new Size(element1.ActualWidth, element1.ActualHeight));
 rectGeo1.Transform = element1.RenderTransform;
 rectGeo2.Rect = new 
  Rect(new Size(element2.ActualWidth, element2.ActualHeight));
 rectGeo2.Transform = element2.RenderTransform;
 return rectGeo1.FillContainsWithDetail(rectGeo2) != IntersectionDetail.Empty;
}

El método BumpUpZIndex lleva a cabo la mayor parte del trabajo. Comienza con la que hace que todos los elementos relacionados tienen valores únicos de ZIndex y que todos estos valores son números pares. El nuevo ZIndex del elemento manipulado no puede ser mayor que cualquier valor ZIndex de cualquier elemento que es la intersección y actualmente en la parte superior del elemento manipulables. Habida cuenta de este límite, el código intenta asignar un nuevo ZIndex que sea superior a los valores de ZIndex de todos los elementos sin intersección.

Normalmente, el código que he tratado hasta ahora tendrá el efecto de progresivamente el aumento de los valores de ZIndex sin límite, al final que excedan el máximo número entero positivo y convertirse en negativo. Esta situación se evita el uso de un SortedDictionary. Todos los elementos relacionados se colocan en el diccionario con los valores de ZIndex como claves. A continuación, se pueden conceder a los elementos en función de sus índices en el diccionario de nuevos valores de ZIndex.

El algoritmo de suavizado Z tiene una rareza o dos. Si el elemento manipulables intersectan elemento A, pero no el elemento B, a continuación, no se puede se retrasado en la parte superior de B si B tiene una mayor ZIndex que a. Además, se ha producido ningún alojamiento especial para la manipulación de dos o más
elementos a la vez.

Manipulación sin transformaciones

En todos los ejemplos mostrados hasta ahora, he usado la información que se entregan con el evento ManipulationDelta para modificar la propiedad RenderTransform del elemento manipulables. No es la única opción. De hecho, si no necesita rotación, puede implementar en cualquier manipulación multi-touch sin transformaciones.

Este enfoque “ ninguna transformación ” implica el uso de un lienzo como contenedor para los elementos que se manipule. Puede mover los elementos en el lienzo, establezca la Canvas.Left y Canvas.Top propiedades adjuntas. Cambiar el tamaño de los elementos, es necesario manipular las propiedades Height y Width, con los mismos valores de escala de porcentaje usados anteriormente o con los valores absolutos de expansión.

Una ventaja clara de este enfoque es que se pueden decorar los elementos manipulables con un borde que no sí son grandes y pequeños a medida que cambia el tamaño del elemento.

Esta técnica se muestra en el proyecto NoTransformManipulation, que incluye un derivado de UserControl denominado NoTransformPictureFrame que implementa la lógica de manipulación.

En esta nueva clase en el marco de imagen no es casi tan sofisticado como de ManipulablePictureFrame. El marco de imagen anterior utiliza una línea de puntos de un efecto de festones. Si realiza este tipo de un marco más grande para adaptarse a un elemento secundario más grande, pero sin aplicar una transformación, seguirá siendo el grosor de línea de la misma y el número de puntos en la línea de puntos más! Esto es muy peculiar y quizás sea demasiado que distraen la atención de un programa de la vida real. El marco de imagen en el nuevo archivo es sólo un borde simple con esquinas redondeadas.

En el archivo MainPage.xaml en el proyecto NoTransformManipulation, cinco objetos NoTransformPictureFrame se agrupan en un lienzo, todos los que contiene los elementos de la imagen y todo ello con un único Canvas.Left y Canvas.Top de propiedades adjuntas. Además, he dado que el cada NoTransformPictureFrame un ancho del alto de 200, pero no. Al cambiar el tamaño de los elementos de la imagen, normalmente es mejor especificar una única dimensión y permitir que el elemento elija la otra dimensión para mantener la relación de aspecto correcta.

El archivo NoTransformPictureFrame.xaml.cs es similar en la estructura del código ManipulablePictureFrame excepto en que no se requiere ningún código de transformación. El reemplazo de OnManipulationDelta ajusta la Canvas.Left y Canvas.Top propiedades adjuntas y utiliza los valores de expansión para aumentar la propiedad Width del elemento. Sólo un poco de trickiness es necesario cuando la escala es en efecto, porque los factores de conversión deben ser ajustadas para acomodar el centro de escalado.

También se requiere un cambio en el método AreElementsIntersecting que desempeña un papel crucial en las transiciones de Z suaves. El método anterior crea dos objetos de RectangleGeometry que refleja las dimensiones sin transformar de los dos elementos y, a continuación, aplica la configuración de la propiedad RenderTransform dos. De figura 4 muestra el método de reemplazo. Estos objetos RectangleGeometry se basan exclusivamente en el actual tamaño de la Canvas.Left y Canvas.Top el desplazamiento del elemento de propiedades adjuntas.

La figura 4 transforma la lógica de Z suave alternativo para la manipulación sin

bool AreElementsIntersecting(FrameworkElement element1, FrameworkElement element2)
{
    rectGeo1.Rect = new Rect(Canvas.GetLeft(element1), Canvas.GetTop(element1),
      element1.ActualWidth, element1.ActualHeight);
    rectGeo2.Rect = new Rect(Canvas.GetLeft(element2), Canvas.GetTop(element2),
      element2.ActualWidth, element2.ActualHeight);
    return rectGeo1.FillContainsWithDetail(rectGeo2) != IntersectionDetail.Empty;
}

Problemas restantes

A medida que he ha hablar sobre los eventos de manipulación, he sido pasando por alto una característica importante y el elefante en el espacio se ha convertido en grandes. J en la función es la inercia, que va a tratar en el próximo número.

Charles Petzold es editor colaborador de larga para MSDN Magazine. Actualmente, es escribir “ Programming Windows teléfono 7, ” 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 ­ capaz de a través de su sitio Web, de charlespetzold.com.

Gracias a los siguientes expertos técnicos para la revisión de esta columna: Doug Kramer and Robert Levy