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.
Generelles
Aktivierung
Um das OwnerDrawing zu aktivieren, muss bei dem entsprechenden Xbase-Part (sofern vorhanden) die Instanzvariable :drawMode verwendet werden. Es gibt folgende Optionen:
XBP_DRAW_NORMAL XBP_DRAW_OWNER XBP_DRAW_OWNERADVANCED
Für XBP_DRAW_NORMAL ergibt sich keine Veränderung.
Der Callback-Slot :draw muss dann wie folgt belegt werden:
oXbp:draw := {|oPS, aInfo, self| DrawItToMe(oPS, aInfo, self)}
Die Variable oPS enthält einen MicroPS, der für die Ausgabe des OwnerDrawings zu benutzen ist.
Die Variable aInfo enthält Informationen, warum das OwnerDrawing ausgeführt wird, und was getan werden soll.
Die Variable self wiederum enthält eine Referenz auf as Xbase-Part, für das OwnerDrawing auszuführen ist.
Ausführung
Die Funktion, die zwecks OwnerDrawing aufgerufen wird, entscheidet durch ihren Rückgabewert, ob OwnerDrawing ausgeführt wird, oder ob die Standard-Mechanismen greifen, die das entsprechende Xbase-Part zeichnen:
RETURN(.F.) // => OwnerDrawing wurde ausgeführt, die Standard-Mechanismen sind NICHT erforderlich RETURN(.T.) // => OwnerDrawing wurde nicht ausgeführt, die Standard-Mechanismen sind erforderlich
Dieses Beispiel zeigt, wie die Caption eines XbpStatic() fett und unterstrichen mit einer beliebigen Schriftart ausgegeben wird:
FUNCTION DrawItToMe(oPS, aInfo, oTarget) Local nAction Local oFont, oOldFont nAction := aInfo[XBP_DRAWINFO_ACTION] DO CASE CASE nAction = XBP_DRAWACTION_DRAWALL .OR. nAction = XBP_DRAWACTION_DRAWFG oFont := XbpFont():new(oPS) oFont:familyName := "Times New Roman" oFont:height := 20 oFont:bold := .T. oFont:underscore := .T. oFont:create() oOldFont := oPS:setFont(oFont) GraStringAt(oPS, {0, 5}, "Ein ganzes Halbes!") oPS:setFont(oOldFont) RETU(.F.) CASE nAction = XBP_DRAWACTION_DRAWBG // falls gewünscht OTHERWISE ENDCASE RETURN (.T.)
OwnerDrawing der Elemente in einer XbpMenuBar()
Einführung in das Thema OwnerDrawing bei 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-CallbackSlots des XbpMenuBar()-Objektes müssen vor der Ausführung von :addItem erfolgen, damit das XbpMenubar()-Objekt diese Callbacks 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.
Verwendung einer anderen Schriftart
Varieren wir das Beispiel nun ein wenig. :measureItem verwendet den Standard-Font, den der Windows-Desktop verwendet (da dies auch der Font ist, mit dem die Captions der Menü-Einträge dargestellt werden).
Aus Gründen der Vereinfachung definieren wir Schriftart und -grösse über zwei figurative Konstanten:
#DEFINE MENU_FONT_NAME "Courier New" #DEFINE MENU_FONT_POINTSIZE 16
Dafür müssen wir erst einmal im :measureItem-Slot eine andere Schriftart zuweisen, damit wir deren Dimension ermitteln:
oPS := AppDesktop():lockPS() oFnt := GraSetFont( oPS ) oFont := XbpFont():new() oFont:familyName := MENU_FONT_NAME oFont:nominalPointSize := MENU_FONT_POINTSIZE oFont:create() GraSetFont(oPS, oFont)
Ich habe hier die Erzeugung eines weiteren XbpFont()-Objektes eingefügt, dem ich als Schriftart "Courier New" zugewiesen habe. Dem XbpPresSpace() weise ich nun diese Schriftart zu, so dass die Ermittlung der Caption-Grösse sich auf die Schriftart "Courier New" mit 12 Pixel Höhe bezieht.
Als weiteren Schritt muss ich diese Zuweisung auch im :drawitem-Slot ausführen lassen:
oFont := XbpFont():new() oFont:familyName := MENU_FONT_NAME oFont:nominalPointSize := MENU_FONT_POINTSIZE oFont:create() 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 ) GraSetFont(oPS, oFont)
Durch die GraSetFont()-Zuweisung verwendet der XbpPresSpace() nur "Courier New" mit 12 Pixel Höhe als Schriftart für die Darstellung der Menü-Captions.
Analog lässt sich auch ein anderer Hintergrund definieren, oder (wie im Alaska-Beispiel gezeigt), den Menü-Einträge auch ein Icon zuweisen.
Erzeugung eines Untermenüs unter Verwendung von XbpMenu()
Wir fügen jetzt einen Menü-Zweig zu dem Fibu-Eintrag der XbpMenuBar() hinzu. Dazu erweitern wir die Funktion GenerateMenu():
FUNCTION GenerateMenu() Local oBar, oSub, oDlg, oSub2 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) } oSub := XbpMenu():new(oBar):create() oSub:title := "FiBu" oSub:measureItem := {|nItem,aDims,self| MeasureMenubarItem(oDlg, self, nItem, aDims) } oSub:drawItem := {|oPS,aInfo,self | DrawMenubarItem(oDlg, self, oPS, aInfo) } oSub2 := XbpMenu():new(oSub):create() oSub2:title := "Buchungen" oSub2:measureItem := {|nItem,aDims,self| MeasureMenubarItem(oDlg, self, nItem, aDims) } oSub2:drawItem := {|oPS,aInfo,self | DrawMenubarItem(oDlg, self, oPS, aInfo) } oSub2:addItem({"was nach rechts", , , XBPMENUBAR_MIA_OWNERDRAW}) oSub2:addItem({"was nach links", , , XBPMENUBAR_MIA_OWNERDRAW}) oSub2:addItem({"was in den Papierkorb", , , XBPMENUBAR_MIA_OWNERDRAW}) oSub:addItem({oSub2, NIL}) oSub:addItem({"Heftungen", , , XBPMENUBAR_MIA_OWNERDRAW}) oSub:addItem({"Zeitschriftungen", , , XBPMENUBAR_MIA_OWNERDRAW}) oBar:addItem({oSub, NIL}) oBar:addItem({"Daten", , , XBPMENUBAR_MIA_OWNERDRAW}) oBar:addItem({"Fenster", , , XBPMENUBAR_MIA_OWNERDRAW}) oBar:addItem({"Exit", , , XBPMENUBAR_MIA_OWNERDRAW}) RETURN (.T.)
Wir fügen ein XbpMenu() hinzu, dessen erster Eintrag direkt ein Untermenü ist, sowie zwei weitere, einfache Menü-Einträge.
In jeder Menü-Struktur wird die gleiche Funktion MeasureMenubarItem() dem Callback-Slot :measureItem zugewiesen.
Wenn wir im Debugger einen Breakpoint beim Aufruf von MeasureMenubarItem() setzen, stellen wir fest, dass beim Programmbeginn diese Funktion viermal aufgerufen wird, und zwar einmal für jeden Eintrag der XbpMenuBar(). Erst beim Auswählen eines Eintrags (Event xbeP_ItemSelected) wird MeasureMenubarItem() erneut aufgerufen, d.h. erst zu diesem Zeitpunkt wird der Menü-Zweig unterhalb von "Fibu" "ermittelt" und erzeugt.