可以什么按钮都没有,也可以使关闭按钮变灰不能用,但是不能只没有关闭按钮,除非自己画标题栏。
什么按钮都没有的方法:
BorderIcons属性中biSystemMenu设为false。
关闭按钮变灰的方法:
执行下面代码
HMENU hMenu = NULL;
hMenu = GetSystemMenu(this->Handle, false);
EnableMenuItem(hMenu, 6, MF_BYPOSITION | MF_DISABLED);
收藏内容:
窗 体 设 计
11.3 窗 体 特 性
我们曾经对窗体的特性、方法等进行过较为详细的讨论。这里,我们再对两个关于窗体类型及边界类型的窗体特性进行介绍。
设置窗体类型使用特性FormStyle。该特性允许用户有两种选择:一种是普通的SDI窗体——SDI意思是Single Document Interface(单文档接口),另一种是组成MDI应用程序的窗体——MDI的意思是Multiple Document Interface(多文档接口)。
下面列出了FormStyle特性的可能值:
?fsNorma1:窗体是普通的SDI窗口或对话框。
?fsMDIChild:窗体是MDI子窗口。
?fsMDIForm:窗体是MDI父窗口——也就是 MDI应用程序的框架窗口。
?fsStayOnTop:窗体是SDI窗口, 但它总处于其它所有窗口的前面, 除了那些也被设置为“stay-on-top”(留在最前面)的窗口。
设置边框样式使用特性BorderStyle。该特性引用窗体的一个可视化元素TFormBorderStyle,有6个可能值:
?bsSizeable:窗体有标准的粗边框,可以拖动边框来调整窗体的大小。这是缺省的样式。
?bsDialog:窗体有标准的对话框边框,它也是粗的,但不能改变大小。拥有这类边框的窗体也就是对话框。
?bsSingle:窗体有一个细边框而且不能改变大小;它也叫作固定边框。与Windows 3.1不同,Windows 95在细边框与粗边框之间没有显示明显的区别。
?bsToolWindow:窗体采用较细的标题栏(使用较小的字体),而且只有一个最小化Close按钮。这是一种不能改变大小的工具栏的特殊类型。
?bsSizeToolWin:窗体采用较细的标题栏,与上一类型相似,但可以改变大小。
?bsNone:窗体没有边框或任何传统的元素(标题、最大化与最小化按钮、系统菜单)。
11.4 固 定 窗 体
现在,我们来讨论窗体编程的技巧之一──固定窗口的位置。在Delphi的程序设计中,只需将窗体(Form)的特性BorderStyle取值为 bsSizeable,用户就可随意变更窗体的大小和位置的移动。但在应用程序中使用Windows API函数同样可以改变窗体的这个特性值,并且还可以实现其它目的。
当我们在进行窗体移动和大小变更时,在此动作的前一时刻,Windows系统首先向窗体送出WM_WINDOWPOSCHANGING消息。由此消息产生 的WM_WindowPosChanging事件句柄传递来的TWMWindowPosChanging消息记录型的WindowPos,是包含有变更动 作内容的TWindowPos记录的指针。因此,我们可以根据对其内容的变更,来改变动作内容。
type TWindowPosMsg = record
Msg:Cardinal;
Unusrd:Integer;
WindowPos:PwindowPos;//lparam 以下记录的指针
Result:Longint;
end;
该TWindowPos记录包含以下内容:
PWindowPos = ^TWindowPos;
TWindowPos = Packed record;
hwnd:HWND;//发生动作窗体的句柄
hwndInsertAfter:HWND;//变更Z顺序时最上层的窗体句柄
x:Integer;/窗体左端的位置
y:Integer;//窗体上端的位置
cx:Integer;//窗体的宽
cy:Integer;//窗体的高
flags:UINT;//变更的内容
在flags中,与此话题相关的标志如下:
?SWP_NOMOVE——保持现在位置(无视x成员和y成员)
?SWP_NOOWNERZORDER——不变更主窗体Z顺序的位置
?SWP_NOSIZE——保持现在的大小(无视cx成员和cy成员)
?SWP_NOREDEAW——即使有变更,但不重写画面
?SWP_NOZORDER——保持现在Z顺序(无视hwndInsertAfter成员)
据此当我们不想让窗体的大小变更时,只要在TWindowPos记录的flags变量中设置SWP_NOSIZE标志即可。同样不想让窗体移动时,则设置SWP_NOMOVE标志。
为了达到以上目的,首先要处理WM_WINDOWPOSCHANGING消息,修改flags变量的内容。作为窗体的处理过程(WMWindowPosChanging)如下:
{禁止窗体移动、禁止大小变更处理}
porcedure Tform1.WMWindowPosChanging(var Msg:TWMWindowPosChanging);
begin
inherited
if bWinStat [1] then
Msg.WindowPos^.flags := Msg.WindowPos^.flags or SWP_NOMOVE;
if bWinStat [2] then
Msg.WindowPos^.flags := Msg.WindowPos^.flags or SWP_NOSIZE;
end;
此例中使用bWinStat变量(Boolean类型数组),来禁止窗体的移动和大小的修改。
11.5 固定窗体的横宽
这里我们继续详细介绍关于在窗体的大小修改中固定横宽的方法。
?当我们修改窗体的大小或按下最大化按扭时,Window系统向应用程序窗体送出WM_GETMINMAXINFO消息。由于这个消息中包含有窗体尺寸的 最大和最小值,如果我们适当利用此消息,则可以指定窗体的大小范围。首先定义WM_GETMINMAXINFO消息的句柄。
(1)WM_GETMINMAXINFO消息句柄的说明:
type
TForm1 = Class(Tform)
procedure WMGetMinMaxInfo(var Msg:TWMGetMinMaxInfo);
message WM_GETMINMAXINFO;
(2)以TWMGetMinMaxInfo记录为参数:
TWMGetMinMaxInfo = record
Msg:Cardinal;
Unused:Integer;
MinMaxInfo:PMinMaxInfo;
Result:Longint;
end ;
TWMGetMinMaxInfo中的MinMaxInfo是具有关于窗体大小信息的TMinMaxInfo记录的指针。
该记录在Delphi的Windows.Pas文件中是如下说明的:
type
{Struct pointed to by WM_GETMINMAXINFO Iparam}
PMinMaxInfo = ^TMinMaxInfo;
TMinMaxInfo = packed record ptReserved:Tpoint; //预留
ptMaxSize:Tpoint; //最大化时的大小
ptMaxPosition:Tpoint ; //最大化时的位置
ptMinTrackSize:Tpoint ; //尺寸变更时最小尺寸
ptMaxTrackSize:Tpoint ; //尺寸变更时最大尺寸
end;
当按下最大化按扭时,在ptMaxSize和ptMinSize中指定窗体的大小和位置。当拖动窗体框架时,ptMinTrackSize和ptMaxTrackSize确定其变化的最大和最小尺寸。
假如窗体的大小只在从 (100,100) 到 (500,400) 的范围内变化,那么,使用以下处理过程即可完成:
//设置窗体大小变化范围
procedure TForm1.WMGetMinMaxInfo(var Msg:TWMGetMinMaxInfo);
begin
inherited;
Msg.MinMaxInfo^.ptMaxTrackSize:= Point(500,400);
Msg.MinMaxInfo^.ptMinTrackSize:= Point(100,100);
end;
首先调用inherited方法,接着对MinMaxInfo构造体设置初始值,即设置ptMaxTrackSize和ptMinTrackSize的值。
这里并没有对按下最大化按钮时的大小进行设定,仅仅只是对拖动框架的大小的动作进行了限制。因此当按下最大化按钮时,窗体与平常一样占满全部画面。
当我们绝对不想让窗体的尺寸发生变更时,则采用以下处理过程:
inherited;
Msg.MinMaxInfo^.ptMaxSize:= Point(300,200);
Msg.MinMaxInfo^.ptMaxTrackSize:= Point(300,200);
同样,横宽固定,纵长可自由改变的时候,则采用以下处理过程。
inherited;
Msg.MinMaxInfo^.ptMaxSize.x= Width;
Msg.MinMaxInfo^.ptMaxTrackSize.x = Width;
Msg.MinMaxInfo^.ptMinTrackSize.x = Width;
上面代入的是该事件句柄所属的窗体的横宽,因此其窗体的横宽不能变更。
到此从功能上讲,我们已经能够对窗体的尺寸进行控制了。可是,现在你一定会注意到以下的问题:一个是全屏化按钮和图标化按钮的表现方法,另一个是对应尺寸变化的光标形状的变化。
关于全屏化按钮和图标化按钮的使用或不使用的控制方法,在Delphi的程序设计中,可通过直接设定窗体(Form)的BorderIcons特性值来控 制。但是光标形状的变换控制稍微有点麻烦。当光标在窗体框架上时,光标显示为尺寸可变更,但是实际上窗体已经被固定。
为解决此问题生成适当的窗体,而后用WinSignt32(Delphi中含有)或Spy++等Windows监视系统对向该窗体传送的消息进行监视。当 鼠标器在该窗体上移动时,Windows监视系统向窗体传送WM_MOUSEMOVE消息的同时,还传送WM_NCHITTEST消息。当移动和敲击鼠标 时,系统对窗体传送WM_NCHITTEST消息,询问现在鼠标光标在窗体的什么位置,如果传回光标在窗体的框架上或窗体内,系统则相应自动改变光标的形 状。
因此,我们先定义WM_NCHITTEST消息句柄,从该句柄传回值。就是说即使光标在框架上,传回值也不在框架上,给Windows系统一个小骗局。该消息句柄说明如下:
interface
type
Tform1 = class(Tform)
private
bWinStat:array [1..4] of Boolean;
procedure WMNCHitTest(var Msg:TWMNCHitTest);
message WM_NCHITTEST;
该消息句柄的处理过程:
//尺寸变更的光标形状不显示
porcedure Tform1.WMNCHitTest(var Msg:TWMNChitTest);
begin
inherited
//当移动禁止时,则使其上端、左端尺寸不变更
if bWinStat [1] and
(Msg.Result in [ HTTOP,HTLEFT,HTTOPLEFT,HTTOPRIGHT;HTBOTTOMLEFT])
then Msg.Result:= Windows.HTNOWHERE;
//整体窗体尺寸变更禁止时
if bWinStat [2]
?and (Msg.Result in { HTLEFT,HTRIGHT,HTTOP,
HTBOTTOM,HTTOPLEFT,HTTOPRIGHT,HTBOTTOMLEFT,HTBOTTOPMRIGHT])
then Msg.Result:= Windows.HTNOWHERE;
//横宽方向尺寸变更禁止时
if bWinStat [3] and (Msg.ResultIn{
HTLEFT,HTRIGHT,HTTOPLEFT,HTBOTTOM,HTTOPLEFT,HTBOTTOPMRIGHT])
then Msg.Result:= Windows.HTNOWHERE;
end;
单纯就窗体的框架而言,可分为上下左右4条边和4个角共8个模式。当移动禁止时,上端、左端的位置尺寸不得变更。
11.6 无标题栏窗口
现在,我们来讨论窗体编程的另一种技巧──建立无标题栏窗口。
为了使标题栏不显示,我们可以在窗体BorderStyle特性中选取bsNone值即可。但选取bsNone值后,窗体周围什么也没有。当我们只想去掉标题栏时又怎么办呢?
实际上窗体的位置、大小、显示风格等均由Windows API的CreateWindowEx函数所指定,这个API的参数是在Delphi的CreateParams过程中被初始化的。Delphi是依据以 下的TCreateParams记录的值生成窗体的。TCreateParams记录包含窗体的位置、大小以及其他各种特性,在Delphi的 Control.Pas单元中记录的定义为:
TCreateParams = record
Caption: PChar; //标题
Style: DWORD; //窗体的显示风格
ExStyle: DWORD; //窗体的扩展显示风格
X, Y: Integer; //显示位置
Width, Height: Integer; //窗体的大小
WndParent: HWnd; //父窗体
Param: Pointer; //指向窗口生成参数(WM_CREATE消息的 //LParam)的指针
WindowClass: TWndClass; //窗体的类别
WinClassName: array[0..63] of Char; //类别名
end;
该记录中包含窗体的位置、大小和窗体显示风格的标志。因此,一般通过其值的改变来控制生成窗体的位置、大小和窗体显示风格。由于我们要设置窗体初始的风 格,所以在Params.Style变量中设定窗体的风格。又因为该变量是取标志形式,所以使用and或or布尔运算来演算,从而完成各个位的on或 off。
例如,当BorderStyle特性为bsNone时,再给窗体加上可变更大小的粗框架,则对CreateParams方法进行重载。处理过程如下即可:
Procedure TForm1.CreateParams(var Params:TCreateParams);
begin
inherited CreateParams(Params);
if BorderStyle = bsNone then
with Params do Style:= Style or WS_THICKFRAME;
end;
需要注意,一般情况下的继承关系,单纯使用inherited,则派生类的方法即可被调出执行;但继承使用了CreateParams方法时,必须明确按inheritedCreateParams(Params)编制程序。
关于窗体风格标志值用以下定量来指定,这里列举的只是具有代表性的一部分。详情请参考Windows API的CreateWindow的help。
WS_BORDER边框
WS_CAPTION标题栏
WS_CHILD子窗体
WS_HSCROLL水平滚动条
WS_MAXIMIZE全屏化窗体
WS_MAXIMIZEBOX全屏按钮
WS_MINIMIIE图标化窗体
WS_MINIMIZEBOX图标化按钮
WS_SYSMENU系统菜单
WS_THICKFRAME可变更窗体大小的粗边框
WS_VISIBLE初始为可视的窗体
到此,没有标题栏的,大小可以变更的窗体已完成。
此窗体由于没有标题栏所以关闭、全屏化等按钮的操作均不可能,并且想通过拉拖标题栏移动窗体也不可能,下面我们先讨论关于移动的问题。
关于WM_NCHITTEST消息前面已经作了解说。当拖拉标题栏移动窗体时,窗体伴随鼠标器的移动,系统向窗体传送WM_NCHITTEST消息,当确认光标在标题栏上,并同时检测出鼠标器左按钮被按下时,移动成开始状态。
只要光标在窗体,则使窗体的移动成为可能。为此,WMNCHitTest处理过程如下。
porcedure TForm1.WMNCHitTest(var Msg:TWMNCHitTest);
begin inherited; //当标题栏非显示时,光标在窗体领域即可移动
if (BorderStyle = brNone) and
(Msg.Result = HTCLIENT) then
Msg.Result:= HTCAPTION;
end;
当鼠标光标在窗体领域内时,并且无标题栏,WM_NCHITTEST消息传回值为HTCLIENT时,将传回值作为HTCAPTION处理,系统则判断鼠标光标在标题栏上,这样就完成了窗体领域可移动的处理。这里再次给Windows系统一个骗局。
另外,讨论一下标题栏的消除和追加的方法。从而解决关闭、全屏化、图标化的问题。首先想到的方法是在窗体(Form)被单击或双击后,改变 BorderStyle特性的值。就是说对Form的OnClick事件进行定义,创建子程序将bsNone变为bsSizeable。
可是我们测试一下,它并不动作。实际上这是因为在前面为了处理移动,在WMNCHitTest过程中,将窗体领域的返回值变更成了标题栏的返回值,因此系统只判断鼠标光标在标题栏上单击。
要想移动和单击同时起作用需要下一点功夫。当用WinSight32来确认显示正常的窗体返回给系统的消息时,与通常窗体领域返回WM_LBUTTONDOWN消息相对,在标题上单击时返回WM_NCLBUTTONDOWN消息。
这里不使用窗体(Form)的OnClik事件,而是通过WM_NCLBUTTONDOWN消息的处理来得到希望的动作。如下编程即可 :
interface
type
TForm1 = Class(TForm);
:
protected WMNCLButtonDown(var Msg:TWMNCLButtonDown);
message WM_NCLBUTTONDOWN;
implemention //鼠标单击窗体追加标题栏
porcedure TForm1.WMNCLButtonDown(var Msg:TWMNCLButtonDown);
begin
if BorderStyle = bsNone then
borderStyle = bsSizeble;
end;
当然,如果在窗体上配置其他可视构件,从而可以简单地通过对构件的单击来完成以上动作。
11.7 窗体间相互连动
下面我们来讨论两个窗体的设计技巧。设其中一个为主窗体,另一个为子窗体。 在Delphi程序设计中,窗体(Form)的Position特性取值为poDesigned,则窗体的显示位置和大小以特性Left、Top、 Width、Height的取值为依据来决定。这样在创建子窗体时,以获得主窗体的显示位置和大小(主窗体的Left、Top、Width、Height 的特性值)为基准,然后确定子窗体的位置和大小即可。
窗体的初始显示位置,可用CreateParams方法进行设置,之后用重载子窗体(Form1)的CreateParams方法,来改变子窗体的显示位 置和大小。主窗体和子窗体之间是怎样互相引用的呢?首先设置窗体(Form1)的单元文件名为unit1.pas,子窗体(Form2)的单元文件名为 unit2.pas。通常要引用某窗体单元文件的特性和内容时,应该在interface的uses处插入其窗体(Form)的单元名(unit)。本实 例中根据主窗体动态生成子窗体,因此在主窗体(Form1)的interface的uses处插入子窗体的单元名(unit2)。这样主窗体即可引用子窗 体的各种特性值。另一方面要实现子窗体引用主窗体的各种特性值又怎么办呢?由于Pascal语言不允许相互循环引用,即A到B,并且B到A。这时,必须在 imptementation的uses处插入主窗体的单位名(unit1),这样即可对主窗体(Form1)的特性值进行引用。
引用方法如下:
implementation
{$R *.DFM}
uses Unit1;//主窗体单元名
{生成窗体时参考数的初始化}
procedure TForm2.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
//当没有窗体题目时,添加细框
if BorderStyle = bsNone then
Params.Style := Params.Style or WS_THICKFRAME;
//根据主窗体的显示位置和大小决定子窗体的显示位置和大小
with Form1 do
begin
Params.X := Left + Width;
Params.Y := Top;
Params.Height := Height;
Params.Width := 100;
end;
end;
这样即完成了对应主窗体的子窗体在其右侧显示的程序设计。现在当我们移动主窗体时,子窗体并不随之移动,自然,移动子窗体时,主窗体也不会移动。虽然通常的Windows的应用程序则到此为止,但是我们的主题是要讨论连动,下面我们继续介绍这一问题。
首先要搞清楚,怎样把握自身窗体的移动和大小变化。在窗体的移动和大小变化时Windows系统要送出WINDOWSPOSCHANGED消息,为了捕捉此消息,对以下方法(Method)进行说明:
type
Tform1 = class(Tform)
protected
procedure WMWindowPosChanged(vax Msg:TWMWindowPosChanged);
message WM_WINDOWPOSCHANGED;
WM_WINDOWPOSCHANGED消息是在窗体的位置和大小变化后送出的。上面是对WM_WONDOWPOSCHANGED消息句柄的说明,接着是TWMWindowsPosChanged消息记录型和TWindowPos记录内容的定义。
type
TWMWindowPosMsg = record
Msg:Cardind;
Unused:Integer;
WindowPos:PwindowPos;
Resukt:Longint;
end;
PwindowPos = ∧TWindowPos;
TwindowPos = packed record
hwnd:HWND; //自身的窗体句柄
hwndInsertAfeer:HWND;//变更Z的顺序时,最上层窗体句柄
x:Integer//新窗体左端的位置
y:Integer//新窗体上端的位置
cx:Integer//新窗体上端的宽
cy:Integer//新窗体上端的高
flags:UINT//变更内容(移动、大小 、Z顺序等)
end;
下面具体介绍此方法的使用,首先要确认子窗体是否存在,而后确认自身的显示位置,一旦发生变更就使用WindowsAPI的SetWindowPos,以变更子窗体的显示位置和大小。要注意的是只是主窗体发生变更时才使用。
这样即可使子窗体随主窗体的变更而变更。接着我们还希望主窗体随子窗体的变更而变更。以下先是主窗体变更使子窗体变更的处理过程。
const
uFlag = SWP_NOACTIVATE or SWP_NOZORDER or SWP_NOMOVE orSWP_NOSIZE;
//大小变更、位置移动结束通知
porcedure TForm1.WMWindowPosChanged(var Msg:TWMWindowPosChanged);
var Rect:TRect;
x,y,cx,cy:Integer;
uFlag2:UINT;
begin
inherited;
if Form2 <> nil then
//如果子窗体存在
begin
GetWindowRoct(Form2.Handle,Rect); //获取子窗体的位置
With Msg do
begin
x:= WindowPos^.x + WindowPos^.cx; //新x坐标
y:= Window^.y; //新y坐标
cx:= 100; //宽固定在100
cy:= WindowPos^.cy //新高度
end;
uFlag2:= uFlag; //需要移动时
if(Rect.Top <> Y) or(Rect.Left<>X) then
uFlag2:= uFlag2 and(not SWP_NOMOVE);
if(Rect.Bottom -Rect.Top <>cy) or
//需要变更大小时
(Rect.Right -Rect.Left<>cx) then
uFlag2:= uFlag2 and(not SWP_NOSIZE);
if uFlag <> uFlag2 then
//对子窗体(Form)进行变更
setWindowPos(Form2.Handle,0,x,y,cx,cy,uFlag2);
以上处理是从主窗体的新位置来计算子窗体的新位置,然后与现在的子窗体的位置信息进行比较,如果不同则变更子窗体。单纯用Wondow API的SetWindowPos对位置进行变更时,Z顺序和活动窗口(Active Window)发生转换,因此指定SWP_NOACTIVATE标志和SWP_NOZORDER标志,阻止其转换。
从子窗体来变更主窗体的过程大致与上相同。不同之处在于主窗体一定存在,而不需要对其进行检验。
什么按钮都没有的方法:
BorderIcons属性中biSystemMenu设为false。
关闭按钮变灰的方法:
执行下面代码
HMENU hMenu = NULL;
hMenu = GetSystemMenu(this->Handle, false);
EnableMenuItem(hMenu, 6, MF_BYPOSITION | MF_DISABLED);
收藏内容:
窗 体 设 计
11.3 窗 体 特 性
我们曾经对窗体的特性、方法等进行过较为详细的讨论。这里,我们再对两个关于窗体类型及边界类型的窗体特性进行介绍。
设置窗体类型使用特性FormStyle。该特性允许用户有两种选择:一种是普通的SDI窗体——SDI意思是Single Document Interface(单文档接口),另一种是组成MDI应用程序的窗体——MDI的意思是Multiple Document Interface(多文档接口)。
下面列出了FormStyle特性的可能值:
?fsNorma1:窗体是普通的SDI窗口或对话框。
?fsMDIChild:窗体是MDI子窗口。
?fsMDIForm:窗体是MDI父窗口——也就是 MDI应用程序的框架窗口。
?fsStayOnTop:窗体是SDI窗口, 但它总处于其它所有窗口的前面, 除了那些也被设置为“stay-on-top”(留在最前面)的窗口。
设置边框样式使用特性BorderStyle。该特性引用窗体的一个可视化元素TFormBorderStyle,有6个可能值:
?bsSizeable:窗体有标准的粗边框,可以拖动边框来调整窗体的大小。这是缺省的样式。
?bsDialog:窗体有标准的对话框边框,它也是粗的,但不能改变大小。拥有这类边框的窗体也就是对话框。
?bsSingle:窗体有一个细边框而且不能改变大小;它也叫作固定边框。与Windows 3.1不同,Windows 95在细边框与粗边框之间没有显示明显的区别。
?bsToolWindow:窗体采用较细的标题栏(使用较小的字体),而且只有一个最小化Close按钮。这是一种不能改变大小的工具栏的特殊类型。
?bsSizeToolWin:窗体采用较细的标题栏,与上一类型相似,但可以改变大小。
?bsNone:窗体没有边框或任何传统的元素(标题、最大化与最小化按钮、系统菜单)。
11.4 固 定 窗 体
现在,我们来讨论窗体编程的技巧之一──固定窗口的位置。在Delphi的程序设计中,只需将窗体(Form)的特性BorderStyle取值为 bsSizeable,用户就可随意变更窗体的大小和位置的移动。但在应用程序中使用Windows API函数同样可以改变窗体的这个特性值,并且还可以实现其它目的。
当我们在进行窗体移动和大小变更时,在此动作的前一时刻,Windows系统首先向窗体送出WM_WINDOWPOSCHANGING消息。由此消息产生 的WM_WindowPosChanging事件句柄传递来的TWMWindowPosChanging消息记录型的WindowPos,是包含有变更动 作内容的TWindowPos记录的指针。因此,我们可以根据对其内容的变更,来改变动作内容。
type TWindowPosMsg = record
Msg:Cardinal;
Unusrd:Integer;
WindowPos:PwindowPos;//lparam 以下记录的指针
Result:Longint;
end;
该TWindowPos记录包含以下内容:
PWindowPos = ^TWindowPos;
TWindowPos = Packed record;
hwnd:HWND;//发生动作窗体的句柄
hwndInsertAfter:HWND;//变更Z顺序时最上层的窗体句柄
x:Integer;/窗体左端的位置
y:Integer;//窗体上端的位置
cx:Integer;//窗体的宽
cy:Integer;//窗体的高
flags:UINT;//变更的内容
在flags中,与此话题相关的标志如下:
?SWP_NOMOVE——保持现在位置(无视x成员和y成员)
?SWP_NOOWNERZORDER——不变更主窗体Z顺序的位置
?SWP_NOSIZE——保持现在的大小(无视cx成员和cy成员)
?SWP_NOREDEAW——即使有变更,但不重写画面
?SWP_NOZORDER——保持现在Z顺序(无视hwndInsertAfter成员)
据此当我们不想让窗体的大小变更时,只要在TWindowPos记录的flags变量中设置SWP_NOSIZE标志即可。同样不想让窗体移动时,则设置SWP_NOMOVE标志。
为了达到以上目的,首先要处理WM_WINDOWPOSCHANGING消息,修改flags变量的内容。作为窗体的处理过程(WMWindowPosChanging)如下:
{禁止窗体移动、禁止大小变更处理}
porcedure Tform1.WMWindowPosChanging(var Msg:TWMWindowPosChanging);
begin
inherited
if bWinStat [1] then
Msg.WindowPos^.flags := Msg.WindowPos^.flags or SWP_NOMOVE;
if bWinStat [2] then
Msg.WindowPos^.flags := Msg.WindowPos^.flags or SWP_NOSIZE;
end;
此例中使用bWinStat变量(Boolean类型数组),来禁止窗体的移动和大小的修改。
11.5 固定窗体的横宽
这里我们继续详细介绍关于在窗体的大小修改中固定横宽的方法。
?当我们修改窗体的大小或按下最大化按扭时,Window系统向应用程序窗体送出WM_GETMINMAXINFO消息。由于这个消息中包含有窗体尺寸的 最大和最小值,如果我们适当利用此消息,则可以指定窗体的大小范围。首先定义WM_GETMINMAXINFO消息的句柄。
(1)WM_GETMINMAXINFO消息句柄的说明:
type
TForm1 = Class(Tform)
procedure WMGetMinMaxInfo(var Msg:TWMGetMinMaxInfo);
message WM_GETMINMAXINFO;
(2)以TWMGetMinMaxInfo记录为参数:
TWMGetMinMaxInfo = record
Msg:Cardinal;
Unused:Integer;
MinMaxInfo:PMinMaxInfo;
Result:Longint;
end ;
TWMGetMinMaxInfo中的MinMaxInfo是具有关于窗体大小信息的TMinMaxInfo记录的指针。
该记录在Delphi的Windows.Pas文件中是如下说明的:
type
{Struct pointed to by WM_GETMINMAXINFO Iparam}
PMinMaxInfo = ^TMinMaxInfo;
TMinMaxInfo = packed record ptReserved:Tpoint; //预留
ptMaxSize:Tpoint; //最大化时的大小
ptMaxPosition:Tpoint ; //最大化时的位置
ptMinTrackSize:Tpoint ; //尺寸变更时最小尺寸
ptMaxTrackSize:Tpoint ; //尺寸变更时最大尺寸
end;
当按下最大化按扭时,在ptMaxSize和ptMinSize中指定窗体的大小和位置。当拖动窗体框架时,ptMinTrackSize和ptMaxTrackSize确定其变化的最大和最小尺寸。
假如窗体的大小只在从 (100,100) 到 (500,400) 的范围内变化,那么,使用以下处理过程即可完成:
//设置窗体大小变化范围
procedure TForm1.WMGetMinMaxInfo(var Msg:TWMGetMinMaxInfo);
begin
inherited;
Msg.MinMaxInfo^.ptMaxTrackSize:= Point(500,400);
Msg.MinMaxInfo^.ptMinTrackSize:= Point(100,100);
end;
首先调用inherited方法,接着对MinMaxInfo构造体设置初始值,即设置ptMaxTrackSize和ptMinTrackSize的值。
这里并没有对按下最大化按钮时的大小进行设定,仅仅只是对拖动框架的大小的动作进行了限制。因此当按下最大化按钮时,窗体与平常一样占满全部画面。
当我们绝对不想让窗体的尺寸发生变更时,则采用以下处理过程:
inherited;
Msg.MinMaxInfo^.ptMaxSize:= Point(300,200);
Msg.MinMaxInfo^.ptMaxTrackSize:= Point(300,200);
同样,横宽固定,纵长可自由改变的时候,则采用以下处理过程。
inherited;
Msg.MinMaxInfo^.ptMaxSize.x= Width;
Msg.MinMaxInfo^.ptMaxTrackSize.x = Width;
Msg.MinMaxInfo^.ptMinTrackSize.x = Width;
上面代入的是该事件句柄所属的窗体的横宽,因此其窗体的横宽不能变更。
到此从功能上讲,我们已经能够对窗体的尺寸进行控制了。可是,现在你一定会注意到以下的问题:一个是全屏化按钮和图标化按钮的表现方法,另一个是对应尺寸变化的光标形状的变化。
关于全屏化按钮和图标化按钮的使用或不使用的控制方法,在Delphi的程序设计中,可通过直接设定窗体(Form)的BorderIcons特性值来控 制。但是光标形状的变换控制稍微有点麻烦。当光标在窗体框架上时,光标显示为尺寸可变更,但是实际上窗体已经被固定。
为解决此问题生成适当的窗体,而后用WinSignt32(Delphi中含有)或Spy++等Windows监视系统对向该窗体传送的消息进行监视。当 鼠标器在该窗体上移动时,Windows监视系统向窗体传送WM_MOUSEMOVE消息的同时,还传送WM_NCHITTEST消息。当移动和敲击鼠标 时,系统对窗体传送WM_NCHITTEST消息,询问现在鼠标光标在窗体的什么位置,如果传回光标在窗体的框架上或窗体内,系统则相应自动改变光标的形 状。
因此,我们先定义WM_NCHITTEST消息句柄,从该句柄传回值。就是说即使光标在框架上,传回值也不在框架上,给Windows系统一个小骗局。该消息句柄说明如下:
interface
type
Tform1 = class(Tform)
private
bWinStat:array [1..4] of Boolean;
procedure WMNCHitTest(var Msg:TWMNCHitTest);
message WM_NCHITTEST;
该消息句柄的处理过程:
//尺寸变更的光标形状不显示
porcedure Tform1.WMNCHitTest(var Msg:TWMNChitTest);
begin
inherited
//当移动禁止时,则使其上端、左端尺寸不变更
if bWinStat [1] and
(Msg.Result in [ HTTOP,HTLEFT,HTTOPLEFT,HTTOPRIGHT;HTBOTTOMLEFT])
then Msg.Result:= Windows.HTNOWHERE;
//整体窗体尺寸变更禁止时
if bWinStat [2]
?and (Msg.Result in { HTLEFT,HTRIGHT,HTTOP,
HTBOTTOM,HTTOPLEFT,HTTOPRIGHT,HTBOTTOMLEFT,HTBOTTOPMRIGHT])
then Msg.Result:= Windows.HTNOWHERE;
//横宽方向尺寸变更禁止时
if bWinStat [3] and (Msg.ResultIn{
HTLEFT,HTRIGHT,HTTOPLEFT,HTBOTTOM,HTTOPLEFT,HTBOTTOPMRIGHT])
then Msg.Result:= Windows.HTNOWHERE;
end;
单纯就窗体的框架而言,可分为上下左右4条边和4个角共8个模式。当移动禁止时,上端、左端的位置尺寸不得变更。
11.6 无标题栏窗口
现在,我们来讨论窗体编程的另一种技巧──建立无标题栏窗口。
为了使标题栏不显示,我们可以在窗体BorderStyle特性中选取bsNone值即可。但选取bsNone值后,窗体周围什么也没有。当我们只想去掉标题栏时又怎么办呢?
实际上窗体的位置、大小、显示风格等均由Windows API的CreateWindowEx函数所指定,这个API的参数是在Delphi的CreateParams过程中被初始化的。Delphi是依据以 下的TCreateParams记录的值生成窗体的。TCreateParams记录包含窗体的位置、大小以及其他各种特性,在Delphi的 Control.Pas单元中记录的定义为:
TCreateParams = record
Caption: PChar; //标题
Style: DWORD; //窗体的显示风格
ExStyle: DWORD; //窗体的扩展显示风格
X, Y: Integer; //显示位置
Width, Height: Integer; //窗体的大小
WndParent: HWnd; //父窗体
Param: Pointer; //指向窗口生成参数(WM_CREATE消息的 //LParam)的指针
WindowClass: TWndClass; //窗体的类别
WinClassName: array[0..63] of Char; //类别名
end;
该记录中包含窗体的位置、大小和窗体显示风格的标志。因此,一般通过其值的改变来控制生成窗体的位置、大小和窗体显示风格。由于我们要设置窗体初始的风 格,所以在Params.Style变量中设定窗体的风格。又因为该变量是取标志形式,所以使用and或or布尔运算来演算,从而完成各个位的on或 off。
例如,当BorderStyle特性为bsNone时,再给窗体加上可变更大小的粗框架,则对CreateParams方法进行重载。处理过程如下即可:
Procedure TForm1.CreateParams(var Params:TCreateParams);
begin
inherited CreateParams(Params);
if BorderStyle = bsNone then
with Params do Style:= Style or WS_THICKFRAME;
end;
需要注意,一般情况下的继承关系,单纯使用inherited,则派生类的方法即可被调出执行;但继承使用了CreateParams方法时,必须明确按inheritedCreateParams(Params)编制程序。
关于窗体风格标志值用以下定量来指定,这里列举的只是具有代表性的一部分。详情请参考Windows API的CreateWindow的help。
WS_BORDER边框
WS_CAPTION标题栏
WS_CHILD子窗体
WS_HSCROLL水平滚动条
WS_MAXIMIZE全屏化窗体
WS_MAXIMIZEBOX全屏按钮
WS_MINIMIIE图标化窗体
WS_MINIMIZEBOX图标化按钮
WS_SYSMENU系统菜单
WS_THICKFRAME可变更窗体大小的粗边框
WS_VISIBLE初始为可视的窗体
到此,没有标题栏的,大小可以变更的窗体已完成。
此窗体由于没有标题栏所以关闭、全屏化等按钮的操作均不可能,并且想通过拉拖标题栏移动窗体也不可能,下面我们先讨论关于移动的问题。
关于WM_NCHITTEST消息前面已经作了解说。当拖拉标题栏移动窗体时,窗体伴随鼠标器的移动,系统向窗体传送WM_NCHITTEST消息,当确认光标在标题栏上,并同时检测出鼠标器左按钮被按下时,移动成开始状态。
只要光标在窗体,则使窗体的移动成为可能。为此,WMNCHitTest处理过程如下。
porcedure TForm1.WMNCHitTest(var Msg:TWMNCHitTest);
begin inherited; //当标题栏非显示时,光标在窗体领域即可移动
if (BorderStyle = brNone) and
(Msg.Result = HTCLIENT) then
Msg.Result:= HTCAPTION;
end;
当鼠标光标在窗体领域内时,并且无标题栏,WM_NCHITTEST消息传回值为HTCLIENT时,将传回值作为HTCAPTION处理,系统则判断鼠标光标在标题栏上,这样就完成了窗体领域可移动的处理。这里再次给Windows系统一个骗局。
另外,讨论一下标题栏的消除和追加的方法。从而解决关闭、全屏化、图标化的问题。首先想到的方法是在窗体(Form)被单击或双击后,改变 BorderStyle特性的值。就是说对Form的OnClick事件进行定义,创建子程序将bsNone变为bsSizeable。
可是我们测试一下,它并不动作。实际上这是因为在前面为了处理移动,在WMNCHitTest过程中,将窗体领域的返回值变更成了标题栏的返回值,因此系统只判断鼠标光标在标题栏上单击。
要想移动和单击同时起作用需要下一点功夫。当用WinSight32来确认显示正常的窗体返回给系统的消息时,与通常窗体领域返回WM_LBUTTONDOWN消息相对,在标题上单击时返回WM_NCLBUTTONDOWN消息。
这里不使用窗体(Form)的OnClik事件,而是通过WM_NCLBUTTONDOWN消息的处理来得到希望的动作。如下编程即可 :
interface
type
TForm1 = Class(TForm);
:
protected WMNCLButtonDown(var Msg:TWMNCLButtonDown);
message WM_NCLBUTTONDOWN;
implemention //鼠标单击窗体追加标题栏
porcedure TForm1.WMNCLButtonDown(var Msg:TWMNCLButtonDown);
begin
if BorderStyle = bsNone then
borderStyle = bsSizeble;
end;
当然,如果在窗体上配置其他可视构件,从而可以简单地通过对构件的单击来完成以上动作。
11.7 窗体间相互连动
下面我们来讨论两个窗体的设计技巧。设其中一个为主窗体,另一个为子窗体。 在Delphi程序设计中,窗体(Form)的Position特性取值为poDesigned,则窗体的显示位置和大小以特性Left、Top、 Width、Height的取值为依据来决定。这样在创建子窗体时,以获得主窗体的显示位置和大小(主窗体的Left、Top、Width、Height 的特性值)为基准,然后确定子窗体的位置和大小即可。
窗体的初始显示位置,可用CreateParams方法进行设置,之后用重载子窗体(Form1)的CreateParams方法,来改变子窗体的显示位 置和大小。主窗体和子窗体之间是怎样互相引用的呢?首先设置窗体(Form1)的单元文件名为unit1.pas,子窗体(Form2)的单元文件名为 unit2.pas。通常要引用某窗体单元文件的特性和内容时,应该在interface的uses处插入其窗体(Form)的单元名(unit)。本实 例中根据主窗体动态生成子窗体,因此在主窗体(Form1)的interface的uses处插入子窗体的单元名(unit2)。这样主窗体即可引用子窗 体的各种特性值。另一方面要实现子窗体引用主窗体的各种特性值又怎么办呢?由于Pascal语言不允许相互循环引用,即A到B,并且B到A。这时,必须在 imptementation的uses处插入主窗体的单位名(unit1),这样即可对主窗体(Form1)的特性值进行引用。
引用方法如下:
implementation
{$R *.DFM}
uses Unit1;//主窗体单元名
{生成窗体时参考数的初始化}
procedure TForm2.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
//当没有窗体题目时,添加细框
if BorderStyle = bsNone then
Params.Style := Params.Style or WS_THICKFRAME;
//根据主窗体的显示位置和大小决定子窗体的显示位置和大小
with Form1 do
begin
Params.X := Left + Width;
Params.Y := Top;
Params.Height := Height;
Params.Width := 100;
end;
end;
这样即完成了对应主窗体的子窗体在其右侧显示的程序设计。现在当我们移动主窗体时,子窗体并不随之移动,自然,移动子窗体时,主窗体也不会移动。虽然通常的Windows的应用程序则到此为止,但是我们的主题是要讨论连动,下面我们继续介绍这一问题。
首先要搞清楚,怎样把握自身窗体的移动和大小变化。在窗体的移动和大小变化时Windows系统要送出WINDOWSPOSCHANGED消息,为了捕捉此消息,对以下方法(Method)进行说明:
type
Tform1 = class(Tform)
protected
procedure WMWindowPosChanged(vax Msg:TWMWindowPosChanged);
message WM_WINDOWPOSCHANGED;
WM_WINDOWPOSCHANGED消息是在窗体的位置和大小变化后送出的。上面是对WM_WONDOWPOSCHANGED消息句柄的说明,接着是TWMWindowsPosChanged消息记录型和TWindowPos记录内容的定义。
type
TWMWindowPosMsg = record
Msg:Cardind;
Unused:Integer;
WindowPos:PwindowPos;
Resukt:Longint;
end;
PwindowPos = ∧TWindowPos;
TwindowPos = packed record
hwnd:HWND; //自身的窗体句柄
hwndInsertAfeer:HWND;//变更Z的顺序时,最上层窗体句柄
x:Integer//新窗体左端的位置
y:Integer//新窗体上端的位置
cx:Integer//新窗体上端的宽
cy:Integer//新窗体上端的高
flags:UINT//变更内容(移动、大小 、Z顺序等)
end;
下面具体介绍此方法的使用,首先要确认子窗体是否存在,而后确认自身的显示位置,一旦发生变更就使用WindowsAPI的SetWindowPos,以变更子窗体的显示位置和大小。要注意的是只是主窗体发生变更时才使用。
这样即可使子窗体随主窗体的变更而变更。接着我们还希望主窗体随子窗体的变更而变更。以下先是主窗体变更使子窗体变更的处理过程。
const
uFlag = SWP_NOACTIVATE or SWP_NOZORDER or SWP_NOMOVE orSWP_NOSIZE;
//大小变更、位置移动结束通知
porcedure TForm1.WMWindowPosChanged(var Msg:TWMWindowPosChanged);
var Rect:TRect;
x,y,cx,cy:Integer;
uFlag2:UINT;
begin
inherited;
if Form2 <> nil then
//如果子窗体存在
begin
GetWindowRoct(Form2.Handle,Rect); //获取子窗体的位置
With Msg do
begin
x:= WindowPos^.x + WindowPos^.cx; //新x坐标
y:= Window^.y; //新y坐标
cx:= 100; //宽固定在100
cy:= WindowPos^.cy //新高度
end;
uFlag2:= uFlag; //需要移动时
if(Rect.Top <> Y) or(Rect.Left<>X) then
uFlag2:= uFlag2 and(not SWP_NOMOVE);
if(Rect.Bottom -Rect.Top <>cy) or
//需要变更大小时
(Rect.Right -Rect.Left<>cx) then
uFlag2:= uFlag2 and(not SWP_NOSIZE);
if uFlag <> uFlag2 then
//对子窗体(Form)进行变更
setWindowPos(Form2.Handle,0,x,y,cx,cy,uFlag2);
以上处理是从主窗体的新位置来计算子窗体的新位置,然后与现在的子窗体的位置信息进行比较,如果不同则变更子窗体。单纯用Wondow API的SetWindowPos对位置进行变更时,Z顺序和活动窗口(Active Window)发生转换,因此指定SWP_NOACTIVATE标志和SWP_NOZORDER标志,阻止其转换。
从子窗体来变更主窗体的过程大致与上相同。不同之处在于主窗体一定存在,而不需要对其进行检验。