ABOUT Visual Basic Programmieren Programmierung Download Downloads Tips & Tricks Tipps & Tricks Know-How Praxis VB VBA Visual Basic for Applications VBS VBScript Scripting Windows ActiveX COM OLE API ComputerPC Microsoft Office Microsoft Office 97 Office 2000 Access Word Winword Excel Outlook Addins ASP Active Server Pages COMAddIns ActiveX-Controls OCX UserControl UserDocument Komponenten DLL EXE
Diese Seite wurde zuletzt aktualisiert am 26.10.2000

Diese Seite wurde zuletzt aktualisiert am 26.10.2000
Aktuell im ABOUT Visual Basic-MagazinGrundlagenwissen und TechnologienKnow How, Tipps und Tricks rund um Visual BasicActiveX-Komponenten, Controls, Klassen und mehr...AddIns für die Visual Basic-IDE und die VBA-IDEVBA-Programmierung in MS-Office und anderen AnwendungenScripting-Praxis für den Windows Scripting Host und das Scripting-ControlTools, Komponenten und Dienstleistungen des MarktesRessourcen für Programmierer (Bücher, Job-Börse)Dies&Das...

Themen und Stichwörter im ABOUT Visual Basic-Magazin
Code, Beispiele, Komponenten, Tools im Überblick, Shareware, Freeware
Ihre Service-Seite, Termine, Job-Börse
Melden Sie sich an, um in den vollen Genuss des ABOUT Visual Basic-Magazins zu kommen!
Informationen zum ABOUT Visual Basic-Magazin, Kontakt und Impressum

Zurück...

ListBox und OLEDrag&Drop

Zurück...


Anzeige

(-hg) mailto:hg_listitemoledrag@aboutvb.de

Per OLEDrag stellt das Verschieben oder Kopieren von Elementen einer ListBox keine besonders schwierige Aufgabe dar. Sie brauchen bloß festzustellen, über welchem Element sich der Mauszeiger im OLEDragDrop-Ereignis gerade befindet und das gezogene Element an die entsprechende ListIndex-Position zu verschieben oder zu kopieren. In der Praxis ist das jedoch nicht ganz so einfach zu bewerkstelligen, wie sich das liest.

Wenn Sie dem Anwender sowohl das Verschieben als auch das Kopieren eines Elements anbieten möchten, müssen Sie zum einen unterscheiden können, welche von beiden Aktionen der Anwender gerade ausführen will. Und zum anderen sollten Sie ihm das entsprechende Feedback über den passenden Mauszeiger geben. Außerdem sollte der ListBox-Inhalt automatisch rollen, wenn er mehr Elemente als sichtbar umfasst. Und wäre es dazu nicht ein netter Zug von Ihnen, wenn Sie dem Anwender auch noch die Einfüge-Position signalisieren würden, so wie es in manchen Dialogen und in der untenstehenden Abbildung zu sehen ist? Das klingt nun nach mächtig mehr Aufwand, nicht wahr? Keine Bange - ganz so schlimm ist es doch nicht.


OLEDrag&Drop eines Elements in einer ListBox mit Anzeige der Einfüge-Position

Die Grundzüge des OLE-Drag&Drops bei Steuerelementen ab Visual Basic 5 kennen Sie sicher: Die Ereignisfolge beginnt mit dem Ereignis MSDN Library - VB OLEStartDragOLEStartDrag, gefolgt

von MSDN Library - VB OLEDragOverOLEDragOver-Ereignissen. Beim Ablegen wird das MSDN Library - VB OLEDragDropOLEDragDrop-Ereignis ausgelöst, wiederum gefolgt von MSDN Library - VB OLECompleteDragOLECompleteDrag. Die beiden weiteren Ereignisse OLEGiveFeedback und OLESetData brauchen wir zum Ziehen von Elementen innerhalb einer ListBox nicht. Außerdem beschränken wir uns hier auf das Ziehen innerhalb ein und derselben ListBox, so dass auch das kompliziert erscheinende Hin und Her der Ereignisfolge zwischen den OLE-Ereignissen der betroffenen Steuerelemente entfällt.

Damit Sie den notwendigen Code nicht in die Ereignisprozeduren jeder ListBox erneut einfügen müssen lohnt es sich, diesen in entsprechende Prozeduren in ein Standard-Modul auszulagern. Die vier Prozeduren für die vier genannten Ereignisse heißen ListItemOLEStartDrag, ListItemOLEDragOver, ListItemOLEDragDrop und ListItemOLECompleteDrag, die Sie einfach von den entsprechenden Ereignisprozeduren Ihrer ListBox(en) aus aufrufen. Schauen wir uns diese Prozeduren einmal der Reihe nach an.

Public Sub ListItemOLEStartDrag(ListBox As ListBox, _
 Data As DataObject, _
 Optional ByVal Clear As Boolean = True, _
 Optional ByVal DataFormat As Integer = 7777, _
 Optional AllowedEffects As Long)

  ListBox.AddItem ""

Sie wundern sich, warum wir hier gleich als erstes ein leeres Element an die bereits vorhandenen Elemente der ListBox anhängen? Angenommen, ein verschobenes oder kopiertes Element soll immer vor einem anderen Element eingefügt werden. Überlegen Sie nur einmal, wie das gezogene Element auch hinter dem allerletzten Element eingefügt werden könnte, ohne den Anwender mit einem komplizierten Mechanismus zu verwirren. Mir ist keine praktikable Lösung eingefallen - außer der einen, einfach ein leeres Element anzuhängen, vor dem das gezogene Element eingefügt werden kann. Daraus ergibt sich auch die Begründung für die Annahme (tatsächlich ist das die von mir vorgegebene Regel), immer vor einem anderen Element einzufügen. Würde die Regel entgegengesetzt lauten, könnte der Anwender zunächst kein Element vor das erste in der Liste ziehen. Würden Sie das leere Hilfselement nun am Anfang der Liste einfügen, würde das den Anwender ziemlich irritieren. Falls Sie spontan auf die Idee gekommen sein sollten, einfach den freien Raum nach dem letzten Element als Maßgabe zum Einfügen als neues letztes Element zu nehmen: Diesen freien Raum gibt es nur, wenn die Anzahl der in der ListBox enthaltenen Elemente klein genug ist. Anderenfalls sorgt die ListBox von selbst beim Rollen dafür, dass kein freier Raum verbleibt.

Ach ja, ehe ich es zu erwähnen vergesse: Wir benutzen den angebotenen automatischen OLEDrag-Modus (Eigenschaft MSDN Library - VB OLEDragModeOLEDragMode = 1 - Automatisch) und sparen uns damit den Code zur Verzögerung des Ziehvorgangs (der ja erst ab einer kleinen Bewegung des Mauszeigers nach der Auswahl des zu ziehenden Elements beginnen sollte) und dergleichen mehr. Diese Automatik sorgt dafür, dass das Data-Objekt bereits mit dem Text des gezogenen Elements und dem einfachen Text-Format (vbCFText) vorbelegt ist. Schließlich handelt es sich bei einem ListBox-Element ja um puren Text. Die Eigenschaft MSDN Library - VB OLEDropModeOLEDropMode setzen wir auf 1 - Manuell.

Dies hat aber zur Folge, dass Sie die Kontrolle darüber verlieren, wo der Text des Elements letztlich eingefügt wird. Er kann nämlich sogar in irgendeinem Steuerelement irgendeiner anderen Anwendung landen, falls dieser per OLE gezogene Textschnipsel als einfügbaren Inhalt akzeptiert (etwa in einem Editor, in einem Word-Dokument, in der Adress-Zeile Ihres Browsers, oder wo auch immer). Und je nach dort herrschender Konvention und Programmierung kann die Anforderung von dort auch auf ein Verschieben hinauslaufen. Das Grundprinzip der einfachsten Form des OLE-Drag&Drop, das bei der Automatik zunächst vorgegeben ist, bedeutet, dass das Ziel die Kontrolle hat und dass es der Quelle nicht bekannt ist.

Falls Sie also verhindern wollen, dass Ihre ListBox-Elemente sonstwo landen, belassen Sie den optionalen Parameter Clear der Prozedur ListBoxOLEStartDrag in der Voreinstellung True, damit das Data-Objekt zurückgesetzt wird.

  With Data
    If Clear Then
      .Clear
    End If

Ohne eine Vorbelegung des Data-Objekts würde der Ziehvorgang jedoch gar nicht erst weitergeführt. Damit jedoch Ihre ListBox in ihrem OLEDragOver-Ereignis weiß, wann eines ihrer Elemente über sie gezogen wird, definieren Sie ein eigenes Data-Format und übergeben es dem Data-Objekt (siehe auch OLE-Drag&Drop mit Parole"OLE-Drag&Drop mit Parole"). Sie können die im optionalen Parameter DataFormat voreingestellte Kennung "7777" für Ihre erste bzw. einzige ListBox übernehmen (der Wert selbst ist im Prinzip ohne Belang). Bei mehreren gleichzeitig für den Anwender erreichbaren ListBoxen sollten Sie jedoch für jede ein eigenes DataFormat wählen.

    .SetData , DataFormat
  End With

Den optional Parameter AllowedEffects brauchen Sie nicht zu übergeben, wenn der Anwender sowohl kopieren als auch verschieben können soll. Wenn Sie das einschränken möchten, lassen Sie den Parameter beim Aufruf der Prozedur weg und weisen Sie den Konstantwert für den einen oder den anderen Modus danach separat in der Ereignisprozedur dem Ereignisparameter AllowedEffects zu.

  AllowedEffects = vbDropEffectMove Or vbDropEffectCopy
End Sub

Der Ziehvorgang ist nunmehr gestartet. Jetzt sollte das OLEDragOver-Ereignis Ihrer ListBox bei jeder Mauszeigerbewegung feuern. Auch dieses Ereignis reichen Sie an eine Hilfsprozedur weiter, an ListItemOLEDragOver. Ihr übergeben Sie zunächst die betreffende ListBox, das Data-Objekt, den Rückgabeparameter Effect und den Button-Status. Wenn Die Unterscheidung zwischen Verschieben und Kopieren über den Shift-Status (Umschalt-Taste, Strg-Taste - letztere wird üblicherweise beim Kopieren zusammen mit dem Mausknopf gedrückt) erfolgt, müssen Sie den im Shift-Parameter des Ereignisses gelieferten Wert übergeben. Wenn Sie im Ereignis OLEStartDrag als AllowedEffect nur Kopieren zugelassen haben, übergeben Sie den Konstantwert vbCtrlMask. Wenn Sie nur Verschieben zugelassen haben, können Sie den Parameter auslassen (oder 0 übergeben). Zum Schluss übergeben Sie noch das im OLEStartDrag-Ereignis festgelegte Data-Format, oder Sie belassen es auch hier beim voreingestellten Format "7777".

Public Sub ListItemOLEDragOver(ListBox As ListBox, _
 Data As DataObject, Effect As Long, _
 Optional ByVal Button As Integer, _
 Optional ByVal Shift As Integer, _
 Optional ByVal DataFormat As Integer = 7777)

  Dim nPoint As POINTAPI
  Dim nItem As Long
  Dim nEffect As Long
  
  With ListBox
    If Data.GetFormat(DataFormat) Then

Als erstes wird geprüft, ob das Data-Objekt mit dem eigenen DataFormat bestückt ist. Wurde der Ziehvorgang an anderer Stelle (etwa in einer "fremden" ListBox, oder gar im Windows-Explorer) gestartet, wird das DataFormat nicht vorgefunden - ein Ablegen soll also nicht erlaubt sein. Wenn der Anwender dennoch andere Inhalte ablegen können soll, können Sie prüfen, ob der Effect-Parameter nach dem Aufruf von ListItemOLEDragOver bereits gesetzt ist. Ist er nicht gesetzt, können Sie die Ereignisprozedur wie gewohnt weiter bearbeiten.

      nItem = zItemFromPoint(.hWnd, True)

Die private Hilfsfunktion zItemFromPoint werden Sie noch nicht kennen. Ich werde sie weiter unten vorstellen. Belassen wir es vorläufig dabei, dass sie anhand des Fenster-Handles einer ListBox feststellen kann, ob sich der Mauszeiger über einem Element der ListBox befindet. Gegegebenenfalls gibt sie dessen ListIndex zurück. Außerdem kann sie (das besagt der zweite Parameter, wenn er auf True gesetzt ist) die ListBox bei jedem Aufruf automatisch um eine Zeile nach oben oder unten rollen (wenn sich noch Elemente in der betreffenden Richtung befinden sollten), wenn der Mauszeiger genau auf den oberen oder unteren Rand trifft. Befindet sich der Mauszeiger außerhalb der ListBox oder im freien Raum unterhalb des letzten Elements, gibt die Funktion -1 zurück - der ListIndex ist unbestimmt.

      If nItem >= 0 Then
        Select Case nItem
          Case .ListIndex, .ListIndex + 1

Konnte für ein Element unter dem Mauszeiger ein ListIndex ermittelt werden, prüfen wir, ob dieser mit dem aktuellen ListIndex der ListBox oder dem nächstfolgenden übereinstimmt.

              If Shift = vbCtrlMask Then
                nEffect = vbDropEffectCopy
              End If

Ist dies der Fall, und ist zugleich die Strg-Taste für den Kopier-Modus niedergedrückt, kann als erlaubter Effekt der Konstantwert vbDropEffectCopy zurückgegeben werden. Ein Verschieben zuzulassen würde an dieser Position nicht viel Sinn machen, weil es eben sinnloserweise das Verschieben des Elements an seine aktuelle Position oder vor seinen Nachfolger bedeuten würde.

            Case Else
              Select Case Shift
                Case 0
                  nEffect = vbDropEffectMove
                Case vbCtrlMask
                  nEffect = vbDropEffectCopy
              End Select
          End Select

Ist der ListIndex ein anderer, prüfen wir, ob keine Shift-Taste niedergedrückt ist - dann soll verschoben werden. Ist die Strg-Taste niedergedrückt, soll kopiert werden.

      End If
      nEffect = nEffect And Effect

Um diese Prozedur unabhängig von Ihren über den Parameter AllowedEffects im OLEStartDrag-Ereignis gesetzten Vorgaben zu halten, wird nun noch geprüft, ob der ermittelte Effekt in der vom Effects-Parameter übermittelten Maske enthalten ist.

    End If
    If nEffect Then
      DrawInsert .Parent.hwnd, .hwnd, nItem
    Else
      DrawInsert .Parent.hwnd, .hwnd, -1
    End If

Ist dies der Fall, wird über die API-Funktion MSDN Library - API DrawInsertDrawInsert (sie stammt aus der Common-Controls-DLL) die Einfügemarke für den ermittelten ListIndex dargestellt (Beachten Sie, dass diese auf den Form-Hintergrund gezeichnet wird - Sie müssen entsprechend Platz links von der ListBox vorsehen!). Ist das nicht der Fall oder ist irgendeine der vorangegangenen Prüfungen ergebnislos gewesen und enthält die lokale Variable nEffects nach wie vor den Wert 0, wird die Einfügemarke gelöscht.

  End With
  Effect = nEffect

Hier wird noch der ermittelte Effekt zurückgegeben. Abgesehen davon, dass je nach zurückgegebenem Wert der entsprechende Mauszeiger angezeigt wird, zeigt auch nur dann das Ablegen auf der ListBox Wirkung (das OLEDragDrop-Ereignis wird dann auslgelöst), wenn ein Wert ungleich 0 (vbDropEffectNone) übergeben wird.

End Sub

Bevor ich auf die Bearbeitung des OLEDragDrop-Ereignisses eingehe, schiebe ich noch kurz die Beschreibung der zItemFromPoint-Funktion ein.

Private Function zItemFromPoint(ByVal hWnd As Long, _
 Optional ByVal AutoScroll As Boolean)

  Dim nPoint As POINTAPI

  GetCursorPos nPoint
  nItem = LBItemFromPt(hWnd, nPoint.X, nPoint.Y, AutoScroll)
End Function

Diese Funktion stellt eine Kapselung der API-Funktion MSDN Library - API LBItemFromPtLBItemFromPt dar (die ebenfalls aus der Common-Controls-DLL stammt). Anhand der absoluten Bildschirm-Koordinaten (die wir mit der API-Funktion MSDN Library - API GetCursorPosGetCursorPos ermitteln) prüft LBItemFromPt zum einen, ob sich die zu dem im Parameter hWnd gehörende ListBox an oberster Stelle unter den angegebenen Koordinaten befindet. Zum anderen stellt sie zugleich fest, ob sich ein Element der ListBox an dieser Position befindet und gibt gegebenenfalls dessen ListIndex zurück. Wie bereits erwähnt, sorgt sie auch für das automatische Rollen der ListBox, wenn sich der Mauszeiger über dem oberen oder unteren Rand der ListBox befinden sollte. Die Prüfung über die absoluten Bildschirm-Koordinaten ist deswegen sinnvoll, weil die ListBox ja teilweise verdeckt sein könnte und damit als Ablageziel an dieser Position ausfallen würde. Bei der sonst üblichen Methode zur Ermittlung eines Elements anhand der inneren (Client-)Koordinaten der ListBox alleine könnte das nicht sichergestellt werden.

Kommen wir nun zum Anfang vom Ende eines Ziehvorgangs, zum Ereignis OLEDragDrop und der Bearbeitungsprozedur ListItemOLEDragDrop. Ihr übergeben Sie wieder die betreffende ListBox, das Data-Objekt und optional den Shift-Status.

Public Sub ListItemOLEDragDrop(ListBox As ListBox, _
 Data As DataObject, _
 Optional ByVal Shift As Integer)

  Dim nItemText As String
  Dim nItemData As Long
  Dim nNewIndex As Long
  
  With ListBox
    LockWindowUpdate .Parent.hWnd

Damit das Einfügen des zu kopierenden Elements oder das Entfernen und Einfügen des zu verschiebenden Elements nicht ein sichtbares Flackern oder gar deutlich sichtbares Hin- und Herrollen der ListBox hervorruft, sperren wir vorübergehend die Aktualisierung des Elternfensters der ListBox (das Sperren der Aktualisierung der ListBox selbst könnte je nach Windows- bzw. Internet-Explorer-Version zu einem Flackern des Desktops führen) mit der API-Funktion MSDN Library - API LockWindowUpdateLockWindowUpdate.

    nItemText = .List(.ListIndex)
    nItemData = .ItemData(.ListIndex)
    .AddItem nItemText, zItemFromPoint(.hWnd)

Wir ermitteln den Text und den ItemData-Wert des aktuellen, zu verschiebenden bzw. zu kopierenden Elements und fügen es an der hier erneut über die Funktion zItemFromPoint ermittelte ListIndex-Position ein. Hier brauchen wir die Gültigkeit dieser Position nicht weiter zu prüfen, da dies bereits in dem letzten, unmittelbar zuvor ausgelösten OLEDragOver-Ereignis geschehen ist. Wäre die Prüfung dort gescheitert, wäre das OLEDragDrop-Ereignis erst gar nicht ausgelöst worden.

    nNewIndex = .NewIndex

Um jegliche Rechnerei zu sparen fragen wir den ListIndex des eingefügten Elements von der ListBox ab.

    .ItemData(nNewIndex) = nItemData

Wir weisen nun noch den zu diesem ListIndex gehörenden ItemData-Wert zu.

    If Shift = 0 Then
      .RemoveItem .ListIndex
    End If

Falls das Element verschoben werden sollte (weder Strg-Taste noch irgendeine andere Taste gedrückt), entfernen wir es aus der ListBox.

    .ListIndex = nNewIndex

Den aktuellen ListIndex setzen wir auf das neu eingefügte Element.

  End With
End Sub

Das endgültige Ende vom Ende des Ziehvorgangs wird mit dem Ereignis OLECompleteDrag signalisiert. Die Bearbeitungsprozedur dazu ist ListItemOLECompleteDrag. Ihr brauchen Sie nur noch die ListBox als Parameter zu übergeben.

Public Sub ListItemOLECompleteDrag(ListBox As ListBox)
  With ListBox
    DrawInsert .Parent.hwnd, .hwnd, -1

Da die Einfügemarke nicht von alleine verschwinden kann, müssen wir sie hier definitiv löschen.

    .RemoveItem .ListCount - 1

Hier entfernen wir auch noch das hilfsweise angehängte leere Element.

  End With
  LockWindowUpdate 0&

Und mit diesem Aufruf wird die Aktualisierungssperre des Forms aufgehoben und damit dafür gesorgt, dass die ListBox nun die neue Element-Reihenfolge tatsächlich anzeigt.

End Sub

Zu guter Letzt nun noch die API-Deklarationen:

Private Type POINTAPI
  X As Long
  Y As Long
End Type

Private Declare Sub DrawInsert Lib "COMCTL32.DLL" _
 (ByVal ParentWnd As Long, ByVal hwnd As Long, _
 ByVal Item As Long)
Private Declare Function GetCursorPos Lib "user32" _
 (lpPoint As POINTAPI) As Long
Private Declare Function LBItemFromPt Lib "COMCTL32.DLL" _
 (ByVal hwnd As Long, ByVal PtX As Long, ByVal PtY As Long, _
 ByVal AutoScroll As Long) As Long
Private Declare Function LockWindowUpdate Lib "user32" _
 (ByVal hwndLock As Long) As Long

Modul und Beispiel-Projekt ListItemDrag (listitemoledrag.zip - ca. 3,9 KB)


Artikel
Zum Download-Bereich dieses Artikel
Mail an den Autor dieses Artikels

KnowHow
Zur KnowHow-Übersicht

KnowHow-Themen
Themen - Allgemeines
Themen - Entwicklungsumgebung (VB-IDE)
Themen - Forms
Themen - Steuerelemente (Controls)
Themen - Grafik
Themen - Dateien
Themen - UserControls
Themen - Einsteiger-Tipps
Themen - Wussten Sie...?

Übersicht nach Titeln in alphabetischer Reihenfolge
Übersicht nach Erscheinungsdatum

Schnellsuche



Zum Seitenanfang

Copyright © 1999 - 2017 Harald M. Genauck, ip-pro gmbh  /  Impressum

Zum Seitenanfang

Zurück...

Zurück...

Download Internet Explorer