delphi 在处理进程的消息时引入了一个隐藏的窗体application ,借此进行消息的分发。这样的机制优美的处理了消息的分发和处置的问题。但是最近我发现这个机制也引入了一个副作用,会在某些情况下影响程序的界面交互行为。
我遇到的需求是需要在程序中实现单个实例,并且在第二个实例被启动的时候,首先将前一个实例置到最前,然后退出。按说这样的问题应该是比较典型的例子,但是这样的一个简单需求就受到了这个副作用的影响。
我的实现方式是这样的:第二个实例启动的时候,对前一实例发一个消息,要求它将自己置前,然后退出。至于前一个实例的句柄怎么取得,可以用findwindows ,也可以用命名的filemapping ,总之,第一个窗体就这样接到了将自己置前的命令。
怎么将这个窗体置到最前呢?众所周知,setforegroundwindow 并不能真正将一个窗体置到最前,相反的,为了礼貌起见,它会让这个指定的窗体在任务栏里面闪动,吸引用户注意,但是它不会把这个窗体盖在z-order 的顶端。很有教养,但是我不喜欢,因为这不是我想要的。
于是我使用了这样一个方法将我要的窗体直接置到最前面:
setwindowpos(hform, hwnd_topmost, 0, 0, 0, 0, swp_nomove or swp_nosize);
setwindowpos(hform, hwnd_notopmost, 0, 0, 0, 0, swp_nomove or swp_nosize);
强行把窗体拖到z-order 的顶端,然后去掉它的topmost 属性,这样就可以了。有点不够礼貌,不过要是礼貌有用的话,要警察做什么?
好了,前面说的内容似乎和我们的标题没太多关系,可是后面的麻烦都根源在这里。
我发现,当第一个实例被最小化时,第二个实例将它唤醒,将它的主窗体置到最前,然后窗体的“最小化”按钮失效了!这时窗体可以操作、可以最大化、可以关闭,却再也不能最小化了!
这是一个很郁闷的问题,一个不能最小化的窗体实在是很不友好的,我可以接受一个不礼貌的窗体,却不愿意接受一个这样不友好的家伙。
我发现问题原因的过程是这样的:
在进程中,主窗体的wm_syscommand 消息是被传递给application 类处理的,当cmdtype 为sc_minimize的时候,application 会调用minimize 方法:
procedure tapplication.minimize;
begin
if not isiconic(fhandle) then
begin
normalizetopmosts;
setactivewindow(fhandle);
if (mainform <> nil) and (showmainform or mainform.visible)
and iswindowenabled(mainform.handle) then
begin
setwindowpos(fhandle, mainform.handle, mainform.left, mainform.top,
mainform.width, 0, swp_showwindow);
defwindowproc(fhandle, wm_syscommand, sc_minimize, 0);
end else
showwinnoanimate(fhandle, sw_minimize);
if assigned(fonminimize) then fonminimize(self);
end;
end;
注意这个isiconic(fhandle),它就是问题原因的冰山一角。isiconic 是用来检测窗体是否处于最小化状态的api。我发现,第二实例将前一实例的主窗体置前之后,这个窗体最小化调用这个方法时,每次isiconic(fhandle) 都是true。也就是说,application 一直认为自己是最小化的。
于是问题就比较清楚了:我们在将主窗体强行置到最前的时候,application 并没有恢复原状态。于是在minimize 方法中主窗体就得不到最小化的命令了。
难怪在vc 开发的程序中不会有这样的问题!因为不存在application 的这个因素。
于是我们只要将主窗体强行置前之前,首先将application 恢复:
if isiconic(application.handle) then
begin
defwindowproc(application.handle, wm_syscommand, sc_restore, 0);
end;
这样就好了。