Session D-DRAG

Drag&Drop mit Visual FoxPro
(Teil 1)

Frank Dietrich
Dietrich Datentechnik


EINLEITUNG


Grundsätzliches

In Visual FoxPro ist jetzt die Möglichkeit Drag&Drop zu nutzen direkt eingebaut. Von VFP werden fünf Tools zur Verfügung gestellt, um das ganze Drum und Dran zu handlen. Dies sind die DragMode-Eigenschaft, die Drag- Methode, die DragIcon-Eigenschaft, der DragOver-Event und der DragDrop-Event. Damit eng verbunden sind der Move-Event und die MouseDown- und MouseUp-Events. Zusätzlich gibt es ein Möglichkeit Drag&Drop "zu Fuß" zu programmieren auf die ich weiter unten eingehen werde und die ich für die Verschiebung von Objekten, nicht von Daten, präferiere.


Die Tools

Mit Hilfe der DragMode - Eigenschaft kann bestimmt werden, ob VFP das Drag selbständig einleiten soll (1 = Automatic) oder, ob wir über unser Programm das Einleiten steuern möchten (0 = Manual = Standardvorgabe).

Wenn DragMode auf Automatic gestellt wird, kann sofort mit dem Drag begonnen werden; einfach mit der Maus draufklicken und ziehen. Das war es aber auch schon fast. Ich habe bisher keine Möglichkeit gefunden zwischen dem Klicken und dem ziehen noch etwas abzufangen, festzuhalten oder dergleichen. Die MouseDown- und MouseUp-Events werden nicht angesprochen, ebensowenig die Drag-Methode, die der eigentliche Controller des Drag&Drop ist. Der Click-Event wird nicth ausgeführt. Somit können bestimmte Vorgänge, die meines Erachtens nach ein Drag&Drop begleiten sollten, und auf die ich weiter unten eingehen werde, nicht realisiert werden.

Hinzukommt, daß grundsätzlich wärend eines Ziehvorgangs KeyPress-, MouseDown-, MouseUp- und MouseMove-Events nicht erkannt werden können. Und schließlich - und daran habe ich zunächst gar nicht gedacht - können einige Objekte, die für einen Drag&Drop-Vorgang vorgesehen sind nicht angeklickt werden. Jedes Anklicken wird als Drag-Versuch gewertet, das Objekt aber selbst nicht angewählt. Eine dragbare Checkbox ist damit (fast) nutzlos, denn ihr Wert kann durch die Maus nicht verändert werden. Da auch der MouseDown-Event nicht abgefangen werden kann, hilft nur eins: Hinhumpeln. >>> Drag01.SCX > Drag

Da mir bisher außer für den Notfall keine vernünftige Anwendung für die Einstellung Automatic eingefallen ist, müssen wir den manuellen Weg gehen. Dieser ist, einmal verstanden, auch nicht viel komplizierter, bietet aber erheblich bessere Eingriffs- und Steuermöglichkeiten. Um Ein Drag einzuleiten braucht man nämlich nur die Drag-Methode mit dem Parameter 1 aufzurufen. Das kann beispielsweise im MouseDown-Event geschehen:

Das reicht im Prinzip schon aus, um das Objekt dem Automatic-Mode in etwa gleichzusetzen. Jedoch gibt es ja bereits hier eine Vielzahl von Eingriffsmöglichkeiten, die es erlauben zu bestimmen, ob die Drag-Methode aufgerufen wird oder nicht (nur Donnerstag Nachmittags ?). So kann man über diesen Weg auch steuern, daß ein Objekt sowohl anklickbar als auch dragbar ist:

 

Entgegen der Beschreibung in VFP-Handbuch und -Hilfe ist es zwar möglich einen Drag-Vorgang durch einen MouseDown-Event einzuleiten, aber nicht, ihn durch einen MouseUp-Event zu beenden. Wie bereits erwähnt reagiert ein Objekt wärend eines Drag-Vorganges nicht auf Maus- oder Tastaturereignisse und damit auch nicht auf einen MouseUp-Event. Das kann zu recht skurilen Effekten führen: Zieht man beispielsweise ein Objekt 1 an einen Ort an dem das Droppen nicht korrekt abgearbeitet wird und läßt es dort fallen, wird der MouseUp-Event des Objektes 1 erst beim nächsten Klick ausgelöst - selbst dann, wenn dieser auf einem Objekt 2 erfolgt!! DRAG01.SCX > Drag 3 zeigt diesen Effekt.

Mit hilfe der Drag-Methode wird das Ziehen eingeleitet (1), beendet (2) oder abgebrochen (0). Um einen der drei Stati zu anzuwählen braucht die Methode lediglich mit dem entsprechenden Wert aufgerufen zu werden. Es braucht kein weiterer Code in der Methode zu stehen, um ein Objekt in den Drag-Modus zu versetzen oder diesen zu beenden. Es können jedoch mit einem Case-Statement weitere Aktionen ausgeführt werden:

 

Bezüglich der Drag-Methode legt VFP ein Verhalten an den Tag, durch das ich noch nicht vollständig durchgestiegen bin: Wird ein Objekt fallengelassen, egal, ob zum Abbrechen oder zum Droppen wird optisch der Drag-Vorgang beendet, d.h. der Cursor erhält wieder seine normale Form. Die Drag-Methode wird jedoch nicht automatisch angesprochen. Das ist insbesondere dann unglücklich, wenn zum Drag-Beginn bestimmte Bedingungen hergestellt werden, die nach dem Fallenlassen wieder aufgehoben werden sollen - Beispielsweise Farbmarkierungen oder Hilfetexte. Bedeutet dies' doch, daß in jedem anderen Objekt auf dem Schirm ein Verweis auf die eigene Drag-Methode erfolgen muß.

Wird in die DragIcon-Eigenschaft nichts eingetragen, verändert sich der Cursor mit Beginn des Drag zu einem schraffierten Rahmen mit einem darinliegenden Mauszeiger, so wie man es von WinWord oder Excel her kennt. Der Rahmen hat die Größe des Objektes.

Es ist jedoch auch möglich den Cursor wärend des Drag-Vorganges selbst zu bestimmen. Im Verzeichnis SAMPLES\GRAPHICS\CURSORS befinden sich eine Reihe von fertigen Cursordateien, die man hierfür verwenden kann. Vorsicht ist jedoch bei der Auswahl geboten: VFP mag nur monochrome Cursor, und ein Großteil der mitgelieferten Beispiel-Cursor ist mehrfarbig. Die Auswahl eines farbigen Cursors erzeugt einen FoxPro-Fehler "Cursordatei defekt oder ungültig", wie auch in der Dokumentation erwähnt. Umso fraglicher ist der Zweck der mitgelieferten farbigen Cursordateien.

VFP (zumindest in der Professional-Version) liegt aber das Program IMAGEEDIT bei, mit dem es nun endlich ohne Umwege möglich ist Icons, Bitmaps und auch Cursordateien zu erstellen und zu verändern. Damit können die farbigen Cursor schnell in monochrome umgewandelt werden und sind dann doch noch zu gebrauchen.

In "Zusammenarbeit" mit dem DragOver-Event ist über die DragIcon-Eigenschaft auch wärend eines Drag-Vorganges eine visuelle Lenkung des Drag&Drop möglich. So kann der Cursor beispielsweise in verbotenen Drop-Zonen die Form eines Halteverbotsschildes annehmen.

Im Gegensatz zu den bisher behandelten Eigenschaften und Methoden werden die bei einem DragOver-Event auszuführenden Zeilen nicht bei dem Objekt eingetragen, das gezogen wird, sondern bei dem Objekt, über das ein zu ziehendes Objekt möglicherweise gezogen wird. Kurz: Es wird nicht gefragt, "Was geschieht, wenn ich über Objekt XY gezogen werde?", sondern "Was geschieht, wenn Objekt XY über mich gezogen wird?". Das wiederum bedeutet, daß

und

Im Falle des DragOver-Events ist das nicht allzu tragisch, da hier in der Regel nicht allzuviel geschieht. Man kann ja auch umgekehrt vorgehen und ein Zieh-Objekt zunächst als "Drop-nicht-erlaubt" initialisieren (>> DragIcon) und nur bei den zulässigen Objekten ein "Drop-erlaubt" daraus machen. Die gleiche Konstellation gilt jedoch auch für den DragDrop-Event bei dem tatsächlich etwas geschehen muß!

Das Geschehen bei einem DragOver läßt sich dabei sehr genau beobachten und beeinflussen, da neben dem Objekt auch ein Status an den Event übergeben wird. Dabei werden die Stati "Objekt wird in meinen Zielbereich hineinbewegt" (0), "Objekt wird aus meinem Zielbereich herausbewegt" (1) und "Objekt wird innerhalb meines Zielbereiches bewegt" (2) unterschieden. So kann in "Zusammenarbeit" mit der DragIcon-Eigenschaft ein bestimmtes Objekt optisch als verboten markiert werden:

 

Aber vorsicht! Nur weil dem User gezeigt wird "Hier darfst Du nicht", heißt das noch lange nicht, daß er das Objekt nicht dennoch auf den verbotenen Bereich fallen lassen kann! Sie müssen selbst im DragDrop-Ereignis dafür Sorge tragen.

Wie auch der DragOver-Event wird der DragDrop-Event beim Zielobjekt eingetragen. Also auch hier gilt nicht die Frage "Was geschieht, wenn ich über Objekt XY fallengelassen werde", sondern "Was geschieht, wenn Objekt XY über mir fallengelassen wird?".

Der DragDrop-Event wird ausgelöst, wenn der User die Maustaste wärend eines Drag-Vorganges losläßt. Es wird dabei stets der DragDrop-Event des Objektes angesprochen, über dem sich der Mauszeiger gerade befindet. Wie bereits erwähnt wird der MouseUp-Event dabei nicht angesprochen.

Im Prinzip ist damit der Drag-Vorgang beendet. Die Drag-Methode des Drag-Objektes wird dabei jedoch nicht automatisch angesprochen, obwohl es eigentlich wünschenswert und sinnvoll wäre, daß die Drag-Methode mit der "Abbruch-Option" ausgeführt wird, wenn sie nicht anders aufgerufen wird. Wenn Sie mit Drag&Drop arbeiten wollen, sollten Sie daher in jedes Objekt auf den entsprechenden Forms einen Eintrag

in den DragDrop-Event vornehmen, damit alle denkbaren Drag-Vorgänge zumindest sauber abgebrochen werden können.


Das Verschieben selbst

Durch die Nutzung der fünf Drag&Drop-Tools wird faktisch noch gar nichts gedraggt und erst recht nichts gedroppt. Genau betrachtet liefern uns die Tools auch lediglich Verweise und Schnittstellen auf und zu Quelle und Ziel eines Drag&Drop-Szenarios.

Grundsätzlich sind zwei unterschiedliche Ziele denkbar, die mit einem Drag&Drop verfolgt werden können: Das Verschieben oder Kopieren von Objekten und das Verschieben oder Kopieren von Daten.

Wenn ein Drag-Vorgang beginnt, ändert sich die Form des Cursors und weist den User daraufhin, daß er jetzt sich jetzt im Dragmodus befindet. Einige Drag-Objekte verändern sich jedoch in keiner Weise (und lassen sich im automatischen Modus auch nicht verändern). Angenommen, Sie haben eine Form mit zehn untereinander liegenden Textobjekten mit kleiner Schrift. Diese kann der Anwender einzeln an einen bestimmten Platz ziehen und die Daten dort abladen. Kann der User nach dem Beginn des Drag sagen, ob er nun das richtige Element angeklickt hat - vielleicht arbeitet er an einem Notebook mit so einer kleinen zittrigen Maus?

Sie sollten ihm daher eine ausreichende Information bieten. Dies ist beim manuellen Ziehen in der Drag-Methode problemlos möglich. Setzen Sie beispielsweise die Hintergrundfarbe um (This.Backcolor = ...), zeigen Sie einen Hilfetext an oder lassen Sie sich etwas Anderes einfallen - es ist nicht sehr aufwendig. Natürlich müssen Sie dafür sorgen, daß diese Markierung hinterher auch wieder entfernt wird.

Objekte die aus mehreren Objekten bestehen wie beispielsweise selbstdefinierte Klassen oder Container-Objekte die selbst Objekte halten, verdienen eine gesonderte Betrachtung. Jedes der einzelnen Teilobjekte verfügt dabei über einen eigenen Satz an Drag&Drop-Tools. Da Sie aber das Gesamtobjekt als eine Einheit betrachten, möchten Sie es auch bei einem Verschiebevorgang als Ganzes ziehen. Zwar ist der Container, der solche Objektgruppen in der Regel umgibt, ihr "Ansprechpartner" für das Drag&Drop, jedoch ist er selbst für das "Klicken" nur an seinem Rand sensibel. D.h., klicken Sie in die Mitte eines solchen Gruppenobjektes, wird die MouseDown-Routine für das entsprechende Objekt an dieser Stelle ausgeführt. Wenn Sie Pech haben, dann befindet sich an dieser Stelle aber gar kein Objekt. In diesem Fall wird die MouseDown-Routine der Form ausgeführt.

Es gibt jedoch einen einfachen Trick, um diesem Problem zu begegnen: legen Sie als oberstes Layer ein Shape-Objekt mit den Abmessungen des Containers an. Dieses Objekt sollte transparent sein und keinen Rahmen haben. Setzen Sie die visible-Eigenschaft jaber nicht auf .F., da es sonst nicht auf Maus-Klicks reagieren kann.

Dieses unsichtbare Shape-Objekt stellt dann Ihre Referenz für das Drag&Drop-Handling dar.

Bei vielen und/oder kleinen Drop-Objekten kann es mitunter schwierig sein das richtige Ziel zu treffen (denken wir an den Notebook-User mit der zittrigen Maus). Wenn Sie, wie bereits im vorangegangenen Abschnitt ausgeführt, ein transparentes (aber visible) Shape-Objekt als Referenz über das eigentliche Objekt legen, können sie über dessen Ausmaße die Sensibilität des Zielobjektes steuern. Sie können die Sensibilität sogar über einen Parameter zur Laufzeit verändern, indem Sie die Größe des Shapes verändern.

Das Verschieben von Objekten ist grundsätlich sehr einfach und funktioniert sogar mit der DragMode-Einstellung "Automatic", denn im DragDrop-Event wird die aktuelle Mausposition übergeben. Sie brauchen lediglich die Move-Methode des Quell-Objektes im DragDrop-Event anzusprechen:

Sooo leicht geht das.

Allerdings bezeichnen die Koordinaten die linke oberere Ecke des Objektes. Beim Drag&Drop wird der User aber naturgemäß das Objekt eher in der Mitte versuchen zu ziehen. Deswegen sollten Sie die relative Mausposition zur linken oberen Ecke zum Beginn des Drag-Vorganges auf Form-Ebene festhalten und hinterher im DragDrop-Event wieder herstellen:

im MouseDown-Event oder in der Drag-Methode des Quell-Objektes:

 

im DragDrop-Event des Ziel-Objektes:

 

Das funktioniert natürlich nur bei manuellem DragMode, da andernfalls die Möglichkeit des Abspeicherns der Position nicht gegeben ist.

Bei der im vorangegangenen Abschnitt beschriebenen Methode wird ein Objekt nicht wirklich verschoben, sondern es hopst an eine bestimmte Position. ein genaues Positionieren ist mit dieser Methode gar nicht möglich, da eine Neupositionierung überhaupt erst ab einem bestimmten Abstand zur vorherigen Position stattfindet.

Eine Methode mit der sich aber ein echtes Verschieben realisieren läßt, kann man sich "zu Fuß", also ohne die Nutzung der Drag&Drop-Tools, unter VFP auch leicht selbst "basteln". Diese Methode nutzt dabei lediglich den MouseDown-, MouseMove- und MouseUp-Event und ist damit gleichzeitig autark, d.h. unabhängig von irgendwelchen "Gegenobjekten". Folgende Schritte werden dabei durchlaufen (vgl. die Klasse HAUS in DRAGDROP.VCX):

Anmerkung: Die Klasse Haus ist aus mehreren Objekten zusammengesetzt, die in ein Container-Objekt eingebettet sind. Das Verändern der Position eines Container-Objektes hat zur Folge, daß die Visible-Eigenschaft auf .F. gesetzt wird. Daher ist in den folgenden Code-Beispielen die Zeile

erforderlich, um das Objekt vor dem Verschwinden zu bewahren.

Im MouseDown-Event wird der Mouse-Offset festgehalten und das Objekt als angeklickt markiert:

 

Im MouseMove-Event wird jede Mausveränderung sofort umgesetzt und das Objekt neu angezeigt:

 

Im MouseUp-Event wird schließlich die Farbmarkierung wieder entfernt:

 

Das ist alles, was wir für unser eigenes Drag&Drop benötigen.

Das Verschieben von Daten gestaltet sich relativ einfach, da im DragDrop-Event des Ziel-Objektes über den Parameter oSource jederzeit auf die Value-Eigenschaft des Quell-Objektes zurückgegriffen werden kann:

 

Während ein Steuerelement gezogen wird, erkennt es keine anderen vom Benutzer eingeleiteten Maus- oder Tastaturereignisse (KeyPress, MouseDown, MouseMove oder MouseUp) >> DragMode-Eigenschaft.

Die Drag&Drop - Eigenschaften beziehen sich teilweise nicht auf das Drag-Objekt, sondern auf das Zielobjekt. So stellt sich bei der DragDrop-Methode nicht die Frage "Was geschieht mir mir, wenn ich auf dem Objekt XY fallengelassen werde?" sondern "Was geschieht, wenn ein Objekt über mir fallengelassen wird?." Gleiches gilt für die DragOver-Eigenschaft. Es heißt auch hier nicht "Was tun, wenn ich über ein Objekt gezogen werde?", sondern "Was tun, wenn ein Objekt über mich gezogen wird?".

Das bedeutet, daß es nicht möglich ist, sich eine Klasse für beispielsweise ein dragbares Text-Objekt zu erstellen, das bestimmte Aktionen ausführt, wird es irgendwohin gezogen, sondern, daß es zu jedem dragbaren Objekt stets eine Vielzahl von "Gegenobjekten" geben kann und muß. Habe ich in einer Form beispielsweise zwei Objekte die auf die ich meine Textobjektklasse ziehen kann und eines auf dem ich nicht abladen darf, so brauche ich grundsätzlich drei "Gegenfunktionen". Auch wenn diese nur dazu dienen wiederum auf eine klasseneigene Verarbeitungsfunktion zu referenzieren

Es ist sehr einfach möglich, Objekte mit Hilfe des DragDrop auf einer Form zu verschieben. Dazu wird lediglich die DragMode - Einstellung auf 1 = Automatic gestellt und in der DragDrop-Methode ein Move-Event mit dem entsprechenden Objekt gemacht. Es gibt jedoch folgende Nachteile:

Das Objekt wird nicht so positioniert wie der Rahmen es anzeigt, sondern mit der oberen linken Ecke auf die Spitze des mauszeigers. Bei der Einstellung DragMode = Automatic, kann jedoch kein MouseDown - Ereignis festgehalten werden, so daß es nicht möglich ist, den Offset des Mouse-Zeigers festzuhalten, um ihn später wieder herzustellen.

Geringe Verschiebungen sind nicht möglich so lange der Drag-Rahmen sich noch im Bereich des Drag-Objektes befindet. Es wird dann kein "Gezogen" erkannt.

Der Click-Event wird ignoriert, so daß das Objekt nur noch per Tastatur oder über eine eigene Konstruktion zu erreichen ist (beispielsweise im DragDrop-Event abbrüfen, ob Drag-Objekt und DropObjekt identisch sind, wenn das so ist, dann die Click-Methode ausführen)

Wird ein manuelles Drag&Drop über den MouseDown-Event eingeleitet kann es nicht über einen MouseUp-Event beendet werden, da wärend des Drag-Vorganges Mausbewegungen und -klicks nicht erkannt werden. Ein MouseUp-Event wird wird erst dann erkannt, wenn der Drag-Modus bereits beendet ist.

 

(a) Container-Objekte sind nur am Rand greifbar. (b) Hat man eine aus mehreren Objekten zusammengesetzte Klasse müßte man eigenlich für jedes Teilobjekt eigenen Drag-Drop-Routinen erstellen. >> HAUS-Klasse. Daher ist es sinnvoll ein transparentes Shape ohne Rahmen oben drüber zu legen und in diesem die gesamte Verarbeitung des Drag&Drop vorzunehmen. Will man die Drag&Drop-Funktionalität dann programmatisch abschalten, braucht lediglich die Visible-eigenschaft dieses Shape auf .F. gesetzt zu werden und die Mausklicks werden nicht mehr erkannt.

Ein transparentes Shape eignet sich auch ausgezeichnet zur Generierung zur Laufzeit veränderbarer Sensibilitäts- und Fangbereiche. Durch die Veränderung der Größe des entsprechenden Shapes wird die Empfindlichkeit getunt.