Using XP Visual Styles with VO

Contents

Introduction
Application Manifest
Correcting Toolbar Tooltips
Correcting Right-aligned SingleLineEdits
MultiLineEdit Borders
Tab Controls
Tab Pages
WinDLLVersion
Enable/Disable Visual Styles

Introduction

XP provides visual styles to define the appearance of controls and windows.

With VO 2.7/a/b the only thing necessary for proper XP style support is the inclusion of the application manifest resource. All the other changes listed here are not required any more because I have ensured that they are now part of VO 2.7.

There are two ways to include the application manifest in the application:  1. use the "Application Manifest" step below or  2.  import ManSrc.MEF from the VO 2.7 AppWiz directory.

If your application is not showing the XP styles you should check that the manifest file (pointed to by the manifest resource) is correct.

The best way to check if your system is configured correctly is to create the DataAware Controls sample in the VO new application gallery. Then include the XP application manifest in this sample and run it. You should see the XP style controls.


However, if you just take your standard VO 2.5 app onto XP the controls just look the same as they used to under Win9x/ME/2000. To get the new look controls you need to make a change to your application - you need to enable the XP visual styles. The table below shows the difference in appearance of some of the controls. In addition to the immediate visual difference, under XP many of the controls also change their appearance when the mouse moves over them.
 

  Controls without XP Visual Style support Controls with XP Visual Style support
Pushbutton
Checkbox,
RadioButton
Slider
Toolbar button
Progress bar

Application Manifest

Fortunately the process to do this is simple, and the same EXE can be run on the older Windows platforms as well as XP. On the older Windows the controls look as they always did, on XP they take on the new look.

The first steps are:

  1.  Create a manifest file. This file will be compiled into your app as a resource and will tell XP that you want to use the new common controls. Copy the following XML and put it into a file - for our example call the file Application.man.

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity version="1.0.0.0" processorArchitecture="X86"
    name="CompanyName.ProductName.YourApp" type="win32"/>
    <description>Your application description here.</description>
    <dependency>
    <dependentAssembly>
    <assemblyIdentitytype="win32" name="Microsoft.Windows.Common-Controls"
    version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df"
    language="*" />
    </dependentAssembly>
    </dependency>
    </assembly>

    You can change the "CompanyName.ProductName.YourApp" and "Your application description here." to whatever you want.

    You can download a copy of application.man from here: application.zip.
     
  2.  Create some DEFINEs in your application:
    
    DEFINE RT_MANIFEST := 24
    DEFINE CREATEPROCESS_MANIFEST_RESOURCE_ID := 1

  3. Create a RESOURCE statement to bring your manifest file into the EXE. For our example I have put the manifest file into the VO executable directory. Note the following should appear as one line in the VO source editor
    
    RESOURCE CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST %executabledir%\application.man

     
  4. Compile and build your EXE. You will not see any difference on non-XP Windows. But on XP you will now see the new common controls, with the new styles, in use in your application.

Correcting Toolbar ToolTips

The steps above go a long way towards providing the proper XP look-and-feel. However there are some additional things that need to be addressed. The first problem is that the application toolbar loses its tooltips. This can be corrected by adding in one method, one function and one structure. No other changes are required to your existing code for this step. Just add the code below and the toolbar tooltip problem is fixed.

STRUCT _winTOOLINFOXP ALIGN 1
        MEMBER cbSize AS DWORD
        MEMBER uFlags AS DWORD
        MEMBER hwnd AS PTR
        MEMBER uId AS DWORD
        MEMBER rect IS _winRECT
        MEMBER hinst AS PTR
        MEMBER lpszText AS PSZ
        MEMBER lParam AS LONG
METHOD ControlNotify(oControlNotifyEvent) CLASS AppWindow

    IF oControlNotifyEvent:NotifyCode == TTN_NEEDTEXT
        ProcessToolTip(SELF,oControlNotifyEvent)
    ELSE
        SUPER:ControlNotify(oControlNotifyEvent)
    ENDIF
FUNCTION ProcessToolTip(oOwner,oControlNotifyEvent)

    LOCAL nCode     AS LONG
    LOCAL lParam    AS LONG

    LOCAL oControl    AS Control

    LOCAL strucToolInfo IS _winTOOLINFOXP
    LOCAL strucToolTip  AS _winTOOLTIPTEXT
    LOCAL strucNotify   AS _winNMHDR
    LOCAL cTipText      AS STRING
    LOCAL oWindow       AS OBJECT


    nCode      := oControlNotifyEvent:NotifyCode
    lParam     := oControlNotifyEvent:lParam
    oControl   := oControlNotifyEvent:Control

    strucNotify          := PTR(_CAST, lParam)
    strucToolInfo.cbSize := _SizeOf(_winTOOLINFOXP)
    strucToolInfo.hwnd   := strucNotify.hwndFrom

    SendMessage(strucNotify.hwndFrom, TTM_GETCURRENTTOOL, 0, ;
                LONG(_CAST, @strucToolInfo))
    oWindow := __WCGetControlByHandle(strucToolInfo.hwnd)
    IF (oWindow == NULL_OBJECT)
        oWindow := __WCGetControlByHandle(GetParent(strucToolInfo.hwnd))
    ENDIF
    strucToolTip := PTR(_CAST, lParam)

    IF IsInstanceOf(oWindow, #ToolBar)
        cTipText  := oWindow:GetTipText(strucToolTip.hdr.idFrom, ;
                                        #MenuItemID)
        IF IsMethod(oOwner, #StatusMessage) .and. ;
               (oOwner:Menu != NULL_OBJECT)
            Send(oOwner, #StatusMessage, ;
                     oOwner:Menu:Hyperlabel(strucToolTip.hdr.idFrom))
        ENDIF
    ELSEIF IsInstanceOf(oWindow, #TabControl)
        cTipText := oWindow:GetTipText(;
                oWindow:__GetSymbolFromIndex(strucToolTip.hdr.idFrom))
    ENDIF

    IF Empty(cTipText)
        IF _And(strucToolTip.uFlags, TTF_IDISHWND) > 0
            oWindow := __WCGetControlByHandle(strucToolTip.hdr.idFrom)
            IF (oWindow == NULL_OBJECT)
                oWindow := __WCGetControlByHandle(;
                                    GetParent(strucToolTip.hdr.idFrom))
                IF (oWindow == NULL_OBJECT)
                    oWindow := __WCGetControlByHandle(;
                                 GetParent(GetParent(strucToolTip.hdr.idFrom)))
                ENDIF
                IF !IsInstanceOf(oWindow, #ComboBox) .and. ;
                           !IsInstanceOf(oWindow, #IPAddress)
                    oWindow := NULL_OBJECT
                ENDIF
            ENDIF
        ELSE
            oWindow := __WCGetControlByHandle(GetDlgItem(oOwner:handle(), ;
                                INT(_CAST, strucToolTip.hdr.idFrom)))
        ENDIF

        IF (oWindow != NULL_OBJECT)
            cTipText := oWindow:ToolTipText
            IF Empty(cTipText) .and. oWindow:UseHLForToolTip
                cTipText := oWindow:Hyperlabel:Description
            ENDIF
        ENDIF
    ENDIF

    IF Empty(cTipText)
        strucToolTip.lpszText := NULL_PSZ
    ELSE
        strucToolTip.lpszText := Cast2Psz(cTipText)
    ENDIF
    RETURN NIL

 

Correcting Right-aligned SingleLineEdits

The usual way to achieve right aligned SingleLineEdit controls is to make use of the ExAlignment property that is available on the ExStyles tab in the VO Window Editor. This causes the control to be created with the WS_EX_RIGHT style enabled. However under the XP Visual Styles a SingleLineEdit needs to have the ES_RIGHT style enabled to right align the text in the control. This style must be applied at the time the control is created - it cannot be set later. This means we need to change the window editor's configuration so that the code generated will contain ES_RIGHT when we want it. This is done by editing the CAVOWED.INF file in the CAVO25\BIN directory. The steps are:

  • Backup the CAVOWED.INF file
  • Edit CAVOWED.INF in a text editor
  • Search for [CONTROL:TEXTCONTROL:EDIT]
  • Put this line after the other WindowStyles in that section:
    WindowStyle19=Alignment,ES_LEFT:ES_CENTER:ES_RIGHT(EDITALIGN)
  • Search for SingleLineStyles=
  • Put ",Alignment" (without quotes) at the end of the line
  • Save CAVOWED.INF.

The next time you open the window editor you should see that there is an Alignment property on the Styles property page for SingleLineEdit controls, and you can choose Left, Center and Right alignments. Use this property to obtain the correct alignment when using XP Visual Styles.

MultiLineEdit Borders

A MultiLineEdit (MLE) control can display an irregular bottom border when used with the XP Visual Styles. This strange border seen at the bottom of the control is actually the top of the control's horizontal scroll bar. The VO SDK actually automatically sets the WS_HSCROLL style for the MLE. However the code doesn't resize the control, so the horizontal scroll bar sits at the bottom of the control, out of sight under previous Windows versions but peeking out under XP.

If you force the control to resize, e.g. call the __RescalCntlB() method or change the control's dimensions, the scroll bar appears under all Windows versions. __RescalCntlB() when called with the current font of the control will actually leave it the same size it was originally but by going through the process the scroll bar will appear.

So if you want the WS_HSCROLL property for the MLE just put something like this in the postinit of the window:


oDCMultiLineEdit1:__RescalCntlB(oDCMultiLineEdit1:font:handle())

Better still, put the resize into a sublcass of MultiLineEdit and use that instead.

If you don't want WS_HSCROLL on the MLE, just put this into the window postinit:


oDCMultiLineEdit1:setstyle(WS_HSCROLL,FALSE)

Tab Controls

Tab controls used to support various positions for the tabs: top, bottom, left and right. Under XP and version 6 of the common controls there is only one choice: the top. You can read about the tab styles at: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/tab/styles.asp.

For example, it says this about the vertical (left/right) style:

TCS_VERTICAL
Version 4.70. Tabs appear at the left side of the control, with tab
text displayed vertically. This style is valid only when used with the
TCS_MULTILINE style. To make tabs appear on the right side of the
control, also use the TCS_RIGHT style. This style is not supported if
you use ComCtl32.dll version 6.

Tab Pages

Tab pages by default do not conform to the XP visual styles - they display "unthemed", as a standard window. Some code is necessary to force the visual style to be used - you need to call the function EnableThemeDialogTexture and pass it the handle of the tab page that you want to display using visual styles. In the example below the code has been put in the PostInit() of a tab page called TabControl1_Page1. In your applications you might choose to put similar code into a TabControl method, shown in the second example below - you can then call this method from the PostInit() of the window owning the TabControl. Note that we also have to test whether or not visual styles are in use in the application, and this is done by checking which version of the common controls is loaded. This checking makes use of the WinDLLVersion class which is also included below.

DEFINE ETDT_DISABLE := 0x01
DEFINE ETDT_ENABLE := 0x02
DEFINE ETDT_ENABLETAB = 0x06
DEFINE ETDT_USETABTEXTURE := 0x04
METHOD PostInit(oParent,uExtra) CLASS TabControl1_Page1
    LOCAL hModule AS PTR
    LOCAL hFunc AS PTR
    LOCAL h AS PTR
    LOCAL dwFlags AS DWORD
    
    // Check if the app i
s using the newer
    // common control for XP visual styles
    IF WinDLLVersion{"COMCTL32"}:major >= 6
        
        //
Get the theme DLL
        hModule := LoadLibrary(PSZ("uxtheme.dll"))
        IF ! hModule == NULL_PTR
            
            // Find the function we need
            hFunc := GetProcAddress(hModule,PSZ("EnableThemeDialogTexture"))
            IF ! hFunc == NULL_PTR
                h := SELF:handle()
                dwFlags := ETDT_ENABLETAB
                
                // Call the function
                PCALL(hFunc,h,dwFlags)
                
            ENDIF
        
        ENDIF
    
    ENDIF
    
    RETURN NIL

 
METHOD ForcePageXPStyle() CLASS TabControl
    LOCAL hModule AS PTR
    LOCAL hFunc AS PTR
    LOCAL h AS PTR
    LOCAL dwFlags AS DWORD
    LOCAL nCount AS DWORD
    LOCAL i AS DWORD
    LOCAL oPage AS OBJECT
    
    // Check if the app if using the newer
    // common control for XP visual styles
    IF WinDLLVersion{"COMCTL32"}:major >= 6
        
        // Find the theme DLL
        hModule := GetModuleHandle(PSZ("uxtheme.dll"))
        IF ! hModule == NULL_PTR
            
            // Find the function we need
            hFunc := GetProcAddress(hModule,PSZ("EnableThemeDialogTexture"))
            IF ! hFunc == NULL_PTR
                dwFlags := ETDT_ENABLETAB
    
                nCount := SELF:tabcount
                
                //
Go through all tab pages
                FOR i := 1 UPTO nCount
                    oPage := SELF:GetTabPage(i)
                    h := oPage:handle()
                    // Call the function
                    PCALL(hFunc,h,dwFlags)
                NEXT
            
            ENDIF
        
        ENDIF
    
    ENDIF

WinDLLVersion 

CLASS WinDLLVersion
    INSTANCE DLLName AS STRING
    PROTECT MajorVersion AS DWORD
    PROTECT MinorVersion AS DWORD
    PROTECT Build AS DWORD
    PROTECT PlatformId AS DWORD
ACCESS Build CLASS WinDLLVersion
    RETURN SELF:Build

ASSIGN DLLName(cDLL) CLASS WinDLLVersion
    LOCAL hDLL AS PTR
    LOCAL hFunc AS PTR
    LOCAL pVersionInfo AS _WINDLLVERSIONINFO

    SELF:DLLName := cDLL

    IF ! (hDLL := GetModuleHandle(PSZ(cDLL))) == NULL_PTR
        IF ! (hFunc := GetProcAddress(hDLL,PSZ("DllGetVersion"))) == NULL_PTR
            pVersionInfo := MemAlloc(_sizeof(_winDLLVERSIONINFO))
            pVersionInfo.cbSize := _sizeof(_winDLLVERSIONINFO)
            IF PCALL(hFunc,pVersionInfo) == 0
                SELF:MajorVersion := pVersionInfo.dwMajorVersion
                SELF:MinorVersion := pVersionInfo.dwMinorVersion
                SELF:Build := pVersionInfo.dwBuildNumber
                SELF:PlatformId := pVersionInfo.dwPlatformId
            ENDIF
            MemFree(pVersionInfo)
        ENDIF
    ENDIF
    
METHOD init(cDLL) CLASS WinDLLVersion
    SELF:DLLName := cDLL
    
ACCESS IsWinNT() CLASS WinDLLVersion
    RETURN SELF:PlatformId == DLLVER_PLATFORM_NT

ACCESS Major CLASS WinDLLVersion
    RETURN SELF:MajorVersion

ACCESS Minor CLASS WinDLLVersion
    RETURN SELF:MinorVersion

ACCESS Platform CLASS WinDLLVersion
    RETURN SELF:PlatformId
STRUCTURE _winDLLVERSIONINFO
    MEMBER cbSize AS DWORD
    MEMBER dwMajorVersion AS DWORD
    MEMBER dwMinorVersion AS DWORD
    MEMBER dwBuildNumber AS DWORD
    MEMBER dwPlatformId AS DWORD

DEFINE DLLVER_PLATFORM_NT              := 0x00000002      // Windows NT

DEFINE DLLVER_PLATFORM_WINDOWS         := 0x00000001      // Windows 9x

Enable/Disable Visual Styles

There are two ways the user can override the XP visual styles for an application:

1. Right click on the EXE. Select Properties. Go to the Compatibility tab. One of the settings there is "Disable visual styles".

2. From the All Programs menu select Accessories and run the Program Compatibility Wizard.

If you want to programmatically control the visual style you can use call the function SetThemeAppProperties. To disable visual styles call it like this:


SetThemeAppProperties(_not(_or(STAP_ALLOW_NONCLIENT,STAP_ALLOW_CONTROLS,;
                                   STAP_ALLOW_WEBCONTENT)))

To enable visual styles call it like this:


SetThemeAppProperties(_or(STAP_ALLOW_NONCLIENT,STAP_ALLOW_CONTROLS,;
                                   STAP_ALLOW_WEBCONTENT))

The easiest thing to do is to call this at the start of the app before any windows are used - this setting effects windows that are created after the call.

The code for SetThemeAppProperties, and the DEFINEs, are below.

If you only want to turn off visual style for an individual control you can use SetWindowTheme(). This function receives the handle of the window/control, the name of the application, and a string of class ids. To turn off the theme for the window the last two parameters need to be empty Unicode strings. So to turn off the theme for a tab control you would do something like this:


oUni := Unicode{""}
SetWindowTheme(SELF:oDCTabControl1:handle(),oUni:str,oUni:str)

The code for SetWindowTheme and Rod da Silva's Unicode class below.

FUNCTION SetThemeAppProperties(dwFlags AS DWORD) AS LOGIC
    LOCAL hModule AS PTR
    LOCAL hFunc AS PTR
    LOCAL lResult AS LOGIC
    
    // Check if the app if using the newer
    // common control for XP visual styles
    IF WinDLLVersion{"COMCTL32"}:major >= 6
        
        // Find the theme DLL
        hModule := LoadLibrary(PSZ("uxtheme.dll"))
        IF ! hModule == NULL_PTR
            
            // Find the function we need
            hFunc := GetProcAddress(hModule,PSZ("SetThemeAppProperties"))
            IF ! hFunc == NULL_PTR
                lResult := PCALL(hFunc,dwFlags) == 0
            ENDIF
        
        ENDIF
    
    ENDIF

    RETURN lResult
DEFINE STAP_ALLOW_CONTROLS := 0x02
DEFINE STAP_ALLOW_NONCLIENT := 0x01
DEFINE STAP_ALLOW_WEBCONTENT := 0x04
FUNCTION SetWindowTheme(hWnd AS PTR, hAppName AS PTR, hIdList AS PTR) AS LONGINT
    LOCAL hModule AS PTR
    LOCAL hFunc AS PTR
    LOCAL liReturn AS LONGINT
    
    // Check if the app if using the newer
    // common control for XP visual styles
    IF WinDLLVersion{"COMCTL32"}:major >= 6
        
        // Find the theme DLL
        hModule := LoadLibrary(PSZ("uxtheme.dll"))
        IF ! hModule == NULL_PTR
            
            // Find the function we need
            hFunc := GetProcAddress(hModule,PSZ("SetWindowTheme"))
            IF ! hFunc == NULL_PTR
                liReturn := LONG(_CAST,PCALL(hFunc,hWnd,hAppName,hIdList))

            ENDIF
        
        ENDIF
    
    ENDIF

    RETURN liReturn
CLASS Unicode    
    PROTECT pUnicodeBuffer    AS PTR
    PROTECT iSLen           AS LONGINT
    DECLARE ACCESS Str
    DECLARE ACCESS SLen

METHOD Axit CLASS Unicode
    MemFree( pUnicodeBuffer )
    RETURN SELF

METHOD INIT( usAnsiStr ) CLASS Unicode
    LOCAL iNumUnicodeChars    AS INT    

    IF ! IsString( usAnsiStr )
        usAnsiStr := NULL_STRING
    ENDIF

    // Allocate a buffer large enough for equivalent Unicode string	
    // At most twice as big (note: + 1 for trailing Nul terminator)
    iNumUnicodeChars := 2 * (SLen( usAnsiStr ) + 1)
    pUnicodeBuffer := MemAlloc( iNumUnicodeChars )
    IF pUnicodeBuffer != NULL_PTR
        MemClear ( pUnicodeBuffer, iNumUnicodeChars )
        RegisterAxit( SELF )    // Register Axit to deallocate memory

          // Convert usAnsiStr to Unicode
        iSLen := MultiByteToWideChar( CP_ACP, 0, String2Psz(usAnsiStr), ;
                                   -1, pUnicodeBuffer, iNumUnicodeChars )
   ELSE    // Memory allocation failed
        //GenComError( ResultFromScode( ErrorCodes( ;
        //        FACILITY_WIN32, WIN32_E_OUTOFMEMORY ) ) )	
   ENDIF

   RETURN SELF

ACCESS SLen AS LONGINT PASCAL CLASS Unicode
RETURN iSLen

ACCESS Str AS PTR PASCAL CLASS Unicode
RETURN pUnicodeBuffer