Dieser Artikel wurde maschinell übersetzt.

Neue Benutzeroberflächentechnologien

Touch und Response

Charles Petzold

Downloaden des Codebeispiels

Charles PetzoldProgrammierung ist eine technische Disziplin anstatt eine Wissenschaft oder einen Zweig der Mathematik so selten es existiert eine einzelne richtige Lösung für ein Problem. Varianten und Variationen werden und ist oft illuminating untersuchen diese alternativen, sondern konzentrieren Sie sich auf einen bestimmten Ansatz.

In meinem Artikel “ Multi-Touch zum Bearbeiten von Ereignissen in WPF ” in der Ausgabe vom August MSDN Magazine begann ich erkunden die spannende multi-touch Unterstützung in Version 4 von Windows Presentation Foundation (WPF) eingeführt. Die zum Bearbeiten von Ereignissen dienen in erster Linie um Multi-touch Eingaben in nützliche geometrischer Transformationen zu konsolidieren und zur Unterstützung bei der Implementierung von Inertia.

In diesem Artikel wurde ich zwei Ansätze für die Behandlung von Ereignissen zum Bearbeiten von Image-Elemente beziehen. In beiden Fällen wurden die Ereignisse tatsächlich von der Window-Klasse verarbeitet. Ein Programm definierten Handler für die Ereignisse zum Bearbeiten von manipulierten Elemente. Andere Ansatz wurde gezeigt, wie die OnManipulation Methoden erhalten Sie die gleichen Ereignisse, die durch die visuelle Struktur geroutet überschrieben.

Die benutzerdefinierte Klasse Herangehensweise

Eine dritte Methode ist auch sinnvoll: Eine benutzerdefinierte Klasse kann definiert werden, für die manipulierten Elemente, die über eigene Methoden OnManipulation, statt diesen Auftrag verlassen, um ein Containerelement überschreibt. Der Vorteil dieses Ansatzes ist, dass Sie die benutzerdefinierte Klasse ein wenig ansprechender machen können, indem ergänzen Sie mit eines Rahmens oder eines anderen Elements; diese Dekorationen können auch verwendet werden, um visuelles Feedback bereitstellen, wenn der Benutzer ein Element manipulable berührt.

Sie denken wahrscheinlich EventTrigger erfahrener Programmierer von WPF zu ermitteln, die ein Steuerelement basierend auf Ereignissen visuelle Änderungen vornehmen müssen, aber WPF-Programmierer müssen einen Übergang zu Visual State Manager zu starten. Auch wenn von UserControl (Strategie) ableiten ich verwenden werden, ist es relativ einfach zu implementieren.

Eine Anwendung, die zum Bearbeiten von Ereignisse sollten wahrscheinlich visuelles Feedback auf dieselben Ereignisse, sondern die systemnahe Ereignisse TouchDown und Retuschieren basieren. Bei der Verwendung der Ereignisse zum Bearbeiten von sollten Sie die optische Rückmeldung mit ManipulationStarting oder ManipulationStarted Ereignis zu beginnen. (Es wird nicht wirklich einen Unterschied machen der für diesen Auftrag ausgewählt.)

Ist jedoch beim Experimentieren mit dieser Rückmeldung, einer der ersten Vorgänge, die Sie ermitteln müssen die ManipulationStarting und ManipulationStarted-Ereignisse werden nicht ausgelöst, wenn ein Element zuerst betroffen ist, jedoch nur beim Verschieben. Dieses Verhalten ist ein Holdover aus der Schnittstelle Stift, und möchten Sie es ändern, indem Sie die folgende angefügte Eigenschaft im manipulierten-Element:

Stylus.IsPressAndHoldEnabled="False"

Nun werden die ManipulationStarting und ManipulationStarted Ereignisse ausgelöst, wenn ein Element zuerst davon betroffen ist. Möchten Sie die optische Rückmeldung entweder mit der ManipulationInertiaStarting deaktivieren oder zum Bearbeiten von ­ Completed-Ereignis, je nachdem, ob Sie möchten, dass das Feedback zu beenden, wenn der Benutzer Finger vom Bildschirm oder nach dem Element anhebt wurde beendet, die aufgrund von Inertia verschieben. Wenn Ihnen nicht verwendeten Inertia (wie ich in diesem Artikel werden können), es ist egal, welches Ereignis Sie verwenden.

Der herunterladbare Code für diesen Artikel ist in einer einzelnen Visual Studio-Projektmappe mit dem Namen TouchAndResponseDemos mit zwei Projekte. Das erste Projekt heißt FeedbackAndSmoothZ, einschließlich eine benutzerdefinierte UserControl-Ableitung namens ManipulablePictureFrame, die die Bearbeitung von Logik implementiert.

ManipulablePictureFrame definiert eine einzelne Eigenschaft eines Typs untergeordnete und seine statischen Konstruktor verwendet, um die Standardeinstellungen für drei Eigenschaften neu definieren: HorizontalAlignment VerticalAlignment und die entscheidenden IsManipulationEnabled. Der Instanzkonstruktor InitializeComponent (wie gewohnt) aufruft, aber dann wird das Steuerelement RenderTransform auf eine MatrixTransform, ist dies nicht der Fall.

Während des Ereignisses OnManipulationStarting Ruft die Manipulable ­ PictureFrame-Klasse:

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

und während des Ereignisses OnManipulationCompleted aufruft:

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

Hierbei handelt es sich um mein Codedatei alleinigen Beitrag zum Implementieren der visuelle Zustände. Der Code tatsächlichen Manipulationen durchführen wird von dem Code im des letzten Monats vertraut sein, mit zwei wesentliche Änderungen:

  • In der Methode OnManipulationStarting wird die ManipulationContainer auf übergeordneten Elements festgelegt.
  • OnManipulationDelta-Methode ist nur ein wenig einfacher, da das Element bearbeitet wird das Manipulable ­ PictureFrame-Objekt selbst ist.

Abbildung 1 zeigt die vollständige ManipulablePictureFrame.xaml-Datei.

Abbildung 1 Die ManipulablePictureFrame.xaml Datei  

<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>

Rahmen mit der Bezeichnung “ Rahmen ” wird verwendet, um das untergeordnete Element der Klasse ManipulablePictureFrame zu hosten. Dies wird wahrscheinlich Image-Element, aber es ist nicht notwendig sein. Die beiden Elemente der Rechteck zeichnen eine Art von “ gewellte ” Rahmen um den Rahmen und den zweiten Rahmen für visuelles Feedback verwendet wird.

Während ein Element verschoben wird, die Animationen in ManipulablePictureFrame.xaml “ aufhellen ” des Bildes ein wenig, tatsächlich ist es mehr einen Effekt “ Waschen ” – und den Schatten zu erhöhen, wie in der Abbildung 2 .

Figure 2 A Highlighted Element in the FeedbackAndSmoothZ Program

Abbildung 2 eine hervorgehobene Element FeedbackAndSmoothZ-Programm

Fast jede Art von einfachen hervorheben kann während Touch Ereignisse visuelles Feedback bereitstellen. Wenn Sie mit kleinen Elementen arbeiten, die davon betroffen und bearbeitet werden können, sollten Sie die Elemente vergrößern, wenn Sie so, dass Sie vollständig durch den Benutzer Finger ausgeblendet wird nicht betroffen sind. (On hingegen möchten Sie ein Element visuelles Feedback für die größere vornehmen, wenn Sie den Benutzer das Ändern der Größe des Elements auch zulassen, sind. Es ist sehr disconcerting zum Bearbeiten eines Elements in eine gewünschte Größe, und lassen Sie es etwas verkleinern, wenn Sie Ihren Finger vom Bildschirm angehoben!)

Sie werden feststellen, dass Sie weniger und größere Bilder vornehmen, der Frame verkleinert oder entsprechend vergrößert. Ist das richtige Verhalten? Vielleicht. Vielleicht nicht. Zeige ich eine Alternative zu diesem Verhalten gegen Ende dieses Artikels.

Glatte Übergänge von Z

In den Programmen wurde ich letzten Monat ein Foto Retuschieren, um in den Vordergrund zu wechseln verursachen würde. Dies war gerade das einfachste Verfahren, das ich könnte sich vorstellen und erforderliche neue Panel.ZIndex angefügten Eigenschaften für die Image-Elemente festlegen.

Eine kurze Auffrischung Ihrer Kenntnisse: Normalerweise bei der untergeordneten Elemente eines Bereichs überlappen, werden Sie vom Hintergrund Vordergrund ausgehend von Ihrer Position in der Systemsteuerung die Children-Auflistung angeordnet. Panel-Klasse definiert jedoch eine angefügte Eigenschaft mit dem Namen ZIndex, die effektiv den untergeordneten Index ersetzt. (Der Name spielt auf die Z-axis ortho ­ Gonal zu konventionellen XY-Ebene des Bildschirms, die konzeptionell außerhalb des Bildschirms festgelegt wird.) Elemente mit einem niedrigeren Wert für die ZIndex im Hintergrund; höhere ZIndex Werte Ablegen eines Elements im Vordergrund. Wenn zwei oder mehrere überlappende Elemente dieselbe Einstellung für ZIndex (die standardmäßig die Groß-/Kleinschreibung), die Indizes in der Children-Auflistung stattdessen verwendet werden, um zu bestimmen, welche oberhalb des anderen untergeordneten.

In den älteren Programmen verwendet habe ich den folgenden Code Festlegen neuer Panel.ZIndex-Werte, wobei das Variablenelement ist das Element, das gerade berührt und Pnl (vom Typ Panel) ist das übergeordnete Element dieses Element und seine gleichgeordneten Elemente:

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

Dieser Code sichergestellt, dass das Element wirkt die höchste ZIndex ruft, und im Vordergrund angezeigt wird.

Leider gelangen berührt Element in den Vordergrund in einer plötzlichen, sondern unnatürlichen Bewegung. Andere Elemente werden manchmal stellen gleichzeitig wechseln. (Wenn Sie vier überlappende Elemente und die erste berühren, er erhält eine ZIndex von 4 und den anderen ZIndex Werte 1, 2 und 3. Wenn Sie das vierte berühren, der ersten wechselt zu einem ZIndex 0 und wechselt plötzlich hinter allen anderen.)

Mein Ziel war es, vermeiden Sie das plötzliche Andocken von Elementen auf der Vorder- und Hintergrund. Ich wollte eine glattere Auswirkungen, die der überfälligen unterhalb eines Stapels ein Foto und es anschließend wieder im Vordergrund überfälligen imitiert. Bedenken Sie meine ich denken dieser Übergänge als “ reibungslose Z ” gestartet Nichts zu Vorder- oder Hintergrund springen möchten, aber wie Sie ein Element, um den verschoben, schließlich es würde finden selbst über allen anderen. (Ein alternativer Ansatz ist in das Herunterladen von CodePlex scatterview.codeplex.com/releases/view/24159-ScatterView-Steuerelement implementiert. ScatterView ist sicherlich vorzuziehen, beim Umgang mit einer großen Anzahl von Elementen.)

Bei der Implementierung dieses Algorithmus, legte ich einige Kriterien selbst verwenden. Erstens haben, Zustandsinformationen über ein Ereignis für die Verschiebung zum nächsten geführt werden soll. Ich möchte also nicht analysieren, ob das manipulierten Element zuvor ein anderes Element überschritten wurde jedoch nicht mehr. Zweitens haben soll Speicherzuordnungen während der Ereignisse ManipulationDelta durchgeführt werden, da es möglicherweise viele davon. Zu viel Komplexität vermeiden, wollte ich zum dritten Änderungen des relativen ZIndex nur dem manipulierten Element einschränken.

Der vollständige Algorithmus wird im Abbildung 3 angezeigt. Crucial zum Ansatz wird ermittelt, ob zwei gleichgeordnete Elemente visuell überschneiden. Es gibt mehrere Möglichkeiten, wechseln Sie zu diesem, aber der Code (in der Methode AreElementsIntersecting verwendet) schien am einfachsten. Es verwendet zwei RectangleGeometry-Objekte als Felder gespeichert.

Abbildung 3 T er Smooth Z-Algorithmus

// 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;
}

Die BumpUpZIndex-Methode führt den Großteil der Arbeit. Es beginnt, indem Sie sicherstellen, dass die gleichgeordneten Elemente besitzen eindeutige ZIndex Werte und aller Werte geraden Zahlen sind. Die neue ZIndex für manipulierten Element darf nicht größer als jeder Wert ZIndex aller Elemente, die sich überschneidenden ist und derzeit über die manipulierten-Element sein. Diese Grenze unter Berücksichtigung, versucht der Code, um eine neue ZIndex zuzuweisen, die höher ist als die ZIndex Werte aller nicht überschneidenden Elemente.

Der Code, der bisher erörtert habe haben normalerweise die Auswirkungen der progressiv steigenden ZIndex gegen unendlich, schließlich überschreiten den maximale positiver Ganzzahlwert und immer negativ. Diese Situation ist mit einem SortedDictionary vermieden. Die gleichgeordneten Elemente werden als Schlüssel in das Wörterbuch mit den Werten ZIndex eingefügt. Die Elemente können dann neue ZIndex Werte basierend auf deren Indizes im Wörterbuch angegeben sein.

Der Algorithmus Smooth Z hat eine Besonderheit oder zwei. Wenn das manipulierten Element Element A jedoch nicht Element B überschritten wird, kann nicht dann es über B überfällig sein wenn B eine höhere ZIndex als a. Darüber hinaus wurde keine spezielle Unterkunft zum Bearbeiten von zwei oder mehr
Elemente zur gleichen Zeit.

Manipulation ohne Transformationen

In allen Beispielen, die ich bisher gezeigt haben, habe ich Informationen gesendet, die mit dem Ereignis ManipulationDelta verwendet, RenderTransform manipulierten Elements zu ändern. Dies ist nicht die einzige Option dar. Wenn Sie die Drehung nicht benötigen, können Sie multi-touch Manipulation ohne auf Transformationen in der Tat überhaupt implementieren.

Dieser Ansatz “ keine Transformation ” umfasst eine Canvas als Container für die manipulierten Elemente verwenden. Anschließend können Sie die Elemente auf der Leinwand verschieben, durch Festlegen der Canvas.Left und Canvas.Top angefügten Eigenschaften. Bearbeiten Sie die Eigenschaften Height und Width, entweder mit den gleichen Prozentsatz skalieren Werten, die zuvor verwendet oder mit den absoluten Werten für die Erweiterung von Ändern der Größe der Elemente benötigt werden.

Einen deutlichen Vorteil dieses Ansatzes ist, dass die manipulierten Elemente mit einem Rahmen, die selbst wird nicht geworden ergänzen können, größerer und kleinerer, wie Sie die Größe des Elements ändern.

Diese Technik wird im Projekt NoTransformManipulation veranschaulicht, der umfasst eine UserControl-Ableitung namens NoTransformPictureFrame, die die Bearbeitung von Logik implementiert.

Der Grafikrahmen in diese neue Klasse ist fast so wie in der ManipulablePictureFrame Fantasievolle nicht. Frühere Grafikrahmen verwendet eine gepunktete Linie eine gewellte Wirkung zu erzielen. Wenn Sie einen solchen Rahmen vergrößern, um größere untergeordnetes Element aufnehmen zu können, aber ohne Anwenden einer Transformation, die Linienstärke bleibt erhöht identisch, und die Anzahl der Punkte in der gepunkteten Linie! Dies sieht sehr besondere und ist wahrscheinlich für eine reale Anwendung zu stören. Der Grafikrahmen in der neuen Datei ist nur eine einfache Rahmen mit abgerundeten Ecken.

In der Datei im Projekt NoTransformManipulation MainPage.xaml werden fünf NoTransformPictureFrame Objekte auf einem Canvas, alle Image-Elemente enthält, und alle mit eindeutigen Canvas.Left und Canvas.Top angefügten Eigenschaften zusammengestellt. Auch habe ich eine Breite von 200, aber keine Höhe jedes NoTransformPictureFrame angezeigt. Beim Ändern der Größe von Image-Elemente ist es in der Regel sollten Sie nur eine Dimension angeben, und lassen Sie das Element aus, wählen Sie die andere Dimension um richtige Seitenverhältnis beizubehalten.

NoTransformPictureFrame.xaml.cs-Datei ist in der Struktur der ManipulablePictureFrame Code ähnlich, außer dass keine Transformation Code erforderlich ist. Die Überschreibung OnManipulationDelta passt die Canvas.Left und Canvas.Top angefügten Eigenschaften und die Expansion Werte verwendet, um die Width-Eigenschaft des Elements zu erhöhen. Nur ein wenig Trickiness ist erforderlich, wenn Skalierung in Kraft ist da die Übersetzung Faktoren werden, um den Mittelpunkt der Skalierung zu berücksichtigen angepasst müssen.

Eine Änderung wurde auch in der Methode AreElementsIntersecting erforderlich, die die reibungslose Übergänge von Z eine entscheidende Rolle spielt. Frühere Methode zwei reflektieren die ohne Transformation Dimensionen aus zwei Elementen RectangleGeometry-Objekte erstellt und anschließend die beiden RenderTransform-Einstellungen angewendet. Die Ersetzungsmethode wird im Abbildung 4 angezeigt. Diese Objekte RectangleGeometry beruhen ausschließlich auf die tatsächliche Größe der Element-Offset vom Canvas.Left und Canvas.Top angefügten Eigenschaften.

Abbildung 4 Alternative Smooth Z-Logik für die Manipulation ohne Transformationen

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;
}

Verbleibende Probleme

Als ich die Ereignisse zum Bearbeiten von erörtert wurden haben, ich habe eine wichtige Funktion ignoriert wurden, und der Elephant im Raum ist größer und größer geworden. Do am Feature ist Inertia, die ich in der nächsten Ausgabe befassen werde.

Charles Petzold schreibt seit langem redaktionelle Beiträge für das MSDN Magazin. Zurzeit schreibt er den Artikel „Programming Windows Phone 7“, der als kostenloses, herunterladbares E-Book im Herbst 2010 veröffentlicht wird. Eine Vorschau-Edition ist derzeit auf seiner Website charlespetzold.com verfügbar.

Dank an den folgenden technischen Experten für die Überprüfung dieser Spalte: Doug Kramer and Robert Levy