Archive for the ‘glass’ Category.

A toolbar that works on glass

While redesigning JSDialog Creator I decided to spruce up the user interface by taking advantage of the glass feature available in recent operating systems.

I wanted to have a toolbar at the top and the ability to change active pages at the bottom of the screen with no other visual distractions at the bottom. Initially for the toolbar I tried the TToolbar component. Unfortunately that control looks awful when double buffered (and not on glass), and looks just as bad when placed on glass.

Here are some captures showing the various issues with TToolbar rendering on glass.

TToolbar on glass
TToolbar on glass
TToolbar on glass
TToolbar on glass

What to do?

The solution is actually contained in the VCL already, it just hasn’t been surfaced for you. The Quick Access Toolbar of the TRibbon component paints correctly so we’ll take advantage of this.

First we need to create a new TActionToolbar descendant. For my use I’ve called it TJSActionToolbar, you can call it whatever you want. Only the WM_Paint message needs to be handled in this new class. Here is the implementation for the toolbar, but it isn’t the entire implementation (unfortunately).

unit JSActionToolbar;

interface

uses
  ActnCtrls, Messages;

type
  TJSActionToolbar = class(TActionToolbar)
  protected
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  end;

implementation

uses
  Windows, Classes, Graphics, Controls, uxTheme;

{ TJSActionToolbar }

procedure TJSActionToolbar.WMPaint(var Message: TWMPaint);
var
  DC, MemDC: HDC;
  PS: TPaintStruct;
  PaintBuffer: HPAINTBUFFER;
  LBlackBrush: HBRUSH;
begin
  if not (csDesigning in ComponentState) and DoubleBuffered and (csGlassPaint in ControlState) then
  begin
    DC := BeginPaint(Handle, PS);
    try
      PaintBuffer := BeginBufferedPaint(DC, PS.rcPaint, BPBF_COMPOSITED, nil, MemDC);
      if PaintBuffer <> 0 then
        try
          LBlackBrush := CreateSolidBrush(ColorToRGB(clBlack));
          FillRect(MemDC, ClientRect, LBlackBrush);
          DeleteObject(LBlackBrush);
          Perform(WM_PRINTCLIENT, WParam(MemDC), PRF_CLIENT);
        finally
          EndBufferedPaint(PaintBuffer, True);
        end;
    finally
      EndPaint(Handle, PS);
    end;
  end
  else
    inherited;
end;

end.


The problem is that the Quick Access Toolbar doesn’t allow captions for the commands, so painting text wasn’t an issue, where in this case it might be. To address this we need to add a couple of if statements into the ActnMan unit. Don’t worry, they are simple changes and easy to include.

The easiest way to make VCL unit changes is to copy the unit into the project folder you are going to be using it in. This is easily done with the File Actions expert included in XE Plus Pack, but I’ve got time to wait for you to do it manually………….

We need to paint on glass, which means using the specific theme DrawText method.

To do this I created a new local method called DrawOnGlass. Here it is:

procedure DrawOnGlass(ADC: HDC; const AText: UnicodeString; var ATextRect: TRect;
  ATextFlags: Cardinal; const AGlowSize: Integer; const AFontColor: TColor);
var
  Options: TDTTOpts;
  LDetail: TThemedElementDetails;
begin
  FillChar(Options, SizeOf(Options), 0);
  Options.dwSize := SizeOf(Options);
  Options.dwFlags := DTT_TEXTCOLOR or DTT_GLOWSIZE or DTT_COMPOSITED;
  if ATextFlags and DT_CALCRECT = DT_CALCRECT then
    Options.dwFlags := Options.dwFlags or DTT_CALCRECT;
  Options.crText := ColorToRGB(AFontColor);
  Options.iGlowSize := AGlowSize;
  LDetail := ThemeServices.GetElementDetails(teEditTextNormal);
  DrawThemeTextEx(ThemeServices.Theme[teEdit], ADC, LDetail.Part, LDetail.State, AText, Length(AText), ATextFlags, ATextRect, Options);
end;


Place this method above the TCustomActionControl.DrawText method in the ActnMan.pas unit. This code has been lifted from the TCustomLabel.DoDrawThemeTextEx method in the VCL, and reformatted to how I (mostly) format my code.

When glass was added (or the version after) a new ControlState was added to the VCL. This control state is csGlassPaint. If a control is to be painted on glass, the controls ControlState property includes csGlassPaint. This makes it easy to identify when we need to call the new DrawOnGlass method, instead of the Windows.DrawText method that is currently done.

Below is the updated version of the TCustomActionControl.DrawText method:

procedure TCustomActionControl.DrawText(var ARect: TRect; var Flags: Cardinal;
  Text: string);
begin
  if csGlassPaint in ControlState then
    DrawOnGlass(Canvas.Handle, Text, ARect, Flags, 5, Canvas.Font.Color)
  else
    Windows.DrawText(Canvas.Handle, Text, Length(Text), ARect, Flags);
end;


The same if statement needs to also be added to the TCustomActionControl.DrawShadowedText method in two places, to make sure disabled command text is displayed correctly.

There is one final step that is required to have this work. When you select the style on the TActionManager component, it MUST be one of the office styles. If you select a non-office style, the controls won’t paint correctly. Since the office styles use a 32-bit bitmap skin to paint elements, it works fine on glass.

NOTE: The only difference between the selected styles is the used font color. The hot, pressed, checked, down states are the same for each style.

If you’ve done everything correctly, it should look something like the images below.

TToolbar on glass
TToolbar on glass

Disclaimer: I’ve tested this under the following scenario – Large Normal Buttons. I don’t expect issues with button based items, but some of the more complex command styles available may have issues.

Summary

For this to work correctly remember the following points:

  1. Create a new TActionToolbar descendant that handles the WM_PAINT message
  2. Copy the ActnMan.pas unit to your projects folder
  3. Add the DrawOnGlass method to the unit
  4. Modify the TCustomActionControl.DrawText and TCustomActionControl.DrawShadowText methods to check and use the new draw on glass method
  5. When using the new toolbar in an application, make sure the Action Manager is using an office based style

When I get a chance (motivated), I’ll add a QC entry and perhaps this might be included in a future release, without this workaround being needed.

Spacely – Glass revisited

Jeremy (not me) mentioned in a comment about black text not appearing on a glass enabled form. This is not an issue with Spacely.

Here are a couple of screen shots:


I’ve got a couple of other things about Spacely I want to mention in the coming days.

Spacely – Glass

I have been given permission by CodeGear to discuss some of the new features and improvements in Spacely, the next release of Delphi. When released, Spacely will be known as Delphi 2007 for Win32.


One new feature added to the Spacely IDE is support for Glass. It is important to understand that Glass is only available under Vista when aero is active.

TForm now has a published property called GlassFrame. The GlassFrame property is a TGlassFrame which contains the following properties.

Bottom – Number of pixels from the bottom covered in glass

Enabled – The glass feature must be enabled before it is visible

Left – Number of pixels from the left covered in glass

RightNumber of pixels from the right covered in glass

SheetOfGlass – When true, the entire form is glass

Top – Number of pixels from the top covered in glass

Here is Spacely running under Windows XP. This form has the Glass feature enable around the edges.

As you can see in the screen shot, when glass is enabled the form area that will appear as glass under Vista is drawn with diagonal pattern.

Running this sample application under Windows XP, displays as a normal form. When run under Vista, it displays like the following screen shot.

The large version of the first screen shot gives clues to two other changes to the IDE for Spacely. Do you see what they are?