OwnerDrawing
Einleitung
OwnerDrawing ist eine Programmtechnik, die es ermöglicht, Dinge mit Standard-XbaseParts zu machen, die man auf anderem Wege nicht machen kann. Grob vereinfacht bietet OwnerDrawing die Möglichkeit, XbaseParts (oder Teile davon) von Windows (= Standard) oder durch das eigene Programm zeichnen zu lassen. Ein Beispiel wäre eine optische Hervorhebung von Zellen in einem Browse.
OwnerDrawing der Elemente in einer XbpMenuBar()
Dieser Programmcode soll aus Verdeutlichung dienen. Teile hiervon stammen aus dem XbpImgMenu()-Beispiel, das Alaska mit Xbase++ ausliefert. Für dieses Beispiel wurde der Code grob vereinfacht.
#DEFINE SCREEN_RATIO 0.6 #DEFINE SCREEN_MIN_X 800 #DEFINE SCREEN_MIN_Y 600 #INCLUDE "AppEvent.CH" #INCLUDE "NLS.CH" #INCLUDE "Xbp.CH" #INCLUDE "GRA.CH" #DEFINE ITEM_SPACING 5 FUNCTION Main() Local mp1, mp2 Local nEvent Local oXbp GenerateMenu() SetAppFocus(SetAppWindow()) nEvent := xbe_None oXbp := mp1 := mp2 := nEvent WHILE nEvent <> xbeP_Close nEvent := AppEvent(@mp1, @mp2, @oXbp) oXbp:handleEvent(nEvent, mp1, mp2) END RETURN (.T.) FUNCTION GenerateMenu() Local oBar, oSub, oDlg oDlg := RootWindow() oBar := oDlg:menuBar():new():create() oBar:measureItem := {|nItem,aDims,self| MeasureMenubarItem(oDlg,self,nItem,aDims) } oBar:drawItem := {|oPS,aInfo,self | DrawMenubarItem(oDlg,self,oPS,aInfo) } oBar:addItem({"Fibu", , , XBPMENUBAR_MIA_OWNERDRAW}) oBar:addItem({"Daten", , , XBPMENUBAR_MIA_OWNERDRAW}) oBar:addItem({"Fenster", , , XBPMENUBAR_MIA_OWNERDRAW}) oBar:addItem({"Exit", , , XBPMENUBAR_MIA_OWNERDRAW}) RETURN (.T.) FUNCTION MeasureMenubarItem(oDlg, oBar, nItem, aDims) LOCAL oPS LOCAL cStr LOCAL cTmp LOCAL oFnt LOCAL aBox oPS := AppDesktop():lockPS() oFnt := GraSetFont( oPS ) cStr := oBar:getItem(nItem)[1] cTmp := StrTran( cStr, "~" ) aBox := GraQueryTextBox( oPS, cTmp ) aDims[1] := aBox[3,1] - aBox[2,1] aDims[2] := oFnt:width + ITEM_SPACING *2 AppDesktop():unlockPS() RETURN oBar FUNCTION DrawMenuBarItem(oDlg, oBar, oPS, aInfo) LOCAL aItem := oBar:getItem( aInfo[1] ) LOCAL aSAttrs := Array( GRA_AS_COUNT ) LOCAL aAAttrs := Array( GRA_AA_COUNT) IF BAnd(aInfo[3], XBP_DRAWSTATE_SELECTED) != 0 aAAttrs[GRA_AA_COLOR] := XBPSYSCLR_WINDOW ELSE aAAttrs[GRA_AA_COLOR] := XBPSYSCLR_3DFACE ENDIF GraSetAttrArea( oPS, aAAttrs ) // Pruefen, ob die Anwendung unter windows XP // mit eingeschalteten Windows Themes ausgefuehrt // wird. Falls ja, Hintergrundrechteck anpassen // (Separator). IF IsThemeActive(.F.) == .T. GraBox( oPS, {aInfo[4][1],aInfo[4][2]+1},{aInfo[4][3],aInfo[4][4]}, GRA_FILL ) ELSE GraBox( oPS, {aInfo[4][1],aInfo[4][2]}, {aInfo[4][3],aInfo[4][4]}, GRA_FILL ) ENDIF aSAttrs[GRA_AS_VERTALIGN] := GRA_VALIGN_HALF GraSetAttrString( oPS, aSAttrs ) oPS:DrawCaptionStr( {aInfo[4][1] + ITEM_SPACING,aInfo[4][2]}, {aInfo[4][3],aInfo[4][4]}, aItem[1]) RETURN oBar FUNCTION DBESys() // keine DBE erforderlich RETURN (.T.) FUNCTION AppSys() Local aSize, oDlg, aPos[2], aSizeNew[2], nI SetLocale(NLS_ICURRENCYEURO, "1") SET CHARSET TO ANSI SET DATE GERMAN SET CENTURY ON aSize := AppDesktop():currentSize() FOR nI := 1 TO 2 aSizeNew[nI] := Int(aSize[nI] * SCREEN_RATIO) NEXT IF aSizeNew[1] < SCREEN_MIN_X aSizeNew[1] := SCREEN_MIN_X ENDIF IF aSizeNew[2] < SCREEN_MIN_Y aSizeNew[2] := SCREEN_MIN_Y ENDIF aPos[1] := Int((aSize[1] - aSizeNew[1]) / 2) aPos[2] := Int((aSize[2] - aSizeNew[2]) / 2) oDlg := XbpDialog():new(AppDesktop(), AppDesktop(), aPos, aSizeNew, , .F.) oDlg:sysmenu := .T. oDlg:hideButton := .T. oDlg:taskList := .T. oDlg:close := {|| WinManKill(), _QUIT()} oDlg:title := "" oDlg:create() RootWindow(oDlg) oDlg:setTitle(GetProgramTitle()) oDlg:drawingArea:scrollBars := XBP_SCROLLBAR_VERT oDlg:show() SetAppWindow(oDlg) RETURN (.T.) FUNCTION RootWindow(oDlg) Static oStatic IF oDlg <> NIL oStatic := oDlg ENDIF IF oStatic = NIL ConfirmBox(, "Fehler", "Programm nicht richtig geladen", XBPMB_CRITICAL, XBPMB_OK) QUIT ENDIF RETURN (oStatic) FUNCTION WinManKill() RETURN (.T.) FUNCTION GetProgramTitle() Local cTitle := "Ein Test-Programm" RETURN (cTitle)
Wichtig ist die Reihenfolge der Zuweisungen. Die Besetzung der :measureItem und :drawItem-Slots des XbpMenuBar()-Objektes müssen vor der Ausführung von :addItem erfolgen, damit das XbpMenubar()-Objekt diese Methoden kennt und für jedes Item ausführen kann.
In dem Slot :measureItem wird ermittelt, wie gross ein Menü-Eintrag ist. Das Ergebnis wird im Format {Länge, Höhe} über den Aufruf-Parameter aDim zurückgeliefert, nicht über den Wert in der RETURN-Anweisung!
Wenn das Menü angezeigt wird, wird für jedes Item, das mit dem Parameter XBPMENUBAR_MIA_OWNERDRAW als viertem Parameter definiert wurde, die in :drawItem definierte Funktion ausgeführt. In diesem Beispiel wird lediglich der normale Text in den als Parameter übergebenen XbpPresSpace() ausgegeben. Denkbar sind hier (wie im Alaska-Beispiel) die Anzeige von Icons, oder verschiedenfarbige Hintergründe für die einzelnen Menü-Einträge.