Finger Style

Einführung in die Multitouch-Unterstützung in Silverlight

Charles Petzold

Beispielcode herunterladen

Jedes Mal, wenn ich das American Museum of Natural History in New York City besuche, lege ich Wert darauf, den Saal mit den Primaten zu besuchen. Mit einer umfangreichen Sammlung an Skeletten und ausgestopften Exemplaren stellt der Saal ein evolutionäres Panorama der Ordnung der Primaten dar, Tiere, die in der Größe von winzigen Tupajas, Lemuren und Seidenaffen bis zu Schimpansen, Menschenaffen und Menschen reichen.

Was in dieser Ausstellung ins Auge springt, ist ein auffallendes Merkmal, das allen Primaten gemeinsam ist: die Knochenstruktur der Hand mit einem opponierbaren Daumen. Dieselbe Anordnung von Gelenken und Fingern, die es unseren Vorahnen und entfernten Verwandten ermöglichte Zweige von Bäumen zu greifen und erklettern, erlaubt es unserer Spezies, die Welt um uns herum zu manipulieren und Dinge zu erschaffen. Unsere Hände mögen aus den Pfoten winziger Primaten, die vor Millionen von Jahren lebten, hervorgegangen sein und sind doch ein Hauptfaktor dessen, was uns als Menschen ausmacht.

Ist es da verwunderlich, dass wir instinktiv mit der Hand auf Objekte auf dem Computerbildschirm zeigen oder diese sogar berühren?

In Reaktion auf dieses menschliche Begehren, unsere Finger in eine etwas engere Beziehung mit dem Computer zu bringen, haben sich auch unsere Eingabegeräte weiterentwickelt. Die Maus ist fantastisch zum Auswählen und Ziehen, aber völlig ungeeignet zum Anfertigen von Freihandzeichnungen oder Handschriften. Mit dem Eingabestift können wir schreiben, aber nicht so gut etwas Aufspannen oder Verschieben. Berührungsbildschirme kennen wir von Geldautomaten oder Museumskiosks, sie sind in der Regel aber auf einfaches Zeigen und Drücken beschränkt.

Ich halte die Technologie, die als "Multitouch" bezeichnet wird, für einen großen Fortschritt. Wie der Name schon sagt, geht Multitouch über die Berührungsbildschirme der Vergangenheit hinaus, da mehrere Finger erkannt werden, und dadurch können ganz andere Arten von Bewegungen und Gesten über den Bildschirm vermittelt werden. Multitouch hat sich aus früheren berührungsorientierten Eingabegeräten entwickelt, legt gleichzeitig jedoch ein ganz anderes Eingabeparadigma nahe.

Multitouch ist wahrscheinlich in Nachrichtensendungen im Fernsehen am offenkundigsten, wo Meteorologen oder Experten Landkarten auf Großbildschirmen handhaben. Microsoft hat verschiedene Versuche in Richtung Multitouch unternommen, vom couchtischgroßen Microsoft Surface-Computer bis zu kleinen Geräten wie dem Zune HD, und die Technologie wird auch auf Smartphones immer mehr zum Standard.

Zwar kann Microsoft Surface auf viele gleichzeitige Finger reagieren (und enthält sogar interne Kameras, um auf dem Glas platzierte Objekte anzuzeigen), die meisten anderen Multitouch-Geräte sind jedoch auf eine diskrete Anzahl beschränkt. Viele reagieren nur auf zwei Finger bzw. so genannte Touchpoints oder Berührungspunkte. (Hier werden die Begriffe Finger und Berührungspunkt fast synonym verwendet.) Hier sind jedoch Synergieeffekte festzustellen: Auf dem Computerbildschirm sind zwei Finger mehr als doppelt so mächtig wie ein Finger.

Die Beschränkung auf zwei Berührungspunkte ist für die Multitouch-Monitore, die in letzter Zeit für Desktop-PCs und Laptops auf den Markt kamen, sowie für den maßgeschneiderten Acer Aspire 1420P-Laptop charakteristisch, der im letzten November an die Teilnehmer der Microsoft Professional Developers Conference (PDC) verteilt wurde und gemeinhin als PDC-Laptop bezeichnet wird. Die Verteilung des PDC-Laptops bot Tausenden von Entwicklern die einmalige Gelegenheit, Anwendungen mit Multitouch-Unterstützung zu schreiben.

Mit dem PDC-Laptop habe ich die Multitouch-Unterstützung unter Silverlight 3 untersucht.

Silverlight-Ereignisse und -Klassen

Multitouch-Unterstützung wird in den verschiedenen Windows-APIs und -Frameworks zum Standard. Multitouch-Unterstützung wurde in Windows 7 und die kommende Windows Presentation Foundation (WPF) 4 integriert. (Der Microsoft Surface-Computer basiert auch auf WPF, enthält jedoch einige benutzerdefinierte Erweiterungen für seine sehr speziellen Funktionen.)

In diesem Artikel möchte ich mich auf die Multitouch-Unterstützung in Silverlight 3 konzentrieren. Die Unterstützung ist etwas mager, aber sicherlich angemessen und sehr hilfreich, wenn man grundlegende Multitouch-Konzepte erforschen möchte.

Wenn Sie eine Multitouch-Silverlight-Anwendung auf Ihrer Website veröffentlichen, stellt sich die Frage, wer sie nutzen kann? Der Benutzer benötigt natürlich einen Multitouch-Monitor, muss die Silverlight-Anwendung aber auch unter einem Betriebssystem und Browser ausführen, die Multitouch unterstützen. Bislang bietet Internet Explorer 8 unter Windows 7 diese Unterstützung, und wahrscheinlich werden in der Zukunft weitere Betriebssysteme und Browser Multitouch unterstützen.

Die Multitouch-Unterstützung von Silverlight 3 besteht aus fünf Klassen, einem Delegaten, einer Enumeration und einem einzigen Ereignis. Es lässt sich nicht feststellen, ob das Silverlight-Programm auf einem Multitouch-Gerät ausgeführt wird oder, wenn es auf einem solchen Gerät ausgeführt wird, wie viele Berührungspunkte das Gerät unterstützt.

Eine Silverlight-Anwendung, die auf Mehrfingereingaben reagieren möchte, muss für das statische Touch.FrameReported-Ereignis einen Ereignishandler definieren:

Touch.FrameReported += OnTouchFrameReported;

Sie können diesen Ereignishandler auf Computern verwenden, die nicht mit Multitouch-Monitoren ausgestattet sind, ohne dass Fehler auftreten. Das FrameReported-Ereignis ist der einzige öffentliche Member der statischen Touch-Klasse. Der Handler sieht folgendermaßen aus:

void OnTouchFrameReported(
  object sender, TouchFrameEventArgs args) {
  ...
}

Sie können mehrere Touch.FrameReported-Ereignishandler in einer Anwendung installieren, und alle Handler werden alle Touch-Ereignisse melden, die irgendwo in der Anwendung auftreten.

TouchFrameEventArgs verfügt über eine öffentliche Eigenschaft namens TimeStamp, die ich bislang noch nicht verwendet habe, und drei grundlegende öffentliche Methoden:

  • TouchPoint GetPrimaryTouchPoint(UIElement relativeTo)
  • TouchPointCollection GetTouchPoints(UIElement relativeTo)
  • void SuspendMousePromotionUntilTouchUp()

Das Argument von GetPrimaryTouchPoint bzw. GetTouchPoints dient lediglich zur Angabe von Positionsdaten im TouchPoint-Objekt. Sie müssen dieses Argument nicht angeben. Wenn das Argument NULL ist, beziehen sich die Positionsdaten auf die obere linke Ecke des Silverlight-Anwendungsfensters.

Multitouch kann Mehrfingereingaben auf dem Bildschirm verarbeiten, und jeder Finger (bis zur maximalen Anzahl, die gegenwärtig üblicherweise bei zwei liegt), der den Bildschirm berührt, stellt einen Berührungspunkt dar. Der erste Berührungspunkt bezieht sich auf den Finger, der bei nicht gedrückter Maustaste als erster Finger den Bildschirm berührt.

Berühren Sie den Bildschirm mit einem Finger. Das ist der primäre Berührungspunkt. Lassen Sie den ersten Finger noch auf dem Bildschirm, und führen Sie einen anderen Finger zum Bildschirm. Offensichtlich ist der zweite Finger kein primärer Berührungspunkt. Lassen Sie jetzt den zweiten Finger auf dem Bildschirm, heben Sie den ersten Finger an, und legen Sie ihn wieder auf den Bildschirm. Ist das jetzt ein primärer Berührungspunkt? Nein. Ein primärer Berührungspunkt ist nur gegeben, wenn keine anderen Finger den Bildschirm berühren.

Ein primärer Berührungspunkt wird dem Berührungspunkt zugeordnet, der an die Maus weitergeleitet wird. In echten Multitouch-Anwendungen dürfen Sie sich nicht auf den primären Berührungspunkt stützen, weil der Benutzer in der Regel der ersten Bildschirmberührung keine besondere Bedeutung beimisst.

Ereignisse werden nur für Finger ausgelöst, die tatsächlich den Bildschirm berühren. Hier gibt es keine Hovererkennung für Finger, die sich sehr nahe am Bildschirm befinden, diesen aber nicht berühren.

Standardmäßig werden Aktivitäten, an denen der primäre Berührungspunkt beteiligt ist, an verschiedene Mausereignisse weitergeleitet. Ihre vorhandenen Anwendungen können daher auf Berührungen reagieren, ohne speziellen Code zu erfordern. Das Berühren des Bildschirms wird zu einem MouseLeftButtonDown-Ereignis, das Bewegen des Fingers, während er den Bildschirm berührt, wird zu einem MouseMove-Ereignis, und das Anheben des Fingers wird ein MouseLeftButtonUp-Ereignis.

Das MouseEventArgs-Objekt, das Mausnachrichten begleitet, enthält eine Eigenschaft namens StylusDevice, mit deren Hilfe sich Mausereignisse von Stift- oder Fingerberührungsereignissen unterscheiden lassen. Auf dem PDC-Laptop habe ich die Erfahrung gemacht, dass die DeviceType-Eigenschaft den Wert TabletDeviceType.Mouse hat, wenn das Ereignis durch die Maus verursacht wurde, und den Wert TabletDeviceType.Touch, wenn der Bildschirm mit dem Finger oder dem Stift berührt wird, wobei nicht zwischen diesen beiden Eingabemedien unterschieden wird.

Nur der primäre Berührungspunkt wird an Mausereignisse weitergeleitet, und (wie der Name der dritten Methode von TouchFrameEventArgs nahelegt) Sie können diese Weiterleitung unterbinden. Auf dieses Thema wird in Kürze näher eingegangen.

Abhängig von einem Berührungspunkt oder mehreren Berührungspunkten kann ein bestimmtes Touch.FrameReported-Ereignis ausgelöst werden. Die TouchPointCollection-Auflistung, die von der GetTouchPoints-Methode zurückgegeben wird, enthält alle Berührungspunkte, die zu einem bestimmten Ereignis gehören. Das von der GetPrimaryTouchPoint-Methode zurückgegebene TouchPoint-Objekt repräsentiert stets einen primären Berührungspunkt. Wenn einem bestimmten Ereignis kein primärer Berührungspunkt zugeordnet ist, gibt GetPrimaryTouchPoint NULL zurück.

Selbst wenn das von GetPrimaryTouchPoint zurückgegebene TouchPoint-Objekt nicht NULL ist, wird es mit keinem der TouchPoint-Objekte identisch sein, die von GetTouchPoints zurückgegeben werden, obwohl alle Eigenschaften den gleichen Wert haben, wenn den Methoden das gleiche Argument übergeben wird.

Die TouchPoint-Klasse definiert die folgenden vier Abrufeigenschaften, die jeweils durch Abhängigkeitseigenschaften unterstützt werden:

  • Action vom Typ TouchAction, eine Enumeration mit den Elementen Down, Move und Up.
  • Position vom Typ Point, die sich auf das Element bezieht, das der Methode GetPrimaryTouchPoint oder GetTouchPoints als Argument übergeben wurde (oder bezieht sich auf die obere linke Ecke des Anwendungsfensters, wenn das Argument NULL ist).
  • Size vom Typ Size. Da auf dem PDC-Laptop keine Größenangaben verfügbar sind, habe ich mit dieser Eigenschaft noch gar nicht gearbeitet.
  • TouchDevice vom Typ TouchDevice.

Die SuspendMousePromotionUntilTouchUp-Methode kann nur dann vom Ereignishandler aufgerufen werden, wenn GetPrimaryTouchPoint ein Objekt zurückgibt, das nicht NULL ist, und die Action-Eigenschaft den Wert TouchAction.Down hat.

Die TouchDevice-Klasse weist zwei Abrufeigenschaften auf, die auch durch Abhängigkeitseigenschaften unterstützt werden:

  • DirectlyOver vom Typ UIElement, das oberste Element unter dem Finger.
  • Id vom Typ int.

DirectlyOver muss kein untergeordnetes Element des Elements sein, das an GetPrimaryTouchPoint oder GetTouchPoints übergeben wird. Diese Eigenschaft kann NULL sein, wenn sich der Finger innerhalb der Silverlight-Anwendung befindet (was durch die Abmessungen des Silverlight-Plug-In-Objekts definiert wird), jedoch nicht in einem Bereich, der von einem auf Treffer prüfbaren Steuerelement umschlossen wird. (Für Panel-Elemente, deren Background-Eigenschaft NULL zugewiesen wurde, können keine Treffertests durchgeführt werden.)

Die ID-Eigenschaft ist beim Unterscheiden mehrerer Finger von zentraler Bedeutung. Am Anfang einer bestimmten Folge von Ereignissen, die einem bestimmten Finger zugeordnet ist, hat die Action-Eigenschaft stets den Wert Down, wenn der Finger den Bildschirm berührt. Anschließend folgen Move-Ereignisse, und abgeschlossen wird die Folge durch ein Up-Ereignis. Allen diesen Ereignissen wird dieselbe ID zugeordnet. (Gehen Sie jedoch nicht davon aus, dass ein primärer Berührungspunkt den ID-Wert 0 oder 1 hat.)

In den meisten etwas anspruchsvolleren Multitouch-Programmen wird die Dictionary-Auflistung verwendet, wobei die ID-Eigenschaft der TouchDevice-Instanz den Wörterbuchschlüssel bildet. Auf diese Weise werden Informationen zu einem bestimmten Finger über mehrere Ereignisse hinweg gespeichert.

Untersuchen der Ereignisse

Wenn man ein neues Eingabegerät ausprobiert, ist es immer hilfreich, eine kleine Anwendung zu schreiben, die die Ereignisse auf dem Bildschirm protokolliert, sodass man sich ein Bild davon machen kann. Der herunterzuladende Code zu diesem Artikel enthält unter anderem ein Projekt namens MultiTouchEvents. Dieses Projekt besteht aus zwei nebeneinander angeordneten TextBox-Steuerelementen, in denen die Multitouch-Ereignisse für zwei Finger angezeigt werden. Wenn Sie einen Multitouch-Monitor besitzen, können Sie dieses Programm ausführen unter charlespetzold.com/silverlight/MultiTouchEvents.

Die XAML-Datei enthält nur ein zweispaltiges Raster mit zwei TextBox-Steuerelementen namens txtbox1 und txtbox2. Die Codedatei ist in Abbildung 1 abgebildet.

Abbildung 1 Code für MultiTouchEvents

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace MultiTouchEvents {
  public partial class MainPage : UserControl {
    Dictionary<int, TextBox> touchDict = 
      new Dictionary<int, TextBox>();

    public MainPage() {
      InitializeComponent();
      Touch.FrameReported += OnTouchFrameReported;
    }

    void OnTouchFrameReported(
      object sender, TouchFrameEventArgs args) {

      TouchPoint primaryTouchPoint = 
        args.GetPrimaryTouchPoint(null);

      // Inhibit mouse promotion
      if (primaryTouchPoint != null && 
        primaryTouchPoint.Action == TouchAction.Down)
        args.SuspendMousePromotionUntilTouchUp();

      TouchPointCollection touchPoints = 
        args.GetTouchPoints(null);

      foreach (TouchPoint touchPoint in touchPoints) {
        TextBox txtbox = null;
        int id = touchPoint.TouchDevice.Id;
        // Limit touch points to 2
        if (touchDict.Count == 2 && 
          !touchDict.ContainsKey(id)) continue;

        switch (touchPoint.Action) {
          case TouchAction.Down:
            txtbox = touchDict.ContainsValue(txtbox1) ? 
              txtbox2 : txtbox1;
            touchDict.Add(id, txtbox);
            break;

          case TouchAction.Move:
            txtbox = touchDict[id];
            break;
 
          case TouchAction.Up:
            txtbox = touchDict[id];
            touchDict.Remove(id);
            break;
        }

        txtbox.Text += String.Format("{0} {1} {2}\r\n", 
          touchPoint.TouchDevice.Id, touchPoint.Action, 
          touchPoint.Position);
        txtbox.Select(txtbox.Text.Length, 0);
      }
    }
  }
}

Beachten Sie die Wörterbuchdefinition am Anfang der Klasse. Das Wörterbuch verfolgt, welches TextBox-Steuerelement den beiden Berührungspunkt-IDs zugeordnet ist.

Der OnTouchFrameReported-Handler unterbindet zuerst, dass Ereignisse an Mausereignishandler weitergeleitet werden. Das ist der einzige Grund für den Aufruf von GetPrimaryTouchPoint, und sehr oft der einzige Grund, warum diese Methode in einem realen Programm aufgerufen wird.

In einer foreach-Schleife werden die TouchPoint-Member der von GetTouchPoints zurückgegebenen TouchPointCollection-Auflistung aufgelistet. Weil das Programm nur zwei TextBox-Steuerelemente enthält und nur in der Lage ist, zwei Berührungspunkte zu handhaben, ignoriert es weitere Berührungspunkte, wenn das Wörterbuch bereits zwei Berührungspunkte enthält und die ID nicht im Wörterbuch gefunden wird. (Ein Silverlight-Programm mit Multitouch-Unterstützung soll zwar mehrere Finger handhaben können, aber auch nicht abstürzen, wenn es auf zu viele Finger trifft!) Die ID wird dem Wörterbuch nach einem Down-Ereignis hinzugefügt und nach einem Up-Ereignis aus dem Wörterbuch entfernt.

Sie werden bemerken, dass die TextBox-Steuerelemente gelegentlich durch zu viel Text steckenbleiben, und Sie müssen den gesamten Text markieren und löschen (Strg+A, Strg+X), damit das Programm wieder ordnungsgemäß ausgeführt wird.

Aus diesem Programm können Sie ersehen, dass Multitouch-Eingaben auf der Anwendungsebene erfasst werden. Wenn Sie beispielsweise Ihren Finger auf die Anwendung drücken und dann von der Anwendung wegbewegen, empfängt die Anwendung weiterhin Move-Ereignisse und schließlich ein Up-Ereignis, wenn Sie Ihren Finger heben. Sobald eine Anwendung Multitouch-Eingaben erhält, werden sogar Multitouch-Eingaben bei anderen Anwendungen verhindert und der Mauszeiger verschwindet.

Diese anwendungszentrierte Erfassung von Multitouch-Eingaben ermöglicht es der MultiTouchEvents-Anwendung, sehr selbstsicher zu sein. Beispielsweise geht das Programm bei Move- und Down-Ereignissen einfach davon aus, dass die ID im Wörterbuch enthalten ist. Eine reale Anwendung sollte mehr Prüf- und Fehlerbehandlungsmechanismen enthalten, für den Fall, dass etwas schiefläuft, aber Sie erhalten in jedem Fall das Down-Ereignis.

Zweifingermanipulation

Ein übliches Multitouch-Szenario ist eine Fotogalerie, bei der Sie mit den Fingern Fotos verschieben, in der Größe ändern und drehen können. Ich beschloss, etwas Ähnliches zu erstellen – einfach um die einschlägigen Konzepte besser kennen zu lernen –, das allerdings auch etwas einfacher sein sollte. Meine Version des Programms kann nur eine Sache manipulieren, nämlich die Zeichenfolge des englischen Wortes "TOUCH". Sie können das TwoFingerManipulation-Programm auf meiner Website unter charlespetzold.com/silverlight/TwoFingerManipulation ausführen.

Wenn Sie eine Anwendung für Multitouch programmieren, werden Sie bei Steuerelementen, die Multitouch-Eingaben unterstützen, die Weiterleitung an Mausereignishandler wahrscheinlich immer unterbinden. Damit das Programm aber auch ohne Multitouch-Monitor brauchbar ist, fügen Sie spezielle Verarbeitungsroutinen für Mausereignisse hinzu.

Auch wenn nur eine Maus oder ein einzelner Finger gegeben ist, können Sie die Zeichenfolge im TwoFingerManipulation-Programm verschieben, aber Sie können nur ihre Position verändern – ein grafischer Vorgang, der auch Translation genannt wird. Mit zwei Fingern am Multitouch-Bildschirm können Sie das Objekt auch skalieren und drehen.

Als ich mich mit einem Block und einem Stift hinsetzte, um den Algorithmus auszutüfteln, den ich für diese Skalierung und Drehung brauchte, wurde es bald klar, dass es keine eindeutige Lösung gab!

Angenommen, ein Finger bleibt fest auf dem Punkt ptRef. (Hier beziehen sich alle Punkte auf die Anzeigefläche unter dem Objekt, das manipuliert wird.) Der andere Finger bewegt sich von Punkt ptOld zu ptNew. Wie in Abbildung 2 dargestellt, genügen diese drei Punkte zur Berechnung horizontaler und vertikaler Skalierungsfaktoren für das Objekt.

Abbildung 2 In Skalierungsfaktoren konvertierte Zweifingerbewegung

Der horizontale Skalierungsfaktor ist beispielsweise der Zuwachs im Abstand der Punkte ptOld.X und ptNew.X vom Punkt ptRef.X oder:

scaleX = (ptNew.X – ptRef.X) / (ptOld.X – ptRef.X)

Die vertikale Skalierung ist ähnlich. In dem Beispiel in Abbildung 2 ist der horizontale Skalierungsfaktor gleich 2 und der vertikale Skalierungsfaktor ist ½.

Das ist sicherlich die einfachste Methode, diese zu programmieren. Das Programmverhalten sieht natürlicher aus, wenn die beiden Finger das Objekt zudem drehen. Dies wird in Abbildung 3 dargestellt.

Abbildung 3 In Drehung und Skalierung konvertierte Zweifingerbewegung

Zuerst werden die Winkel der beiden Vektoren (von ptRef bis ptOld und von ptRef bis ptNew) berechnet. (Die Math.Atan2-Method ist ideal für diese Aufgabe.) Dann wird ptOld um die Differenz der beiden Winkel um ptRef gedreht. Dieser gedrehte Punkt ptOld wird dann zusammen mit ptRef und ptNew zur Berechnung der Skalierungsfaktoren verwendet. Diese Skalierungsfaktoren sind viel kleiner, weil eine Drehungskomponente entfernt wurde.

Der tatsächliche Algorithmus (der in der ComputeMoveMatrix-Methode in der C#-Datei implementiert wurde) war dann recht einfach. Das Programm erforderte jedoch einigen Code zur Unterstützung von Transformationen, um die Mängel der Transformationsklassen von Silverlight zu kompensieren, bei denen im Gegensatz zu WPF keine Value-Eigenschaft oder Matrizenmultiplikation verfügbar ist.

Im wirklichen Programm können beide Finger gleichzeitig bewegt werden, und die Interaktion zwischen den beiden Fingern ist einfacher zu handhaben, als es anfangs erscheint. Jeder sich bewegende Finger wird unabhängig vom anderen Finger verwaltet, wobei der andere Finger als Referenzpunkt dient. Trotz der höheren Komplexität der Berechnung sieht das Ergebnis natürlicher aus, und ich denke, es gibt dafür eine einfache Erklärung: Im wirklichen Leben werden Objekte sehr häufig mit den Fingern gedreht, aber es ist sehr ungewöhnlich, sie mit den Fingern zu skalieren.

Die Drehung ist in der realen Welt so gängig, dass es sinnvoll ist, sie zu implementieren, wenn ein Objekt nur mit einem Finger oder der Maus manipuliert wird. Dies wird im alternativen AltFingerManipulation-Programm gezeigt (das Sie ausführen können unter: charlespetzold.com/silverlight/AltFingerManipulation). Bei Verwendung von zwei Fingern arbeitet das Programm so wie das Programm TwoFingerManipulation. Wird ein Finger verwendet, dann berechnet es die Drehung in Bezug auf den Mittelpunkt des Objekts, und jede zusätzliche, vom Mittelpunkt nach außen gerichtete Bewegung wird zur Translation verwendet.

Ereignisbehandlung mit weiteren Ereignissen

Im Allgemeinen verwende ich gerne die Klassen, die Microsoft wohlüberlegt in einem Framework bereitstellt, statt sie in meinem eigenen Code zu verpacken. Allerdings hatte ich Multitouch-Anwendungen im Sinn, die von einer etwas ausgeklügelteren Ereignisschnittstelle profitieren würden.

Erstens wollte ich ein System, das etwas modularer ist. Ich wollte einige benutzerdefinierte Steuerelemente, die ihre Fingereingaben selbst handhabten, mit vorhandenen Silverlight-Steuerelementen kombinieren, bei denen Fingereingaben einfach in Mausausgaben umgewandelt werden. Zudem wollte ich das Aufzeichnen der Eingaben implementieren. Obwohl die Silverlight-Anwendung selbst Eingaben von Multitouch-Geräten erfasst, wollte ich, dass einzelne Steuerelemente einen bestimmten Berührungspunkt unabhängig von anderen Steuerelementen aufzeichnen.

Außerdem wollte ich die Ereignisse Enter und Leave. In gewisser Hinsicht widersprechen diese Ereignisse dem Aufzeichnungsparadigma. Um den Unterschied zu verstehen, stellen Sie sich eine Klaviertastatur auf dem Bildschirm vor, bei der jede Taste eine Instanz eines PianoKey-Steuerelements ist. Zuerst stellen Sie sich diese Tasten vielleicht wie die Schaltflächen vor, die mit der Maus bedient werden. Wenn die Maustaste gedrückt wird, aktiviert das PianoKey-Steuerelement die Ausgabe eines Tons, und wenn die Maustaste losgelassen wird, deaktiviert es die Tonausgabe.

Das beschreibt aber nicht, wie Klaviertasten eigentlich funktionieren sollen. Sie möchten in der Lage sein, mit den Fingern über die Tastatur zu streichen, um Glissando-Effekte zu erzielen. Die Tasten sollten sich eigentlich nicht um Down- und Up-Ereignisse kümmern müssen. In Wahrheit sind nur die Enter- und Leave-Ereignisse relevant.

In WPF 4 und Microsoft Surface gibt es bereits Routingereignisse für Multitouch-Eingaben, und in künftigen Versionen von Silverlight werden sie sicherlich auch verfügbar sein. Meine aktuellen Anforderungen habe ich jedoch durch eine Klasse erfüllt, die ich TouchManager nannte und im Petzold.MultiTouch-Bibliotheksprojekt in der TouchDialDemos-Projektmappe implementierte. Ein Großteil von TouchManager besteht aus statischen Methoden, Feldern und einem als static deklarierten Ereignishandler für das Touch.FrameReported-Ereignis, der es der Klasse ermöglicht, Multitouch-Ereigisse in der gesamten Anwendung zu verwalten.

Eine Klasse, die sich bei TouchManager registrieren möchte, erstellt wie folgt eine Instanz von TouchManager:

TouchManager touchManager = new TouchManager(element);

Das Konstruktorargument ist vom Typ UIElement, und in der Regel handelt es sich dabei um das Element, das das Objekt erstellt:

TouchManager touchManager = new TouchManager(this);

Durch die Registrierung bei TouchManager gibt die Klase an, dass sie an allen Multitouch-Ereignissen interessiert ist, bei denen die DirectlyOver-Eigenschaft von TouchDevice ein untergeordnetes Element des Elements ist, das dem TouchManager-Konstruktor übergeben wurde, und dass diese Multitouch-Ereignisse nicht an Mausereignisse weitergeleitet werden sollen. Es gibt es keine Möglichkeit, die Registrierung eines Elements aufzuheben.

Nachdem eine neue Instanz von TouchManager erstellt wurde, können Klassen Ereignishandler für die Ereignisse TouchDown, TouchMove, TouchUp, TouchEnter, TouchLeave und LostTouchCapture installieren:

touchManager.TouchEnter += OnTouchEnter;

Alle Handler werden entsprechend dem EventHandler<TouchEventArgs>-Delegaten definiert:

void OnTouchEnter(
  object sender, TouchEventArgs args) {
  ...
}

TouchEventArgs definiert vier Eigenschaften:

  • Source vom Typ UIElement repräsentiert das Element, das ursprünglich dem TouchManager-Konstruktor übergeben wurde.
  • Position vom Typ Point. Diese Position ist auf Source bezogen.
  • DirectlyOver vom Typ UIElement, wird einfach aus dem TouchDevice-Objekt kopiert.
  • Id vom Typ int, wird auch einfach aus dem TouchDevice-Objekt kopiert.

Nur während der Verarbeitung des TouchDown-Ereignisses darf eine Klasse die Capture-Methode mit der diesem Ereignis zugeordneten Berührungspunkt-ID aufrufen:

touchManager.Capture(id);

Alle weiteren Fingereingaben für diese ID werden an das Element gesendet, die dieser TouchManager-Instanz zugeordnet ist, bis das TouchUp-Ereignis ausgelöst oder ReleaseTouchCapture explizit aufgerufen wird. In beiden Fällen löst TouchManager das LostTouchCapture-Ereignis aus.

Die Ereignisse treten für gewöhnlich in der folgenden Reihenfolge auf: TouchEnter, TouchDown, TouchMove, TouchUp, TouchLeave und LostTouchCapture (sofern zutreffend). Natürlich können verschiedene TouchMove-Ereignisse zwischen TouchDown und TouchUp auftreten. Wenn ein Berührungspunkt nicht registriert wurde, können mehrere Ereignisse in der Reihenfolge TouchLeave, TouchEnter und TouchMove auftreten, weil der Berührungspunkt ein registriertes Element verlässt und ein anderes Element betritt.

Das TouchDial-Steuerelement

Änderungen an den Benutzereingabeparadigmen machen es oft notwendig, dass alte Annahmen über den richtigen Entwurf von Steuerelementen und anderen Eingabemechanismen in Frage gestellt werden müssen. Beispielsweise sind nur wenige Steuerelemente von grafischen Benutzeroberflächen so fest verwurzelt wie die Bildlaufleiste oder der Schieberegler. Diese Steuerelemente werden zum Navigieren in großen Dokumenten oder Bildern, aber auch als winzige Lautstärkeregler in der Medienwiedergabe verwendet.

Als ich mir überlegte, eine Lautstärkeregelung zu entwickeln, die auf Berührungen reagierte, fragte ich mich, ob der alte Ansatz wirklich der Richtige war. In der Realität werden gelegentlich Schieberegler als Lautstärkeregler verwendet; dies ist im Allgemeinen aber nur bei professionellen Mixern oder grafischen Equalizern der Fall. In der wahren Welt sind die meisten Lautstärkeregler Drehknöpfe. Ist möglicherweise ein Drehknopf eine bessere Lösung für eine Lautstärkeregelung mit Multitouch-Unterstützung?

Ich werde nicht so tun, als hätte ich die definitive Antwort, aber ich zeige Ihnen, wie Sie einen Lautstärkeregler erstellen.

Das TouchDial-Steuerelement ist in der Petzold.MultiTouch-Bibliothek der TouchDialDemos-Projektmappe enthalten (nähere Einzelheiten finden Sie im Codedownload). TouchDial ist von RangeBase so abgeleitet, dass die Eigenschaften Minimum, Maximum und Value genutzt werden können, einschließlich der Erzwingungslogik, um Value im Bereich zwischen Minimum und Maximum zu halten, und des ValueChanged-Ereignisses. In TouchDial sind die Eigenschaften Minimum, Maximum und Value jedoch Winkel in der Einheit Grad.

TouchDial reagiert sowohl auf die Maus als auch auf Berührung und erfasst unter Verwendung der TouchManager-Klasse Berührungspunkte. Sowohl bei Maus- als auch bei Fingereingaben ändert TouchDial die Value-Eigenschaft während eines Move-Ereignisses anhand der neuen Position und der vorherigen Position der Maus bzw. des Fingers bezogen auf den Mittelpunkt. Die Aktion unterscheidet sich von der in Abbildung 3 dargestellten nur dadurch, dass keine Skalierung erfolgt. Im Move-Ereignishandler wird die Math.Atan2-Methode verwendet, um kartesische Koordinaten in Winkelgrade zu konvertieren, und dann wird die Differenz der beiden Winkel dem Wert von Value hinzugefügt.

TouchDial enthält keine Standardvorlage und hat daher auch keine vorgegebene visuelle Darstellung. Wenn Sie TouchDial verwenden, besteht Ihre Aufgabe darin, eine Vorlage bereitzustellen. Diese kann jedoch ganz einfach sein und einige wenige Elemente umfassen. Offensichtlich sollte sich etwas in dieser Vorlage entsprechend den Änderungen der Value-Eigenschaft drehen. Der Einfachheit halber stellt TouchDial nur die RotateTransform-Abrufeigenschaft bereit, wobei die Angle-Eigenschaft gleich der Value-Eigenschaft von RangeBase ist und die Eigenschaften CenterX und CenterY den Mittelpunkt des Steuerelements darstellen.

Abbildung 4 enthält eine XAML-Datei mit zwei TouchDial-Steuerelementen, die Verweise auf einen Stil und eine Vorlage enthalten, die als Ressourcen definiert wurden.

Abbildung 4 Die XAML-Datei für das SimpleTouchDialTemplate-Projekt

<UserControl x:Class="SimpleTouchDialTemplate.MainPage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:multitouch="clr-namespace:Petzold.MultiTouch;assembly=Petzold.MultiTouch">
  <UserControl.Resources>
    <Style x:Key="touchDialStyle" 
      TargetType="multitouch:TouchDial">
      <Setter Property="Maximum" Value="180" />
      <Setter Property="Minimum" Value="-180" />
      <Setter Property="Width" Value="200" />
      <Setter Property="Height" Value="200" />
      <Setter Property="HorizontalAlignment" Value="Center" />
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="multitouch:TouchDial">
            <Grid>
              <Ellipse Fill="{TemplateBinding Background}" />
              <Grid RenderTransform="{TemplateBinding RotateTransform}">
                <Rectangle Width="20" Margin="10"
                  Fill="{TemplateBinding Foreground}" />
              </Grid>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </UserControl.Resources>
    
  <Grid x:Name="LayoutRoot">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
        
    <multitouch:TouchDial Grid.Column="0"
      Background="Blue" Foreground="Pink"
      Style="{StaticResource touchDialStyle}" />
        
    <multitouch:TouchDial Grid.Column="1"
      Background="Red" Foreground="Aqua"
      Style="{StaticResource touchDialStyle}" />
  </Grid>
</UserControl>

Beachten Sie, dass der Stil die Maximum-Eigenschaft auf 180 und die Minimum-Eigenschaft auf -180 festlegt, damit der Balken um 180 Grad nach links und rechts gedreht werden kann. (Komischerweise funktionierte das Programm nicht richtig, wenn ich die Reihenfolge dieser beiden Eigenschaften in der Style-Definition vertauschte.) Der Drehknopf besteht einfach aus einem in einer Ellipse enthaltenen Balken, der aus einem Rectangle-Element gefertigt wurde. Der Balken befindet sich in einem einzelligen Grid-Element, dessen RenderTransform-Eigenschaft an die von TouchDial berechnete RotateTransform-Eigenschaft gebunden ist.

Abbildung 5 zeigt, wie das SimpleTouchDialTemplate-Programm während der Ausführung aussieht.

Abbildung 5 Das SimpleTouchDialTemplate-Programm

Sie können es ausprobieren (ebenso wie die nächsten beiden Programme, die ich hier besprechen werde). Besuchen Sie hierzu charlespetzold.com/silverlight/TouchDialDemos.

Es ist etwas umständlich, den Balken mit der Maus im Kreis zu drehen, mit dem Finger ist diese Drehung viel natürlicher. Beachten Sie, dass Sie den Balken an eine beliebige Position im Kreis drehen können, wenn Sie die linke Maustaste drücken (oder den Bildschirm mit dem Finger berühren). Während Sie den Balken drehen, können Sie die Maus oder den Finger weg bewegen, da beide erfasst werden.

Wenn der Benutzer den Balken nur drehen können soll, wenn sich der Mauszeiger oder Finger direkt über dem Balken befindet und dann die Maustaste gedrückt bzw. der Bildschirm berührt wird, dann können Sie die IsHitTestVisible-Eigenschaft des Ellipse-Objekts auf False festlegen.

Meine erste Version des TouchDial-Steuerelements besaß noch keine RotateTransform-Eigenschaft. Ich hielt es für sinnvoller, wenn die Vorlage eine explizite RotateTransform-Eigenschaft enthielte, wobei die Angle-Eigenschaft das Ziel einer TemplateBinding-Instanz sein sollte, die eine Bindung an die Value-Eigenschaft des Steuerelements definierte. Allerdings funktionieren in Silverlight 3 Bindungen nicht mit Eigenschaften von Klassen, die nicht von FrameworkElement abgeleitet sind, und daher kann die Angle-Eigenschaft von RotateTransform kein Bindungsziel sein (in Silverlight 4 wurde dies korrigiert).

Eine Drehung erfolgt immer in Bezug auf einen Mittelpunkt, und dies schlichte Tatsache macht das TouchDial-Steuerelement kompliziert. TouchDial verwendet den Mittelpunkt auf zweierlei Weise: zur Berechnung der in Abbildung 3 dargestellten Winkel und zum Festlegen der Eigenschaften CenterX und CenterY der von ihm erstellten RotateTransform-Instanz. Standardmäßig berechnet das TouchDial-Steuerelement beide Mittelpunkte, indem die Werte der Eigenschaften ActualWidth und ActualHeight halbiert werden. Daraus ergibt sich der Mittelpunkt des Steuerelements, was in vielen Fällen aber nicht der gewünschte Punkt ist.

Nehmen wir beispielsweise an, bei der Vorlage in Abbildung 4 soll die RenderTransform-Eigenschaft des Rectangle-Objekts an die RotateTransform-Eigenschaft des TouchDial-Steuerelements gebunden werden. Das funktioniert nicht, weil das TouchDial-Steuerelement die Eigenschaften CenterX und CenterY von RotateTransform auf 100 festlegt, sich die Mitte des Rectangle-Objekts bezogen auf sich selbst jedoch am Punkt (10, 90) befindet. Damit diese Standardwerte, die das TouchDial-Steuerelement auf der Grundlage der Steuerelementgröße berechnet, überschrieben werden können, definiert das Steuerelement die Eigenschaften RenderCenterX und RenderCenterY. In der SimpleTouchDialTemplate-Eigenschaft können Sie diese Eigenschaften etwa folgendermaßen festlegen:

<Setter Property="RenderCenterX" Value="10" />
<Setter Property="RenderCenterY" Value="90" />

Sie können diese Eigenschaften aber auch auf Null (0) festlegen und die RenderTransformOrigin-Eigenschaft des Rectangle-Elements definieren, um den auf das Rechteck bezogenen Mittelpunkt anzugeben:

RenderTransformOrigin="0.5 0.5"

Sie möchten das TouchDial-Steuerelement möglicherweise auch in Fällen verwenden, in denen sich der Punkt, der als Referenz der Maus- oder Fingerbewegung dient, nicht im Mittelpunkt des Steuerelements befindet. In diesem Fall können Sie die Eigenschaften InputCenterX und InputCenterY abrufen, um die Standardwerte zu überschreiben.

Abbildung 6 enthält die XAML-Datei für das OffCenterTouchDial-Projekt.

Abbildung 6 XAML-Datei für das OffCenterTouchDial-Projekt

<UserControl x:Class="OffCenterTouchDial.MainPage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:multitouch="clr-namespace:Petzold.MultiTouch;assembly=Petzold.MultiTouch">
  <Grid x:Name="LayoutRoot">
    <multitouch:TouchDial Width="300" Height="200" 
      HorizontalAlignment="Center" VerticalAlignment="Center"
      Minimum="-20" Maximum="20"
      InputCenterX="35" InputCenterY="100"
      RenderCenterX="15" RenderCenterY="15">
      <multitouch:TouchDial.Template>
        <ControlTemplate TargetType="multitouch:TouchDial">
          <Grid Background="Pink">
            <Rectangle Height="30" Width="260"
              RadiusX="15" RadiusY="15" Fill="Lime"
              RenderTransform="{TemplateBinding RotateTransform}" />
            <Ellipse Width="10" Height="10"
              Fill="Black" HorizontalAlignment="Left"
              Margin="30" />
          </Grid>
        </ControlTemplate>
      </multitouch:TouchDial.Template>
    </multitouch:TouchDial>
  </Grid>
</UserControl>

Diese Datei enthält nur ein TouchDial-Steuerelement, wobei die Eigenschaften für das Steuerelement direkt festgelegt werden, und der Template-Eigenschaft wird eine Vorlage namens Control zugewiesen, die ein einzelliges Grid-Objekt mit einem Rectangle- und einem Ellipse-Element enthält. Die Ellipse ist ein winziger symbolischer Drehpunkt für das Rechteck, den Sie um 20 Grad nach oben und unten schwenken können (siehe Abbildung 7).


Abbildung 7 Das OffCenterTouchDial-Programm

Da sich die Eigenschaften InputCenterX und InputCenterY immer auf das gesamte Steuerelement beziehen, geben Sie die Position des Mittelpunkts des Ellipse-Elements innerhalb des rosafarbenen Grid-Elements an. Die Eigenschaften RenderCenterX und RenderCenterY sind immer auf den Teil des Steuerelements bezogen, dem die RotateTransform-Eigenschaft zugeordnet wurde.

Lautstärkeregler und Stimmpfeifen

Die beiden letzten Beispiele zeigen, wie Sie das Erscheinungsbild von TouchDial gestalten können, indem Sie entweder die Template-Eigenschaft explizit im Markup-Code festlegen oder, wenn die Vorlage für mehrere Steuerelemente verwendet werden soll, einen Verweis auf ein als Ressource definiertes ControlTemplate-Objekt angeben.

Sie können auch eine neue Klasse von TouchDial ableiten und die XAML-Datei nur zum Festlegen einer Vorlage verwenden. Dies ist bei RidgedTouchDial aus der Petzold.MultiTouch-Bibliothek so. RidgedTouchDial unterscheidet sich von TouchDial nur dadurch, dass dieses Steuerelement eine bestimmte Größe und visuelle Darstellung hat (die Sie gleich sehen werden). ///RidgedTouchDial unterscheidet sich von TouchDial nur dadurch, dass dieses Steuerelement eine bestimmte Größe und visuelle Darstellung hat (die Sie gleich sehen werden).

Zudem ist es möglich, TouchDial (oder eine davon abgeleitete Klasse wie RidgedTouchDial) in einer von UserControl abgeleiteten Klasse zu verwenden. Dieser Ansatz hat den Vorteil, dass Sie alle in RangeBase definierten Eigenschaften, einschließlich Minimum, Maximum und Value, verbergen und durch eine neue Eigenschaft ersetzen können.

Dies ist bei VolumeControl der Fall. VolumeControl wurde von RidgedTouchDial abgeleitet, damit es dessen visuelle Darstellung hat, und mit einer neuen Eigenschaft namens Volume ausgestattet. Die Volume-Eigenschaft wird durch eine Abhängigkeitseigenschaft unterstützt. Sobald diese Eigenschaft verändert wird, wird ein VolumeChanged-Ereignis ausgelöst.

In der XAML-Datei für VolumeControl wird einfach auf das RidgedTouchDial-Steuerelement verwiesen, und es werden darin verschiedene Eigenschaften, darunter Minimum, Maximum und Value, festgelegt:

<src:RidgedTouchDial 
  Name="touchDial"
  Background="{Binding Background}"
  Maximum="150"
  Minimum="-150"
  Value="-150"
  ValueChanged="OnTouchDialValueChanged" />

Das TouchDial-Steuerelement kann sich also zwischen Minimum und Maximum um 300 Grad drehen. In Abbildung 8 ist die Datei VolumeControl.xaml.cs dargestellt. Das Steuerelement übersetzt den 300-Grad-Bereich des Drehknopfs in eine logarithmische Dezibelskala, die von 0 bis 96 reicht.

Abbildung 8 Die C#-Datei für VolumeControl

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

namespace Petzold.MultiTouch {
  public partial class VolumeControl : UserControl {
    public static readonly DependencyProperty VolumeProperty =
      DependencyProperty.Register("Volume",
      typeof(double),
      typeof(VolumeControl),
      new PropertyMetadata(0.0, OnVolumeChanged));

    public event DependencyPropertyChangedEventHandler VolumeChanged;

    public VolumeControl() {
      DataContext = this;
      InitializeComponent();
    }

    public double Volume {
      set { SetValue(VolumeProperty, value); }
      get { return (double)GetValue(VolumeProperty); }
    }

    void OnTouchDialValueChanged(object sender, 
      RoutedPropertyChangedEventArgs<double> args) {

      Volume = 96 * (args.NewValue + 150) / 300;
    }

    static void OnVolumeChanged(DependencyObject obj, 
      DependencyPropertyChangedEventArgs args) {

      (obj as VolumeControl).OnVolumeChanged(args);
    }

    protected virtual void OnVolumeChanged(
      DependencyPropertyChangedEventArgs args) {

      touchDial.Value = 300 * Volume / 96 - 150;

      if (VolumeChanged != null)
        VolumeChanged(this, args);
    }
  }
}

Weshalb 96? Obwohl die Dezibelskala auf Dezimalzahlen basiert (sobald sich die Amplitude eines Signals um einen Multiplikationsfaktor von 10 erhöht, erhöht sich die Lautstärke linear um 20 Dezibel), gilt auch, dass 10 hoch 3 näherungsweise gleich 2 hoch 10 ist. Das bedeutet wiederum, dass sich die Lautstärke um 6 dB erhöht, wenn sich die Amplitude verdoppelt. Wenn die Amplitude durch einen 16-Bit-Wert dargestellt wird (was bei CD- und PC-Sounddaten der Fall ist), erhält man einen Bereich von 16 Bit mal 6 Dezibel pro Bit oder 96 Dezibel.

Die PitchPipeControl-Klasse ist auch von UserControl abgeleitet und definiert eine neue Eigenschaft namens Frequency. Die XAML-Datei enthält ein TouchDial-Steuerelement sowie einige TextBlocks-Objekte zum Anzeigen der 12 Töne einer Oktave. PitchPipeControl nutzt überdies eine andere Eigenschaft von TouchDial, die bislang noch nicht besprochen wurde: Wenn SnapIncrement auf einen Wert ungleich Null (0) in Winkelgraden festgelegt wird, ist die Bewegung des Drehknopf nicht flüssig, sondern von Inkrement zu Inkrement springend. Weil PitchPipeControl auf die 12 Töne einer Oktave festgelegt werden kann, wird die SnapIncrement-Eigenschaft auf 30 Grad festgelegt.

Abbildung 9 zeigt das PitchPipe-Programm, in dem VolumeControl und PitchPipeControl kombiniert wurden. Sie können das PitchPipe-Programm ausführen unter charlespetzold.com/.


Abbildung 9 Das PitchPipe-Programm

Das Bonusprogramm

An früherer Stelle in diesem Artikel erwähnte ich bereits im Kontext eines Beispiels ein Steuerelement namens PianoKey. PianoKey ist tatsächlich ein Steuerelement und eines der Steuerelemente des Piano-Programms, das Sie ausführen können unter charlespetzold.com/. Für das Programm sollte das Browserfenster maximiert werden. (Oder drücken Sie F11, um Internet Explorer in den Vollbildmodus zu versetzen und noch mehr Platz auf dem Bildschirm zu schaffen.) Ein winziger Ausschnitt ist in Abbildung 10 dargestellt. Die Tastatur wurde in sich überlappende Teile für Höhe und Bässe unterteilt. Der rote Punkt markiert das eingestrichene C.


Abbildung 10 Das Piano-Programm

Für dieses Programm habe ich die TouchManager-Klasse geschrieben, weil im Piano-Programm Fingereingaben auf dreierlei Weise interpretiert werden. Das blaue VolumeControl-Steuerelement, das den Berührungspunkt nach einem TouchDown-Ereignis erfasst und die Eingabeaufzeichnung nach einem TouchUp-Ereignis beendet, wurde bereits besprochen. Die PianoKey-Steuerelemente, aus denen die Tastaturen bestehen, verwenden TouchManager ebenso, aber diese Steuerelemente reagieren nur auf die Ereignisse TouchEnter und TouchLeave. Hier können Sie die Finger tatsächlich über die Tasten gleiten lassen, um Glissando-Effekte zu erzielen. Die braunen Rechtecke, die als Haltepedale fungieren, sind ganz gewöhnliche Silverlight-ToggleButton-Steuerelemente. Sie verfügen über keine spezielle Multitouch-Unterstützung; stattdessen werden Berührungspunkte in Mausereignisse umgewandelt.

Der Piano-Programm zeigt drei verschiedene Arten des Multitouch-Einsatzes auf. Ich nehme an, dass es sehr viel mehr Einsatzmöglichkeiten gibt.

 

Charles Petzold* schreibt seit langem redaktionelle Beiträge für das MSDN Magazin. Sein neuestes Buch heißt "The Annotated Turing: A Guided Tour Through Alan Turing’s Historic Paper on Computability and the Turing Machine" (Wiley, 2008). Petzold veröffentlicht einen Blog auf seiner Website charlespetzold.com.*

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels:  Robert Levy und Anson Tsao