Frage zu Klickverhalten bei einem ownerdraw Button.

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Frage zu Klickverhalten bei einem ownerdraw Button.

Beitrag von Kurzer »

Aloha zusammen,

ich probiere gerade ein wenig mit der Thematik "ownerdrawn Buttons" herum.

Das klappt soweit ganz gut. Die wichtigsten Infos stehen in der API-Hilfe unter DRAWITEMSTRUCT, den Rest hab ich mir aus dem Forum zusammengesucht.
Mein Testcode stellt den Button einwandfrei dar, nur komischerweise reagiert er bei einem Doppelklick nicht so wie ein normaler Button.

Ein normaler Button wird bei einem schnellen Doppelklick zwei mal "eingedrückt", also jedes mal, wenn man den mousebutton drückt.
Bei dem ownerdraw Button ist das nicht so. Da wird der Button nur "eingedrückt", wenn man einfach klickt. Ein Doppelklick wird offenbar verschluckt.

Warum ist das so?

Hier mal mein Testcode:
Öffnet ein Fenster mit zwei Buttons. Links der ownerdraw Button, rechts ein normaler Windowsbutton.

Code: Alles auswählen

EnableExplicit

; Varaiablen für den Eventloop
Global iEvent.i, iEventType.i, iEventWindow.i, iEventwParam.i, iEventlParam.i, iEventGadget.i, iEventMenu.i
Global iIcon1.i, iIcon2.i

Procedure.i MyWindowCallback(iWindowID, iMessage, wParam, lParam)
	Protected *dis.DRAWITEMSTRUCT
	
	Select iMessage
	Case  #WM_DRAWITEM
    *dis.DRAWITEMSTRUCT = lparam

    If *dis\CtlType = #ODT_BUTTON
      Debug "Act:" + Str(*dis\itemAction)
      Debug "Sta:" + Str(*dis\itemState)
      
      Select *dis\itemState
        Case #ODS_FOCUS
		    	DrawFrameControl_(*dis\hDC, *dis\rcItem, #DFC_BUTTON, #DFCS_BUTTONPUSH | #DFCS_ADJUSTRECT)
			    DrawIcon_(*dis\hDC, 4, 4, iIcon1)

        Case #ODS_FOCUS | #ODS_SELECTED
		    	DrawFrameControl_(*dis\hDC, *dis\rcItem, #DFC_BUTTON, #DFCS_BUTTONPUSH | #DFCS_PUSHED | #DFCS_ADJUSTRECT)
			    DrawIcon_(*dis\hDC, 4, 4, iIcon2)

				Default
		    	DrawFrameControl_(*dis\hDC, *dis\rcItem, #DFC_BUTTON, #DFCS_BUTTONPUSH)
			    DrawIcon_(*dis\hDC, 4, 4, iIcon1)
      EndSelect
    EndIf
 	EndSelect

  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure



If OpenWindow(0, 300, 200, 195, 60, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)

	iIcon1 = LoadIcon_(0, #IDI_APPLICATION)
	iIcon2 = LoadIcon_(0, #IDI_QUESTION)

	SetWindowCallback(@MyWindowCallback())

	ButtonGadget(0, 10, 10, 40, 40, "Test", #BS_OWNERDRAW)
	ButtonGadget(1, 80, 10, 40, 40, "Test2")

	Repeat
	  iEvent       = WaitWindowEvent(25)
	  iEventType   = EventType()
	  iEventWindow = EventWindow()
	  iEventwParam = EventwParam()
	  iEventlParam = EventlParam()
	  iEventGadget = EventGadget()
	  iEventMenu   = EventMenu()
	    
	Until iEvent = #PB_Event_CloseWindow

	CloseWindow(0)
EndIf
Gruß Markus
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.02 x64, OS: Win 7 Pro x64 & Win 11 x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Useralter in 2024: 56 Jahre.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Re: Frage zu Klickverhalten bei einem ownerdraw Button.

Beitrag von Kaeru Gaman »

ich kann jetzt mal raten:
DBLCLICK ist eine komplett andere MSG als CLICK.
damit die nicht erzeugt werden kann, muss das Fensterelement als solches klassifiziert werden,
also als ein Element das kein DBLCLICK verarbeiten kann.
sonst ist ein DBLCLICK ein DBLCLICK, und darauf reagiert es dann nur mit der auf DBLCLICK spezifizierten Reaktion,
und wenn die nicht spezifiziert ist, ist es keine Reaktion.
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Benutzeravatar
Fluid Byte
Beiträge: 3110
Registriert: 27.09.2006 22:06
Wohnort: Berlin, Mitte

Re: Frage zu Klickverhalten bei einem ownerdraw Button.

Beitrag von Fluid Byte »

Ein uraltes Problem wovon selbst Software wie Winamp viele Jahre betroffen war. Da geht Kaerus Post schon in die richtige Richtung. Das Problem hängt tatsächlich mit der Verarbeitung von #WM_LBUTTONDBLCLK und #WM_LBUTTONDOWN zusammen.

Um das Problem zu verdeutlichen: Lass dein Beispiel laufen und während du deinen OD-Button drückst bewege die Maus hin und her. Du wirst sehen der Button reagiert dann genauso schnell wie ein regulärer Button. Wenn du die Maus bewegst wird aufgrund ihrer neuen Position jedes mal nur ein #WM_LBUTTONDOWN ausgelöst.

Ums kurz zumachen, du musst unterbinden das die Nachricht #WM_LBUTTONDBLCLK überhaupt gesendet wird. Dazu musst du die Klasse des Fenster entsprechen modifizieren. Füge folgende Zeile nach Erstellung deines Buttons hinzu:

Code: Alles auswählen

SetClassLongPtr_(GadgetID(0),#GCL_STYLE,GetClassLongPtr_(GadgetID(0),#GCL_STYLE) &~ #CS_DBLCLKS)
Windows 10 Pro, 64-Bit / Outtakes | Derek
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Frage zu Klickverhalten bei einem ownerdraw Button.

Beitrag von Kurzer »

Alles klar, ich denke ich hab's vollständig verstanden. :)

Der Button (seines Zeichens auch nur ein "Window"-control) hat seine eigene Struktur vom Typ WNDCLASS, über die sein Aussehen, Verhalten usw. verwaltet wird.

Code: Alles auswählen

typedef struct _WNDCLASS {    // wc  
    UINT    style; 
    WNDPROC lpfnWndProc; 
    int     cbClsExtra; 
    int     cbWndExtra; 
    HANDLE  hInstance; 
    HICON   hIcon; 
    HCURSOR hCursor; 
    HBRUSH  hbrBackground; 
    LPCTSTR lpszMenuName; 
    LPCTSTR lpszClassName; 
} WNDCLASS;
Besten Dank für Eure fixen Erklärungen. :allright:

PS: Hmm, die Struktur hat ja nen eigenen WNDPROC pointer. :?
Evtl. muß ich gar nicht nen Callback für das ganze Main-Fenster einrichten, sondern nur für den Button?
Ich probiere das mal aus.

Edit: Nö, geht nicht, da werden dann gar keine Buttons mehr dargestellt.
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.02 x64, OS: Win 7 Pro x64 & Win 11 x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Useralter in 2024: 56 Jahre.
Benutzeravatar
Fluid Byte
Beiträge: 3110
Registriert: 27.09.2006 22:06
Wohnort: Berlin, Mitte

Re: Frage zu Klickverhalten bei einem ownerdraw Button.

Beitrag von Fluid Byte »

Kurzer hat geschrieben: Evtl. muß ich gar nicht nen Callback für das ganze Main-Fenster einrichten, sondern nur für den Button?
Ich probiere das mal aus.
Ich hab' dein Edit gesehen aber zur Sicherheit kann ich dir sagen das #WM_DRAWITEM immer an das Elternfenster gesendet wird. Somit brauchst du das Callback im Hauptfenster. Die #WM_DRAWITEM Nachricht kann nicht vom Button selber berabeitet werden.
Windows 10 Pro, 64-Bit / Outtakes | Derek
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Frage zu Klickverhalten bei einem ownerdraw Button.

Beitrag von Kurzer »

Ich brauche noch mal Eure Hilfe, sonst krieg ich hier nen Koller.

Was ich eigentlich erreichen möchte:

- Einen Button, dessen Aussehen ich mit zwei Images (mit Transparenz) bestimmen kann (gedrückter und ungedrückter Zustand)

- Die Images des Buttons sollen den Untergrund nicht zerstören- auch wenn die Images für gedrückt und ungedrückt verschiedene Formen haben (kann man sicher durch Zwischenspeicherung des Untergrunds des Buttons erreichen)

- Der Button soll nur eine CLICK Message erzeugen, wenn die Maus in einem nicht-transparenten Bereich des Images geklickt wurde. Auch soll der Button nur als gedrückt gezeichnet werden, wenn die Maus im nicht-transparenten Bereich geklickt wurde.
Stellt Euch einfach einen kreisförmigen Button vor. Als "gedrückt" gilt er nur, wenn innerhalb des Kreises geklickt wurde.

- Er soll bei dauerhaft gedrückter Maustatse so reagieren, wie ein "normaler" Button. Also sobald ich mit der Maus aus dem Kreis herausfahre, soll er wieder nichtgedrückt erscheinen, und wenn ich wieder hereinfahre, dann soll er wieder als gedrückt erscheinen. Eine CLICK-Message soll erst nach Loslassen des Mausbuttons generiert werden (und auch nur dann, wenn sich der Mauszeiger dabei im nicht-transparenten Bereich befindet).

- Der Button soll keine Doppelklicks auswerten.


Nun, daß das ganze solch ein Fiasko wird, hatte ich nicht erwartet. :shock: Schon blöd, wenn man die internen Windowszusammenhänge nicht gut kennt.

Ich habe nun mehrere Ansätze durchprobiert, bin aber zu keiner zufriedenstellenden Lösung gekommen.
Vielleicht hab ich eine völlig falsche Herangehensweise - keine Ahnung. Ich hoffe jetzt auf die Erfahrung der Windows-Cracks unter euch, um die richtigen Denkanstöße zu bekommen.

Mein bisheriger Stand ist folgender:

- Es wird ein PB-Buttongadget mit der Option #BS_OWNERDRAW genutzt.
- Die Imagehandles werden mittels SetProp_()/GetProp_() an den Eigenschaften des jeweiligen Gadgets zwischengespeichert, und dadurch für den WindowCallBack verfügbar gemacht (also keine globalen Variablen).
- Ein "ControlCallBack" pro Gadget ist ebenfalls nötig, um die mittels SetProp_() gesetzen Eigenschaften beim destroyen des Gadegts wieder zu entfernen.
- Im Fenstercallback/#WM_DRAW - Abschnitt wird die aktuelle Mausposition innerhalb des Gadgets berechnet und mit diesen Koordinaten der Tranzparentwert des darunter liegenden Pixels ermittelt (AlphaValue).

Wo ich gerade dran scheitere ist, daß der Button nicht gedrückt sein darf, wenn die Maus in einen transparenten Bereich klickt.
Da ich einen normalen Button als Gadget benutze, gilt er leider schon als gedrückt, wenn die Maus irgendwo innerhalb des Gadget-Rects klickt. Also müsste ich an dieser Stelle verhindern, daß -welche Prozedure auch immer dafür verantwortlich ist- in diesem Fall kein CLICK-Event erzeugt wird.

Das kommt mir ehrlich gesagt alles ziemlich gewürgt und gekrampft vor.
Hat jemand von Euch schon einmal ein völlig eigenes Customgadget unter Windows programmiert? Das muß doch geschmeidiger gehen?

Wenn ich daran denke, daß ich außer diesem popeligen CustomButtonGadget noch ein viel komplexeres Gadget benötige zur Darstellung einer Waveform inkl. Scroll- und Zoombuttons, wird mir ganz anders (da komme ich in Zukunft bestimmt nochmal auf Euch zurück).

Hier ist der Code den ich bisher erstellt habe:

Code: Alles auswählen

EnableExplicit

Procedure.i ButtonImageCustomCallback(iWindowID, iMessage, wParam, lParam)
  ; +-----------------------------------------------------------------
  ; |Description  : CallbackProzedur des Imagebuttons
  ; |Arguments    :
  ; |Result       :
  ; +-----------------------------------------------------------------
  Protected iOldWindowCallback.i 	= GetProp_(iWindowID, "bic_OldCallback")
	
	Select iMessage
  Case #WM_NCDESTROY
    RemoveProp_(iWindowID, "bic_OldCallback")
    RemoveProp_(iWindowID, "bic_Image1")
    RemoveProp_(iWindowID, "bic_Image2")
    RemoveProp_(iWindowID, "bic_CurrentImage")
    RemoveProp_(iWindowID, "bic_ParentWindow")
    Debug "Destroy"
 	EndSelect

  ProcedureReturn CallWindowProc_(iOldWindowCallback, iWindowID, iMessage, wParam, lParam)
EndProcedure

Procedure.i WindowCallback(iWindowID, iMessage, wParam, lParam)
  ; +-----------------------------------------------------------------
  ; |Description  : CallbackProzedur des übergeordneten Fensters
  ; |Arguments    :
  ; |Result       :
  ; +-----------------------------------------------------------------
	Protected stCursorPos.POINT
	Protected *stDrawitem.DRAWITEMSTRUCT
	Protected iAlphaValue.i
  Protected iImage1.i
  Protected iImage2.i
  Protected iCurrentImage.i
  Protected iParentWindow.i

	Select iMessage
  Case #WM_DRAWITEM
	  *stDrawitem = lParam
	  If *stDrawitem\CtlType = #ODT_BUTTON
     
		  iImage1	= GetProp_(*stDrawitem\hwndItem, "bic_Image1")
		  iImage2	= GetProp_(*stDrawitem\hwndItem, "bic_Image2")
		  iCurrentImage = GetProp_(*stDrawitem\hwndItem, "bic_CurrentImage")
		  iParentWindow = GetProp_(*stDrawitem\hwndItem, "bic_ParentWindow")

		  stCursorPos\x = WindowMouseX(GetDlgCtrlID_(iParentWindow)) - GadgetX(*stDrawitem\CtlID)
		  stCursorPos\y = WindowMouseY(GetDlgCtrlID_(iParentWindow)) - GadgetY(*stDrawitem\CtlID)
 		  Debug Str(stCursorPos\x) + "  " + Str(stCursorPos\y)
		
			If stCursorPos\x  & $FF < GadgetWidth(*stDrawitem\CtlID) And stCursorPos\y  & $FF < GadgetHeight(*stDrawitem\CtlID) And iCurrentImage > 0
				StartDrawing(ImageOutput(iCurrentImage))
					DrawingMode(#PB_2DDrawing_AlphaChannel)
					iAlphaValue = Alpha(Point(stCursorPos\x, stCursorPos\y))
			  	Debug "Alpha: " + Hex(iAlphaValue)
			  StopDrawing()
			EndIf

      Select *stDrawitem\itemState
      Case #ODS_FOCUS | #ODS_SELECTED
      	If iCurrentImage <> iImage2
					StartDrawing(WindowOutput(GetDlgCtrlID_(iParentWindow)))
						;DrawingMode(#PB_2DDrawing_AlphaChannel)
				  	DrawAlphaImage(ImageID(iImage2), GadgetX(*stDrawitem\CtlID), GadgetY(*stDrawitem\CtlID))
				  StopDrawing()
					SetProp_(*stDrawitem\hwndItem, "bic_CurrentImage", iImage2)
					;Debug 1
				EndIf
      Case #ODS_FOCUS
      	If iCurrentImage <> iImage1
					StartDrawing(WindowOutput(GetDlgCtrlID_(iParentWindow)))
						;DrawingMode(#PB_2DDrawing_AlphaChannel)
				  	DrawAlphaImage(ImageID(iImage1), GadgetX(*stDrawitem\CtlID), GadgetY(*stDrawitem\CtlID))
				  StopDrawing()
					SetProp_(*stDrawitem\hwndItem, "bic_CurrentImage", iImage1)
					;Debug 2
				EndIf
      Default
      	If iCurrentImage <> iImage1
					StartDrawing(WindowOutput(GetDlgCtrlID_(iParentWindow)))
						;DrawingMode(#PB_2DDrawing_AlphaChannel)
				  	DrawAlphaImage(ImageID(iImage1), GadgetX(*stDrawitem\CtlID), GadgetY(*stDrawitem\CtlID))
				  StopDrawing()
					SetProp_(*stDrawitem\hwndItem, "bic_CurrentImage", iImage1)
					;Debug 3
				EndIf
      EndSelect
 	  
		EndIf
	EndSelect

  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure


Procedure.i ButtonImageCustom(iGadgetNr.i, iPosX.i, iPosY.i, iWidth.i, iHeight.i, iText.s, iFlags, iImage1.i, iImage2.i) 
  ; +-----------------------------------------------------------------
  ; |Description  : Darstellen eines eigenen Imagebuttons ohne störenden Rand (ImageGadget)
  ; |Arguments    : iGadgetNr    : Feste Nummer oder #PB_Any
  ; |             : iPosX        : X Position des Gadgets
  ; |             : iPosY        : Y Position des Gadgets
  ; |             : iWidth       : Breite des Gadgets
  ; |             : iHeight      : Höhe des Gadgets
  ; |             : iText        : For future use
  ; |             : iFlags       : Flags eines normalen ImageGadgets
  ; |             : iImage1      : Image-Nummer (nicht ID) des ungedrückten ImageButtons
  ; |             : iImage2      : Image-Nummer (nicht ID) des gedrückten ImageButtons
  ; |Result       : ID des neuen Gadgets
  ; +-----------------------------------------------------------------
	Protected iGadgetID.i

	If iGadgetNr.i = #PB_Any
		;iGadgetID = ImageGadget(#PB_Any, iPosX, iPosY, iWidth, iHeight, ImageID(iImage1), iFlags)
		iGadgetID = ButtonGadget(#PB_Any, iPosX, iPosY, iWidth, iHeight, "", #BS_OWNERDRAW)
		iGadgetID = GadgetID(iGadgetID)
	Else
		;iGadgetID = ImageGadget(iGadgetNr, iPosX, iPosY, iWidth, iHeight, ImageID(iImage1), iFlags)
		iGadgetID = ButtonGadget(iGadgetNr, iPosX, iPosY, iWidth, iHeight, "", #BS_OWNERDRAW)
	EndIf

  SetProp_(iGadgetID, "bic_OldCallback", SetWindowLongPtr_(iGadgetID, #GWL_WNDPROC, @ButtonImageCustomCallback()))
  SetProp_(iGadgetID, "bic_Image1", iImage1)
  SetProp_(iGadgetID, "bic_Image2", iImage2)
  SetProp_(iGadgetID, "bic_CurrentImage", 0)
  SetProp_(iGadgetID, "bic_ParentWindow", GetParent_(iGadgetID))

	; Doppelklicks verhindern
	SetClassLongPtr_(iGadgetID, #GCL_STYLE, GetClassLongPtr_(iGadgetID, #GCL_STYLE) &~ #CS_DBLCLKS)

  ProcedureReturn iGadgetID

EndProcedure

Global iEvent.i, iEventType.i, iEventWindow.i, iEventwParam.i, iEventlParam.i, iEventGadget.i, iEventMenu.i
Global iButtonImage1.i, iButtonImage2.i

If OpenWindow(0, 300, 200, 220, 60, "Custom Button Test", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)

	SetWindowCallback(@WindowCallback())

 	iButtonImage1 = LoadImage(#PB_Any, "alphabutton1.bmp")
 	iButtonImage2 = LoadImage(#PB_Any, "alphabutton2.bmp")

	ButtonImageCustom(1, 10, 10, 32, 32, "Test",0, iButtonImage1, iButtonImage2)
	ButtonImageCustom(#PB_Any, 60, 10, 32, 32, "Test2", 0, iButtonImage2, iButtonImage1)
	ButtonGadget(2, 120, 10, 80, 40, "Normaler Button")

	Repeat
	  iEvent       = WaitWindowEvent(25)
	  iEventType   = EventType()
	  iEventWindow = EventWindow()
	  iEventwParam = EventwParam()
	  iEventlParam = EventlParam()
	  iEventGadget = EventGadget()
	  iEventMenu   = EventMenu()
	    
	Until iEvent = #PB_Event_CloseWindow

	CloseWindow(0)
EndIf
Und hier nochmal die BMPs inkl. Code: BMP Dateien

Ich bin für konstruktive Hinweise dankbar.
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.02 x64, OS: Win 7 Pro x64 & Win 11 x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Useralter in 2024: 56 Jahre.
Antworten