No announcement yet.

Owner Draw button themed for XP

  • Filter
  • Time
  • Show
Clear All
new posts

  • Owner Draw button themed for XP

    the following is an attempt to address the issue of making image buttons themed when run in xp. this has been a frequent request on this forum, but hasn’t yet been answered. i actually just draw red text, but bitblting an image onto a button would be nearly as easy.

    don’t get too excited though, because my code doesn’t yet ‘hotlight’ the button by adding an orange border when the mouse cursor is on it. can anyone suggest why the %wm_drawitem message never seems to be received with the %ods_hotlight flag set?

    the code assumes that there is an xp manifest in the resource file. calling isthemeactive does not appear to be necessary because openthemedata only returns a handle if the program is running under xp and the style chosen is themed (i.e. not ‘windows classic’). is by rosa meltronco and is the bottom half of the code posted at:
    #resource "themed"  ‘includes xml manifest file
    #include ""
    #include ""  ‘by rosa meltronco
    callback function ownerdrawproc
       select case cbmsg
         case %wm_drawitem       'owner draw buttons 
           dim t as drawitemstruct ptr
           dim txt$, r as rect, htheme as dword, istate as long
           r= @t.rcitem
           htheme= openthemedata(cbhndl, "button")
           if htheme then   'xp theme         
              control send cbhndl,cbwparam,%wm_erasebkgnd,@t.hdc,0 
                'avoids button acquiring square corners after being pressed
              istate=1 ' default state = %minbs_normal 
              if (@t.itemstate and %ods_inactive) then istate=5        
              if (@t.itemstate and %ods_disabled) then istate=4      
              if (@t.itemstate and %ods_hotlight) then istate=2 ' %minbs_hot       
              if (@t.itemstate and %ods_selected) then istate=3 ' %minbs_pushed       
              call drawthemebackground(htheme, @t.hdc, 1, istate, @t.rcitem, byval %null) 
              call closethemedata(htheme)
           else             'not xp or not themed
             if(@t.itemstate and %ods_selected) then
               call drawframecontrol(byval @t.hdc,@t.rcitem,%dfc_button,%dfcs_buttonpush or %dfcs_pushed)
               r.ntop=r.ntop+2 : r.nleft=r.nleft+2 'move the text 1 pixel when button pressed
               call drawframecontrol(byval @t.hdc,@t.rcitem,%dfc_button,%dfcs_buttonpush)  
             end if
           end if
      'we've drawn the button - now put text or graphics onto it
           control get text cbhndl,cbctl to txt$
           settextcolor @t.hdc,%red
           setbkmode @t.hdc,%transparent
           drawtext @t.hdc,bycopy txt$,-1,r,%dt_singleline or %dt_center or %dt_vcenter
       end select
    end function
    function pbmain
       local hdlg as dword
       dialog new 0,"themed owner draw buttons",0,0,150,100,%ws_caption or %ws_sysmenu to hdlg
       control add button, hdlg, 100, "owner draw 1",5,20,50,14 ,%ws_tabstop or %bs_ownerdraw 
       control add button, hdlg, 101, "owner draw 2",5,40,50,24 ,%ws_tabstop or %bs_ownerdraw
       control add button, hdlg, 102, "standard",5,80,50,14 'standard button for comparison
       dialog show modal hdlg, call ownerdrawproc  
    end function

  • #2
    Hm, still no joy with 'hotlighting'. I came across the BCN_HOTITEMCHANGE notification message on msdn (%BCN_HOTITEMCHANGE = -1249), but I'm only getting this for normal buttons, not my owner-drawn ones. It seems that on my version of XP (Home SP2), M$ has neglected to implement the documented %ODS_HotLight state of the WM_DrawItem message for owner-drawn buttons. Perhaps it works with other types of control, otherwise it is a totally redundant facility, since WM_DrawItem is only used with owner draw.

    I suppose I could do my own hit testing in a WM_MouseMove handler, or perhaps a sub-classed button would retain its themed look, but that seems rather a lot of work just to get a button to highlight when the mouse is on it.

    Any comments or suggestions please?


    • #3
      Seems like you will have to subclass the button, trap mouse-over and draw this state "by hand".


      PowerBASIC Staff
      Private web-site:
      Free downloads: POFFS, incLean, PBcodec, custom controls and PB samples.


      • #4
        Thanks, Borje.

        Well here's a much simpler way of doing XP themeing of 'special' buttons, using sub-classing rather than owner-draw, making Windows do all the work of button drawing, hotlighting, etc. No special includes are now needed.

        This code is a bit primitive at the moment - it clearly need a some arrays if there are more than two buttons to be treated in this way. The global oldProc could be avoided by storing it in the window properties with SetProp. This would also provide a separate value for each button for the address of the normal button procedure, which Windows seems to use.
        #RESOURCE "Themed"   'include XML manifest file
        #INCLUDE ""
        GLOBAL oldProc AS DWORD  'use for both sub-classed buttons
                               BYVAL wParam AS LONG, BYVAL lParam AS LONG) AS LONG
          LOCAL hDC AS DWORD, R AS RECT, hOldObject AS DWORD
             'done normal painting first, now add our finishing touches
          IF wMsg=%WM_PAINT THEN
            GETClientRECT hWnd,R
              SELECT CASE GetDlgCtrlID(hWnd)
              CASE 100   'put red text on the button
                 SETTEXTCOLOR hDC,%RED
                 DRAWTEXT hDC,"Sub-classed",-1,R,%DT_SINGLELINE OR %DT_CENTER OR %DT_VCENTER
                 SelectObject hDC,hOldObject
              CASE 101    'draw an ellipse on the button
                 hOldObject=SelectObject (hDC,GetStockObject(%Gray_Brush))
                 ELLIPSE hDC, R.nLeft+20, R.nTop+10, R.nRight-20, R.nBottom-10
                 SelectObject hDC,hOldObject
              END SELECT 
            ReleaseDC hWnd,hDC   
          END IF
        END FUNCTION  
        CALLBACK FUNCTION ButtonProc
           STATIC hCtrl1,hCtrl2 AS DWORD 
             CONTROL HANDLE CBHNDL,100 TO hCtrl1
             oldProc=SETWINDOWLONG(hCtrl1,%GWL_WndProc, CODEPTR(SCButtonProc))
             CONTROL HANDLE CBHNDL,101 TO hCtrl2
             IF SETWINDOWLONG(hCtrl2,%GWL_WndProc, CODEPTR(SCButtonProc))<>oldProc THEN _
                 MSGBOX "False assumption!" 'works OK using the same oldProc for both buttons
           CASE %WM_Destroy  
             SETWINDOWLONG hCtrl1,%GWL_WndProc, oldProc
             SETWINDOWLONG hCtrl2,%GWL_WndProc, oldProc
           END SELECT
           LOCAL hDlg AS DWORD 
           DIALOG NEW 0,"Themed sub-classed buttons",,,150,100,%WS_CAPTION OR %WS_SYSMENU TO hDlg
           CONTROL ADD BUTTON, hDlg, 100, "",5,20,50,14 
           CONTROL ADD BUTTON, hDlg, 101, "",5,40,50,30 
           CONTROL ADD BUTTON, hDlg, 102, "Standard",5,80,50,14 'standard button for comparison
           DIALOG SHOW MODAL hDlg, CALL ButtonProc  


        • #5
          Just tried Simon's code and, hitting Left-Alt or one of the
          Arrow-Keys, encountered a problem with disappearing text and icon
          of both subclassed buttons, respectively (XP/PB8). The problem
          was cured after replacing

          "IF wMsg=%WM_PAINT THEN"

          I hope there are no objections...

          Thanks, Simon, for your code. This subclassing thing was really
          over my head!

          Regards, Hanns.


          [This message has been edited by Hanns Ackermann (edited September 23, 2006).]


          • #6
            Glad you found it useful, Hanns. Thanks for improving my code.

            I'd never come across %WM_UPDATEUISTATE before. I see it only came in with Win 2000. MSDN:
            An application sends the WM_UPDATEUISTATE message to change the user interface (UI) state for the specified window and all its child windows.
            Hmm, not quite sure what that means in practical terms. Hitting Alt causes the keyboard accelerator characters to be underlined, which is presumably achieved with this message, but not sure why the arrow keys cause it to be sent.

            Also, I would have thought that %WM_UPDATEUISTATE would cause a %WM_Paint to be generated if a button needed to change its appearance, but obviously not.


            • #7
              Perhaps the "correct" method would be to call RedrawWindow in response to WM_UPDATEUISTATE?

              Slam Database Manager - simple, but effective database system.
              PrpT Property List - kiss those complex 'options dialogs' goodbye!
     | Slam DBMS | PrpT Control | Other Downloads | Contact Me


              • #8
                Didn't Jose or one of those guys post something to do just this on
                their forum??? I got the code in a zip file right in front of me.

                Even handles themed and no theme and does very nice.

                If you aim at will hit it.
                Mobile Solutions
                Sys Analyst and Development


                • #9
                  Theme aware XP push button that can also act as a toggle button and
                  display an icon or bitmap.


                  SED Editor, TypeLib Browser, COM Wrappers.


                  • #10
                    See...when all else fails, ask Jose. If he can't fix it, it's broke.

                    If you aim at will hit it.
                    Mobile Solutions
                    Sys Analyst and Development