Thursday, December 25, 2008

Delphi 2009

Installed Delphi 2009 a week ago and have been trying to update my code. The big change is Unicode support. All strings are now Unicode strings by default so there are a lot of instances that need reassigning to AnsiString. That was not too difficult.

My next attempt was to use 32 bit images in an ImageList. Not as easy as expected. I tried to load PNG images but the transparency did not come out very well. There was a faint grey border around the images, as if the semi-transparent pixels were grey instead of semi-transparent (fully transparent pixels were OK). After many hours of experimenting I found out that 32 bit bitmaps work perfectly. So now I use Inkscape to draw the icons, export to a PNG file, then use Pixelformer to convert from PNG to 32 bit BMP (using A8R8G8B8 format), then load the BMPs into the TImageList (which is set to 32 bits) and assign clNone as the transparent color. Pixelformer is a nice painting program that handles transparency very well and is perfect for this job.

The alpha-channel images can be used on TButtons as well. Unfortunately, they do not work on TSpeedButtons. So I thought of changing all TSpeedButtons to TButtons, but TButtons do not have a Down state. I searched the web and found out I could use the BN_SETSTATE message to make a button seem depressed. The problem is, the button does not stay depressed: as soon as you move the focus away, the button pops up again. This strange phenomenon was partly explained by Dennis Martin in this web page. It seems that a Click event is fired when the button looses focus. Dennis has a solution, but I wanted to create a component to emulate a TSpeedButton. I came up with a different way. Here is the code (feel free to use and share):

//TdhStickyButton, stays down when Down is true.
//intercepts Kill_Focus message to set a flag that shows
// that it is down and was just killed (lost focus).
//intercepts WM_Command, which receives a Click message,
// and only sends it along if it was not just killed in a down state.
// also, resets temporary flag and sets the Down state appropriately
// (otherwise it reverts automatically).
TdhStickyButton = class(TButton)
FDDown: Boolean;
FJustKilledWhenDown: Boolean;
procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
procedure CNCommand(var Message: TWMCommand); message CN_COMMAND;
procedure SetDownState(Value: Boolean);
constructor Create(AOwner: TComponent); override;
property Down: Boolean read FDDown write SetDownState default false;

procedure Register;


procedure Register;
RegisterComponents('Additional', [TdhStickyButton]);

constructor TdhStickyButton.Create(AOwner: TComponent);
inherited Create(AOwner);
FDDown := false;

procedure TdhStickyButton.WMKillFocus(var Message: TWMKillFocus);
if FDDown then FJustKilledWhenDown := true else FJustKilledWhenDown := false;

procedure TdhStickyButton.CNCommand(var Message: TWMCommand);
if (Message.NotifyCode = BN_CLICKED) and not(FJustKilledWhenDown) then Click;
SendMessage(self.Handle, BM_SETSTATE, Longint(FDDown), 0);
FJustKilledWhenDown := false;

procedure TdhStickyButton.SetDownState(Value: Boolean);
FDDown := Value;
SendMessage(self.Handle, BM_SETSTATE, Longint(FDDown), 0);

A StickyButton is used like a regular button. If you want to show it down, just set its 'Down' property to true. The button will not toggle between down and up automatically, you have to do it with code. Also, if you attach an Action to the button, it will not toggle together with the Action's 'checked' property, as SpeedButtons do; again, you have to do it with code.

1 comment:

  1. Anonymous3:45 PM

    It seems I'm struggling with the ImageList + PNG problem you have described, but your solution doesn't work for me. I have described this problem in great detail here: I would be gratful if you could help me by either responding to that post or writing me at aionelATaionelDOTnet. Thank you.