Skip to content

Skinning Windows Applications

The cCJSkinFramework class wraps and extends Codejock Software’s COM Xtreme SkinFramework package, allowing DataFlex developers to transform the appearance of their application by applying a skin to it. An application can take on a completely new appearance with as little as four lines of code. Skins are applied by selecting a skin file. These skin files are standard “Visual Style” files (.msstyles). A few of these skin files are provided with the skin framework, and many others can be downloaded from the Internet. In addition, skin editors are available to create your own skins.

Codejock SkinFramework Overview

The Codejock SkinFramework was designed with the Windows Visual Style architecture in mind. Two types of skin files can be used: the Codejock .cjstyles files and the standard Microsoft .msstyles files. You can use any *.msstyles file from any Visual Style developed for use with Windows XP or later themes.

How To Use Visual Styles

To use Microsoft Visual Styles with the SkinFramework, you will first need to download a visual style or use one of the operating system's predefined Visual Styles. Visual Styles are typically located under the Windows directory at “C:\Windows\Resources\Themes” (search your computer for *.msstyles). Additionally, many Visual Styles can be found on the web, some of these for free. In either case, you simply need to copy the .cjstyles or the .msstyles file into your workspace’s skin default directory, and the file is ready to be used.

How do I find Visual Styles?

Searching for Visual Styles

Application Skinning

Application skinning is supported by creating a single desktop skinning object. If a skin file (psSkinFile) and a skin section (psSkinIni) are defined inside your skin object, the skin/section will be applied to all visual objects in your application.

The skinning object normally requires that a cApplication object be defined first. We recommend that the skinning object be located directly below the application object.

// First create a single application object
Object oApplication is a cApplication
:
End_Object

// Then create a single SkinFramework object
Object oSkin is a cCJSkinFramework
    Set psSkinFile to "WinXP.Luna.cjstyles"
    Set psSkinIni to "NormalHomeStead.ini"
End_Object

The above sample represents how most developers will use skins. They will:

  • Select a skin they want to use.
  • Ensure that the skin file is copied into their workspace’s programs directory.
  • Create the skin object and set the psSkinFile and psSkinIni properties.

Dynamic Skin Changes

The skin object can also be used to change skins dynamically at runtime. This is accomplished by changing the psSkinFile and psSkinIni property values and then calling ApplySkin. If you use this technique, you will probably be doing this within some other object.

Procedure SetANewSkin String sFile String sIni
    Set psSkinFile of ghoSkinFramework to sFile
    Set psSkinIni of ghoSkinFramework to sIni
    Send ApplySkin of ghoSkinFramework
End_procedure

The ghoSkinFramework variable is a global handle to your skin object and is described below.

Be aware that there is a trade-off between applying a static skin and dynamically setting skins. If you, the developer, choose the skin, you can use a carefully chosen skin to give your application a distinct and professional look. Dynamically setting skins usually implies that you are allowing the user to select their skin. If you allow the user to choose a skin, you lose a distinct look for your application. In return, you gain an exciting new configuration option. Before deciding what direction you want to go, you may wish to look around and see what types of applications are using skinning to brand their products and what types of applications offer skinning as an end-user feature.

Skin File Management

The application framework expects skin files to be placed in your workspace's programs directory. When psSkinFile is defined with a relative name, which is recommended, the file will be located relative to the default skin directory. This is the simplest way to work with skin files. If you want a skin file to be available to an application, copy it to the workspace programs directory. You can then access that file by assigning its relative name to psSkinFile.

When a relative name is used in psSkinFile, the qualified file name is determined by calling SkinQFile. SkinQFile determines the default skin directory by calling the SkinPath function. In advanced cases, SkinQFile and SkinPath can be augmented to change the default search paths or to create multiple search paths.

We recommend that you do not change the default location of the skin directory. The Studio’s property panel provides a skin selector dialog that requires skin files to be located in this default directory. When used as expected, it is very easy to set the psSkinFile and psSkinIni properties from the Studio.

Saving and Loading Skin Preferences

If you allow your users to select their own skins, you may want to be able to save and load these skin preferences. Setting the pbLoadPreference property to true enables this capability. A cApplication object is required to save and load preferences. In addition, the application object’s pbPreserveEnvironment property must be set to true.

The loading and saving of preferences can be customized by augmenting the LoadSkinPreference and SaveSkinPreference methods.

Using Skin Object Without Application Object

The skinning object normally requires that a cApplication object is created and that it is created before the skin object. We recommend that the skinning object be located directly below the application object. The skin object uses the application object to determine where the default directory is located and it uses it to load and save skin preferences. If an application is not available to provide this information, an error will be raised.

It is possible to use the skin object without an application object. There are two ways to do this:

  1. The application object will not be needed if psSkinFile is set to a fully qualified name and pbLoadPreference is set to false.
  2. The functionality required of the application object can be replaced with your own custom code. The methods ApplySkin, LoadSkinPreference, and SaveSkinPreference all make calls to the application object. You could override these methods and provide an alternative way of performing these functions. If you are going to do this, you will want to study the existing code for these methods.

Global Skin Framework Handle

The application framework maintains a global handle named ghoSkinFramework. This handle contains the object ID of the skin object. It will be 0 if the skin object does not exist. This can be used by other parts of your program to communicate with the skin object. It can also be used to test if a skin object exists.

Procedure SetANewSkin String sFile String sIni
    If ghoSkinFramework Begin
        Set psSkinFile of ghoSkinFramework to sFile
        Set psSkinIni of ghoSkinFramework to sIni
        Send ApplySkin of ghoSkinFramework
    End
End_procedure

Enumerating Available Skins

If you allow the dynamic selection of skins, you will need a way to know what skin files are available and what skin sections are available for each skin file. The function EnumerateSkins will do this for you. It will search a directory for all skin files and then search each skin file for its section names, returning an array with all of this information. It can search your default skin directory or any directory you choose. This makes it easy to make new skins available to an application. Simply copy the skin file into the appropriate directory, and the skin enumeration function will find it.

Skin Enumeration View

A special view named SkinEnumeration.vw has been created in the system pkg directory. This view, which can be added to any application that contains a skin object, can be used to display and select skins from the default skin directory.

Adding this view is all that is required to support dynamically changing skins. In addition, it can be used as a template for building your own custom selector. It shows you how to use most of the skinning interfaces such as ghoSkinFramework, EnumerateSkins, ApplySkin, psSkinFile, and psSkinIni.

Custom Window Class Mapping

The skin framework class requires that all non-standard window classes be mapped to known window classes. The non-standard classes are then skinned using the rules from the known window classes. All of the DataFlex window classes are mapped within a private event named OnAddVDFWindowClasses. In most cases, this provides all class mappings required to support skinning.

If you are using custom window classes in your application, most likely Active/X classes, you are going to need to apply this mapping yourself. This mapping should be placed inside of OnAddCustomWindowClasses. Refer to this event’s documentation for more detailed information about mapping custom controls.

Client Area Sizing Support

When the Codejock skinning control applies a new skin, it maintains the size of the outer window rectangle. If the skin’s border width or caption bar width changes, the client area size will change. This may result in child objects no longer looking correct. If the client area gets smaller, some controls may be clipped at the bottom and right sides. If the client area gets larger, the bottom and right sides will appear too big. If anchoring is used with child controls, the results will be unpredictable. Sometimes anchoring corrects these sizing issues, while other times it makes them worse. The bigger the change in border and caption widths, the bigger the problem with changed client area size.

Client area sizing support is added to appropriate objects (views, modal panels, etc.). Rather than maintaining the outer window size, the client area size is kept constant. This allows applications to port across different versions of Windows without creating sizing problems.

Unfortunately, the Codejock skinning control does not maintain client area sizing. Special code has been added to the DataFlex framework to maintain client area sizing when skins are applied. This works with both statically and dynamically applied skins. This makes your dialogs look much better with skins. It also allows you to design your views without needing to worry about what kind of skin will be applied to it. This special code accommodation is only applied to dialogs that have their pbSizeToClientArea property set to true. When true, client area size will always be maintained, even with skins.

cCJSkinFramework Interface

The cCJSkinFramework’s interface has intentionally been kept simple. Skinning is meant to be applied to an entire application, and it works best if a skin is applied before any visual objects are paged. The cCJSkinFramework class is based on the cCJComSkinFramework class, which is a one-to-one wrapper of the COM class. This COM class contains additional interfaces that can be used to perform advanced tasks. While these COM interfaces are available to you, use them carefully.

The COM interface allows you to selectively remove skinning support for individual window classes, add and remove skin support for individual objects, and apply different kinds of skinning rules (apply skin colors, apply skin borders, etc.). Most of these interfaces are there to provide workarounds when things go wrong. Many of these workarounds were created for products other than DataFlex. These should not be thought of as “features” and therefore they should probably not be changed.

Additional Considerations

The Codejock skin control does not skin standard Windows menus. It does properly skin menus created with the Codejock Commandbar system. Therefore, if you are going to skin your application, you will want to use the cCJCommandBarSystem for your menu system (which you would probably want to do anyway).

RunProgram

Executables accessed with the RunProgram will not skin the calling program. The executable is run as a separate executable and must be skinned separately.

Chain Wait

Skinning and Chain Wait are not compatible. If you are still using Chain Wait and wish to use skins, you will need to find a replacement for Chain Wait. You can use RunProgram wait or you can move the chained-waited code directly into your main application.

EnableThemeTextureDialog

Codejock supports a method called ComEnableThemeTextureDialog, which allows tab pages, and only tab pages, to use a textured visual style. This method is currently not supported in DataFlex. This technique, which requires that each tab page sends this message to the skinning object, disables the setting of background color, does not work well with child dialogs (e.g., Group and container3D), and creates numerous painting issues.

The Old StatusBar / Sentinel Class

The StatusBar class, which is obsolete, invokes a status dialog by running a separate program (by default, Sentinel.exe). This separate program is not skinned. The standard status panel object, which is based on cStatusPanel, is skinned.

The Winprint2 Report Viewer

The Winprint2 Report Viewer is currently not skinned.