OwnerDrawing

Aus Wiki des Deutschsprachige Xbaseentwickler e. V.
Zur Navigation springen Zur Suche springen


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.