[返回]
计算机世界2001年第6期

在 Delphi中定制提示窗口

福建省古田县水电局 朱丽莺

在程序设计中,有时我们可能需要一种特定的提示窗口,但是系统提供的简单的提示窗口却不能满足需要,那么只能我们自己编写。本文介绍如何在Delphi中定制自己的提示窗口。

分析问题

应用程序在显示提示窗口之前,首先会创建一个提示窗口类(THintWindowClass)的全局实例(HintWindowClass),即提示窗口对象(THintWindow)。我们可以创建提示窗口的子类,然后在程序启动时,将提示窗口类的全局变量设置为派生的提示窗口即可。

应用程序启动提示窗口时,提示窗口的函数调用顺序如下:首先调用CalcHintRect,用它来计算提示窗口的大小;然后,调用ActivateHint计算提示窗口的大小和位置,在该函数调用过程中会触发WM_NCPaint消息重画窗口客户区域;最后调用Paint函数,在屏幕上显示想要的提示内容。

在派生提示窗口子类时,可以重载THintWindow对象的方法和消息处理函数,此外,还可以通过响应WM_ERASEBKGND消息去掉背景重画的过程。

本文定制的提示窗口的效果如图1所示:

类定义

下面是一个派生的提示窗口类的定义,它的目的是创建一个圆角矩形的文字框加上一个三角形的尖角的提示窗口(见图1)。

TxyHintWindow = class(THintWindow)

private

FActivating: Boolean;

FHDirection: Boolean;

FVDirection: Boolean;

procedure WMNCPaint(var Message: TMessage); message WM_NCPAINT;

procedure WMEraseBkgnd(var Message:

TWmEraseBkgnd); message WM_ERASEBKGND;

protected

procedure Paint; override;

public

constructor Create(AOwner:TComponent);override;

procedure ActivateHint(Rect: TRect; const AHint: string); override;

function CalcHintRect(MaxWidth: Integer; const AHint: string;AData: Pointer): TRect; override;

end;

下面两个私有成员用来确定提示窗口的尖角方向,分别用来表左右和上下方向:

FHDirection: Boolean;

FVDirection: Boolean;

Controls单元中的THintWindow对象部分成员定义如下:

THintWindow = class(TCustomControl)

private

FActivating: Boolean;

procedure WMNCPaint(var Message:TMessage); message WM_NCPAINT;

protected

procedure Paint; override;

public

constructor Create(AOwner: TComponent);

override;

procedure ActivateHint(Rect: TRect; const

AHint: string); virtual;

function CalcHintRect(MaxWidth: Integer; const AHint: string;AData: Pointer): TRect; virtual;

end;

编程实现

下面是派生窗口类具体实现时的部分代码及其简单说明:

//设置提示窗口的背景颜色和字体大小

constructor TxyHintWindow.Create(AOwner:

TComponent);

begin

inherited Create(AOwner);

Color := $80FFFF;

Canvas.Font.Size := 14;

end;

//去掉窗口的背景重画的过程

procedure TxyHintWindow.WMEraseBkgnd(var

Message: TWmEraseBkgnd);

begin

Message.Result := 1;

end;

//显示提示窗口的内容

procedure TxyHintWindow.Paint;

var//最后调用该方法

R,

ArrowR, //尖角所在的矩形

TextR: TRect;//显示文字的矩形

RectRgn, //显示文字的圆角矩形区域

TPolyRgn, //尖角的多边形区域

ShadowRgn:HRgn; //阴影区域

AP:array[0..2] of TPoint;//尖角的顶点坐标

begin

R := ClientRect;

Inc(R.Left, 2);

Inc(R.Top, 2);

Canvas.Font.Color := clInfoText;

//额外高度为22

Canvas.Brush.Color := $80FFFF;

TextR := R;

ArrowR := R;

if FVDirection then

begin 向上

Inc(TextR.Top, 22);

ArrowR.Bottom := TextR.Top;

end;

else //向下

begin

ArrowR.Top := ArrowR.Bottom - 22;

TextR.Bottom := ArrowR.Top;

end;

if FHDirection then

begin //向左

if FVDirection then

begin

AP[0] := ArrowR.TopLeft;

AP[1].x := ArrowR.Left + 10;

AP[2].x := ArrowR.Left + 25;

AP[1].y := ArrowR.Bottom;

AP[2].y := ArrowR.Bottom;

end;

else

begin

AP[0].x := ArrowR.Left;

AP[0].y := ArrowR.Bottom;

AP[1].x := ArrowR.Left + 10;

AP[2].x := ArrowR.Left + 25;

AP[1].y := ArrowR.Top;

AP[2].y := ArrowR.Top;

end;

end;

else

begin //向右

if FVDirection then

begin

AP[0].x := ArrowR.Right;

AP[0].y := ArrowR.Top;

AP[1].x := ArrowR.Right - 10;

AP[2].x := ArrowR.Right - 25;

AP[1].y := ArrowR.Bottom;

AP[2].y := ArrowR.Bottom;

end;

else

begin

AP[0] := ArrowR.BottomRight;

AP[1].x := ArrowR.Right - 10;

AP[2].x := ArrowR.Right - 25;

AP[1].y := ArrowR.Top;

AP[2].y := ArrowR.Top;

end;

end;

//创建显示文字的圆角矩形区域

RectRgn := CreateRoundRectRgn(TextR.Left,

TextR.Top,TextR.Right,TextR.Bottom,10,10);

//创建三角形区域

TPolyRgn := CreatePolygonRgn(AP,3,WINDING);

//组合两个区域,使用RGN_OR操作

CombineRgn(RectRgn,RectRgn,TPolyRgn,

RGN_OR);

//填充区域

FillRgn(Canvas.Handle,RectRgn,Canvas.Brush.

Handle);

DeleteObject(RectRgn);

DeleteObject(TPolyRgn);

//在文字显示矩形中绘制提示文字

DrawText(Canvas.Handle, PChar(Caption), -1,

TextR,DT_VCENTER or DT_CENTER or DT_NOPREFIX or DT_WORDBREAK or DrawTextBiDiModeFlagsReadingOnly);

end;

procedure TxyHintWindow.ActivateHint(Rect: TRect; const AHint: string);

begin

//初始化尖角方向

FHDirection := True;

FVDirection := True;

FActivating := True;

try

Caption := AHint;

Inc(Rect.Bottom, 4);

UpdateBoundsRect(Rect);

//决定尖角方向

if Rect.Left < Screen.DesktopLeft then

begin

Rect.Left := Screen.DesktopLeft;

end;

if Rect.Top+Height >Screen.DesktopHeight then

begin

Dec(Rect.Top, Height);

FVDirection := False; //向下

end;

if Rect.Left +Width >Screen.DesktopWidth then

begin

Dec(Rect.Left, Width);

FHDirection := False; //向右

end;

if Rect.Bottom < Screen.DesktopTop then

begin

Rect.Bottom := Screen.DesktopTop;

FVDirection := False;

end;

//设定窗口位置以及大小

SetWindowPos(Handle, HWND_TOPMOST,

Rect.Left, Rect.Top, Width, Height,

SWP_SHOWWINDOW or SWP_NOACTIVATE);

Invalidate;

finally

FActivating := False;

end;

end;

function TxyHintWindow.CalcHintRect(MaxWidth:

Integer; const AHint: string;AData: Pointer): TRect;

begin

//计算矩形区域

Result := Rect(0,0,Canvas.TextWidth(AHint),Canvas.TextHeight(AHint));

Inc(Result.Right, 22);

Inc(Result.Bottom, 10);

Dec(Result.Top, 15);

//清除上次提示窗口的残余图像

SetWindowPos(Handle, HWND_TOPMOST,

Result.Left, Result.Top, Width, Height,

SWP_HIDEWINDOW or SWP_NOACTIVATE);

Invalidate;

end;

需要注意的是:在计算提示窗口矩形大小时,应该清除上次提示窗口的残余图像。如果去掉这个调用,当窗口上两个控件距离很近时,移动鼠标就会发现多余的背景或者上次显示的残余图像。本例中,我们使用带SWP_HIDEWINDOW或SWP_NOACTIVATE标志的SetWindowPos函数。

另外,在处理WM_NCPAINT消息时,THintWindow绘制了一个有边框矩形,如果不想要这个边框矩形,那么在TxyHintWindow中可以什么也不做(不要继承)。

在Paint方法中,本例中使用区域进行显示,当然,如果愿意的话,也可以使用位图图像来绘制背景。

最后,加入如下所示的单元初始化代码:

initialization

HintWindowClass := TxyHintWindow;

注意:上面这行代码的效果将影响到Delphi的IDE所用到的提示窗口。参见图2和图3:

如果不希望有图2和图3这样的结果,那么,可以另外创建一个新组件,例如,把定制的提示窗口类TxyHintWindow包装起来。可以在新组件的构造函数或Loaded方法中加入如下代码:

procedure TxyHint.Loaded;

begin

if not (csDesigning in ComponentState) then

begin

inherited Loaded;

HintWindowClass := TxyHintWindow;

end;

end;