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 16.02.2001

Diese Seite wurde zuletzt aktualisiert am 16.02.2001
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...

Double-Trouble ade

Zurück...


Anzeige

(-hg) mailto:hg_singleclick@aboutvb.de

Vor allem Einsteiger in die Bedienung von Windows und Windows-Anwendungen haben oftmals noch Schwierigkeiten den einfachen Mausklick und den Doppelklick auseinander zu halten. Zudem gibt es Mäuse und Maustreiber, die es erlauben, den Doppelklick auf eine (dritte) Maustaste oder bei Rad-Mäusen auf das Rädchen zu legen. Wie schnell wird da mal versehentlich ein Doppelklick auf eine Schaltfläche abgesetzt...

Solche versehentlichen Doppelklicks können Sie jedoch verhindern. Ein wenig Aufwand ist zwar nötig - er hält sich jedoch in Grenzen, wenn Sie die hierfür notwendige Funktionalität nur genügend abstrakt und allgemein verwendbar implementieren.

Der erste, und schon mal richtige, Gedanke ist, die vom Anwender eingestellte (oder unverändert belassene) Doppelklickzeit zu berücksichtigen. Diese Zeitspanne, innerhalb derer Windows einen Mausklick als Doppelklick interpretiert, können Sie über die API-Funktion GetDoubleClickTime erhalten, in Millisekunden. Sie halten den Zeitpunkt eines Mausklicks fest, und wenn der nächste Klick innerhalb dieser Zeitspanne erfolgen sollte, wird er als zweiter Klick eines Doppelklickpaares verworfen. Den Zeitpunkt eines Klicks erhalten Sie über die API-Funktion GetTickCount, praktischerweise ebenfalls in Millisekunden. Zwischen den Klick-Ereignissen wird der Zeitpunkt in einer statischen Variablen festgehalten.

Private Declare Function GetDoubleClickTime Lib "user32" () As Long
Private Declare Function GetTickCount Lib "kernel32" () As Long

Private Sub Command1_Click()
  Dim nAction As Boolean
  Dim nThisTime As Long
  
  Static sPrevTime As Variant

  nThisTime = GetTickCount()
  If sPrevTime + GetDoubleClickTime() < nThisTime Then
    nAction = True
  End If
  sPrevTime = nThisTime
  If Not nAction Then
    Exit Sub
  End If
  
  ' eigentliche Aktion:
  Debug.Print "Click"
End Sub

Das sieht schon recht gut. Und wenn Sie es ausprobieren, dürfte das auch wie gewünscht funktionieren - aber auch nur scheinbar! Denn es wird zum einen nicht berücksichtigt, dass der von GetTickCount gelieferte Wert eigentlich ein vorzeichenloser Wert ist, der den normalen positiven Wertebereich eines Long-Wertes überschreiten kann. Der Aufruf der API-Funktion stört sich zwar nicht daran und gibt Ihnen brav einen Long-Wert zurück. Doch wenn der Zähler eigentlich auf einem Wert steht, der größer als &H7FFFFFFF (= 2.147.483.647) ist, erscheint der Wert in Visual Basic negativ - der Größenvergleich würde fehlschlagen. Zum anderen wird nicht berücksichtigt, dass etwa alle 49,7 Tage der Wert des internen Zeitzählers, der von GetTickCount zurückgegeben wird, überläuft und auf 0 zurück gesetzt wird. Der erste Klick danach würde nicht durchkommen, da der Zeitvergleich auch hier falsch ausgehen würde.

Das erstere Problem lösen wir, indem wir den Wert des Zeitzählers in einen vorzeichenlosen Wert umwandeln, der wegen seiner Größe in einer Variant-Variablen abgelegt und dazu in den Variant-Unterdatentyp Decimal konvertiert wird. Die Beschreibung und den Hintergrund der dazu verwendeten Funktion CLongToULong finden Sie in Vorzeichenlose Werte"Vorzeichenlose Werte".

Damit ist auch die Grundlage zur Lösung des zweiten Problems geschaffen, des Überlaufs. Zunächst müssen wir den Fall ausklammern, dass noch gar kein Zeitpunkt für einen vorhergehenden Klick zwischengespeichert wurde. Denn da wir nun für die Zeitwerte Variant-Variablen verwenden müssen, sind diese im uninitialisierten "Roh"-Zustand "Leer" (Empty), und nicht etwa 0, wie eine frisch dimensionierte Long-Variable.

Für den Fall des Überlaufs prüfen wir, ob der aktuelle Zeitwert kleiner ist, als der vorhergehende. Ist das der Fall, setzt sich die Zeitspanne, die wir mit dem Wert von GetDoubleClickTime vergleichen müssen, aus dem aktuellen Zeitwert und der Differenz zwischen letztem Zeitwert und dem höchstmöglichen Zeitwert (&HFFFFFFFF - vorzeichenlos!) zusammen. Anderenfalls ist die Vergleichsspanne die normale Differenz zwischen aktuellem Zeitwert und vorhergehendem Zeitwert.

Private Declare Function GetDoubleClickTime Lib "user32" () As Long
Private Declare Function GetTickCount Lib "kernel32" () As Long

Private Sub Command1_Click()
  Dim nAction As Boolean
  Dim nThisTime As Variant
  Dim nDiffTime As Long
  
  Static sPrevTime As Variant
  
  nThisTime = CLongToULong(GetTickCount())
  If sPrevTime = Empty Then
    nAction = True
  Else
    If sPrevTime <= nThisTime Then
      nDiffTime = nThisTime - sPrevTime
    Else
      nDiffTime = CLongToULong(&HFFFFFFFF) - sPrevTime + nThisTime
    End If
  End If
  If nDiffTime > GetDoubleClickTime() Then
    nAction = True
  End If
  sPrevTime = nThisTime
  If Not nAction Then
    Exit Sub
  End If

  ' eigentliche Aktion:
  Debug.Print "Click"
End Sub

Das funktioniert nun ganz prächtig und zuverlässig. Allerdings ist das eine mächtige Menge Code, die Sie in jede Click-Ereignisprozedur einer jeden Schaltfläche einzufügen hätten. Sie können den Code jedoch auch in eine Funktion in einem Standard-Modul auslagern. Nun brauchen Sie nur noch den vorhergehenden Zeitwert als statische Variable in der Click-Ereignisprozedur zu speichern und diese Funktion aufzurufen. Gibt die Funktion True zurück, soll das Click-Ereignis verwendet werden.

Private Declare Function GetDoubleClickTime Lib "user32" () As Long
Public Declare Function GetTickCount Lib "kernel32" () As Long

Public Function SingleClick(PrevTime As Variant) As Boolean
  Dim nAction As Boolean
  Dim nThisTime As Variant
  Dim nDiffTime As Long
  
  nThisTime = CLongToULong(GetTickCount())
  If PrevTime = Empty Then
    nAction = True
  Else
    If PrevTime <= nThisTime Then
      nDiffTime = nThisTime - PrevTime
    Else
      nDiffTime = CLongToULong(&HFFFFFFFF) - PrevTime + nThisTime
    End If
  End If
  If nDiffTime > GetDoubleClickTime() Then
    nAction = True
  End If
  PrevTime = nThisTime
  If Not nAction Then
    Exit Function
  End If
  SingleClick = True
End Function

Und der Code in einer Click-Ereignisprozedur reduziert sich auf folgendes:

Private Sub Command1_Click()
  Static sPrevTime As Variant
  
  If SingleClick(sPrevTime) Then
    ' eigentliche Aktion:
    Debug.Print "Click"  End If
End Sub

Ist Ihnen das immer noch zu viel? Dann hilft nur noch eine Klasse, die Sie für jede Schaltfläche instanzieren müssen. Eine Klasse bietet dabei einen etwas eleganteren Weg. Sie können nämlich auf das Click-Ereignis der Schaltfläche verzichten und die Klasse ein entsprechendes Click-Ereignis auslösen lassen, das sie dann ohne weiteren Code darin wie ein gewöhnliches Click-Ereignis verwenden können. Und falls Sie schon immer das tatsächliche Doppelklick-Ereignis einer Schaltfläche vermisst haben sollten: Die Klasse bietet nun auch dieses.

Die dafür notwendige Erweiterung des obenstehenden Codes ist gar nicht aufwändig - sie ist sogar schon eingebaut. Denn statt den unerwünschten Doppelklick zu verwerfen, lösen Sie einfach ein DblClick-Ereignis aus.

Der vollständige Code der Klasse (hier sehen Sie auch noch einmal die Konvertierfunktion für vorzeichenlose Werte CLongToULong):

Private Declare Function GetDoubleClickTime Lib "user32" () As Long
Private Declare Function GetTickCount Lib "kernel32" () As Long

Public Event Click()
Public Event DblClick()

Private mPrevTime As Variant
Private mDblClick As Boolean

Private WithEvents eCommand As CommandButton

Public Sub Init(CommandButton As CommandButton)
  Set eCommand = CommandButton
End Sub

Private Sub Class_Initialize()
  mPrevTime = 0
End Sub

Private Sub eCommand_Click()
  Dim nAction As Boolean
  Dim nThisTime As Variant
  Dim nDiffTime As Long
  
  nThisTime = CLongToULong(GetTickCount())
  If mPrevTime <= nThisTime Then
    nDiffTime = nThisTime - mPrevTime
  Else
    nDiffTime = CLongToULong(&HFFFFFFFF) - mPrevTime + nThisTime
  End If
  If (nDiffTime > GetDoubleClickTime()) Or mDblClick Then
    nAction = True
  Else
    mDblClick = True
  End If
  mPrevTime = nThisTime
  Select Case True
    Case nAction
      mDblClick = False
      RaiseEvent Click
    Case mDblClick
      RaiseEvent DblClick
  End Select
End Sub

Private Function CLongToULong(LongValue As Long) As Variant
  Select Case LongValue
    Case Is < 0
      CLongToULong = CDec(LongValue - CDec(&H80000000) * 2)
    Case &H80000000
      CLongToULong = CDec(LongValue) * -1
    Case Else
      CLongToULong = CDec(LongValue)
  End Select
End Function

Sie legen nun für jede Schaltfläche, die Sie mit der Funktionalität dieser Klasse nachrüsten möchten, eine Ereignisempfänger-Variable in dem Modul an, wo die Ereignisse verarbeitet werden sollen:

Private WithEvents mCommand1SingleClick As clsSingleClick

Die Instanz erzeugen Sie beispielsweise im Form_Load-Ereignis und übergeben der Klasse die betreffende Schaltfläche über den Aufruf der Init-Methode:

Private Sub Form_Load()
  Set mCommand1SingleClick = New clsSingleClick
  mCommand1SingleClick.Init Command1
End Sub

Nun haben Sie beide Ereignisse, Click und DblClick, separat zur Verfügung:

Private Sub mCommand1SingleClick_Click()
  Debug.Print "Click"
End Sub

Private Sub mCommand1SingleClick_DblClick()
  Debug.Print "DblClick"
End Sub

Beispiel-Projekt, Modul und Klasse (singleclick.zip - ca. 4,3 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