Using OwnerDraw Pushbuttons
With A Manifest


Copyright © 2009 Clayton Jones
by Clayton Jones
(
with thanks to Alaska support staff for their help)
Revised July 21, 2009

Introduction

When I originally set about to make an ownerdraw button I found very little information. The folder \SOURCE\SAMPLES\BASICS\OWNERDRAW had only samples for menu and listbox. The xbpPushButton:xbeP_Draw help topic had basic information about the aInfo constants, but no example of how to use them and no mention of the effects of a manifest.

Based on the available information and my own experimenting I ended up with a very nice button that worked perfectly (without a manifest).  I didn't experiment with a manifest at the time because it didn't affect the appearance of the ownerdraw button and didn't seem relevant.  It was only while preparing a new Top-Down demo app with a compiled manifest that I noticed problems with the behavior.  Things like buttons remaining depressed after a click or not depressing at all, not retaining focus, or not drawing or removing focus dots properly.

This sent me into Detective Mode and it turns out that the ownerdraw events sent to a button are different with a manifest.  The bottom line was that the event handling logic that worked perfectly without a manifest was insufficient with one.  The challenge was to find a way to make it work both ways.  Fortunately I was able to find workarounds for all but one minor issue.  It required a  reworking of the event handling logic, but the button now works essentially the same with and without a manifest.

This article outlines the things I learned, in hopes that it will save others from having to learn the hard way.  First up is a brief recap of the ownerdraw constants used by a button.
 
Action and State Constants
The aInfo array for each ownerdraw event contains numeric values for
Action and State, among other things.  These two values are the important ones for controlling the behavior:

        
aInfo = {nItem, nAction, nState, aRect}

Here are the constants used for these in XBP_DRAW_OWNER mode

*** Only these three nAction values are received by a button
   XBP_DRAWACTION_DRAWALL     1
   XBP_DRAWACTION_SELCHANGE   2
   XBP_DRAWACTION_FOCUSCHANGE 4

*** Only these three nState values are of use here
   XBP_DRAWSTATE_NORMAL    0
   XBP_DRAWSTATE_SELECTED  1
   XBP_DRAWSTATE_FOCUS    16

 

The Original :Draw() Method

The original draw method was divided into three sections based solely on the three Action values:

  IF Action=DrawAll
   ...draw button in normal state without focus dots
   ...draw focus dots if dot flag is .T.

ELSEIF Action=SelChange
   ...make button appear depressed

ELSEIF Action==FocusChange
   ...set dot flag .T. if receiving focus,
      .F. if losing focus

ENDIF
This worked perfectly without a manifest but was badly broken with one.  Clearly, something was wrong. 
 

What Is Different With A Manifest?

There are three differences:

a) The initial FocusChange event (losing focus) associated with TAB/SH_TAB keystrokes is omitted. This means that this event can not be used for focus dot management.

b) The Action value of selection events is changed from SelChange to DrawAll, resulting in no SelChange events being received with a manifest. SelChange events were used to make the button appear depressed when clicked.

c) Extra and unnecessary DrawAll events are generated on a mouseclick (caused by Enter/Leave mouse tracking automatically implemented with a manifest). This causes a minor flicker problem. Not a show-stopper, but still an annoyance.
 

Scenarios

To illustrate the differences, here are three scenarios that had behavior issues.  Each chart compares the events generated with and without a manifest and is followed by a problem description and workaround.   The Action and State values are presented in this format: Action/State

*************************************************************
Scenario 1: Click on a Button

   No Manifest                    Manifest
------------------------------------------------------------

                             * DrawAll/Normal
* FocusChange/Focus          * FocusChange/Focus
* SelChange/Selected+Focus   * DrawAll/Selected+Focus
* SelChange/Focus            * DrawAll/Focus
                             * DrawAll/Focus

Problem: The SelChange Actions have been changed to DrawAll, so we cannot rely on Action=SelChange to make the button appear depressed. In addition, the first and last DrawAll events cause extra draws, resulting in distracting flickers when the mouse pointer enters and leaves a button's  rectangle.

WorkAround: Make the first event handler section trap for State=Selected.  This ignores the Action value and will trap manifest and non-manifest Selection events. (I could not find a way to eliminate the extra DrawAll events).

*************************************************************
Scenario 2: Focus is on Button, press Spacebar

    No Manifest                 Manifest
------------------------------------------------------------

* SelChange/Selected+Focus   * DrawAll/Selected+Focus
* SelChange/Focus            * DrawAll/Focus

Problem: Same problem as above, the SelChange Actions have been changed to DrawAll.

WorkAround: The challenge here is to trap the 2nd SelChange event when a manifest is not in use.  This is done in the second event handler section in the new draw method below.

*************************************************************
Scenario 3: Focus is on Button 1, press TAB to put focus on Button 2

      No Manifest                Manifest
------------------------------------------------------------

* B1 - FocusChange/Normal  *
* B2 - FocusChange/Focus   * B2 - FocusChange/Focus
* B1 - DrawAll/Normal      * B1 - DrawAll/Normal

Problem: The first FocusChange/Normal event (losing focus) is missing, so we cannot rely on it for focus dot management (same thing happens with Shift+Tab).

WorkAround: Test for State=Focus in the DrawAll and FocusChange sections to manage the focus dots.

 

The New :Draw() Method

The solution requires changing the conditional logic in the :Draw() method. The revised version still has three sections, but the conditions defining them are a mixture of Action and State values. Also, the order has changed. The test for Selected must be first.
 
  ******* The first condition traps for State=Selected ******* which is associated with SelChange/Selected
******* without a manifest, with a manifest is
******* DrawAll/Selected.
IF State=Selected
   ...make button appear depressed

ELSEIF Action=DrawAll .OR. Action=SelChange
     *** This OR condition traps for SelChange/Focus
     *** events generated after SpaceBar keyUp without a
     *** manifest

   ...draw button in normal undepressed state,
      no focus dots

   ******* Focus Dots?
   IF State=Focus
      ...draw focus dots
   ENDIF

ELSEIF Action=FocusChange .AND. State=Focus
   ...draw focus dots
ENDIF

 
Why Is This Happening?
I received an explanation for this behavior from Alaska support.  Rather than try to explain it myself and make a mess of it I will just quote the relevant parts here:
 
  Manifests carry a lot of information...it's the dependency on the "new" common controls DLL that changes things.  Back in the old days, two versions of the native Windows controls were shipped with Windows XP.  However, only applications explicitly requesting the new DLL (version 6.0 versus 5.82) got the new Luna look. 

The Windows controls in the newer common controls DLL behave differently.  Part of it is the info that is contained in the various (background) images defined in a visual style.  The Win32 API has  functions such as "DrawThemeBackground()" which draw the  background of a themed object with respect to its current state.  The image drawn already contains the "selected", "disabled" or  "hot" state information of an object. Achieving the same results  required several drawing operations previously. Therefore, the  paradigm has changed a bit here. The differences in the drawing  events reflect this change.

 
 

To summarize, the purpose for the different events is to match the needs of the newer control set.  With normal pushbuttons this all happens under the hood.  But when we create an ownerdraw button we are taking the drawing events and acting upon them ourselves.  Our challenge is to have an event handler that can work in both cases.
 

Final Thoughts

I hope this will be helpful to anyone wanting to stick their toe in the ownerdraw button waters.  My thanks to the support staff of Alaska Software for their effort and patience in helping to understand this issue.  They have assured me that they will review the Xbase++ documentation for ownerdraw buttons and manifests and try to fill in some of the gaps.
 

Copyright © 2009 Clayton Jones
All rights reserved.

Return to:  Articles Page |Top-Down Page | Software And Services | Home