科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件Delphi下QQ窗体自动隐藏探索

Delphi下QQ窗体自动隐藏探索

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

腾讯QQ是当前流行的网络聊天工具之一,由于它在应用设计上有很多独特之处,所以也吸引了很多程序员对之进行研究和模仿。

作者:cobi 来源:开发高手 2007年10月31日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
四、进一步完善

  上面的代码已经基本实现了窗体的自动隐藏效果,但是我在介绍代码的时候有两个问题是被提出但没有被解答的。

  首先是为什么触发隐藏时Fanchors中将至少有一个值而不多于两个值呢?注意代码中对Fanchors的赋值是通过四个判断进行的, 那么如果触发隐藏的话,Fanchors中将毫无疑问会有一个值存在,但这种情况是针对隐藏发生在屏幕的四边而言。当窗体被推入到屏幕的四角时,那么Fanchors中便将会有两个值存在。那此时窗体会隐藏到什么地方呢?

  实际的效果告诉我们,窗体会被隐藏到屏幕的四角上。此时若我们试图让窗体重新显示,你便会发现窗体在不断的闪烁。为什么呢?这就是第二个问题提出的原因了。因为对窗体显示或隐藏的处理是根据Fanchors中的值作出的。当Fanchors中有两个值的时候,就将会引发对窗体属性的两次设置。而因为设置语句只有顺序差异而没有优先级差异,那么OnTimer事件中每次都会对窗体进行两次的属性值设置,从而导致我们看到闪烁的显示效果。

  怎么去解决这个问题呢?我们再观察一下QQ的处理。在2003 II版的QQ里面,窗体的隐藏效果作了一定的调整:当窗体在屏幕左右两边隐藏时,它会自动充满屏幕的左右两边且高度不可改变;当窗体脱离屏幕两边的隐藏区域后,窗体的大小会恢复为隐藏前的大小,如图五所示。(注意:窗体并非是完全充满屏幕的两边。QQ在处理这个效果时可能只注意了系统工具栏总在最前显示且位于屏幕下方的情况,所以其充满的区域也只是屏幕顶端到系统工具栏上方的一段空间,如图六所示。)这样的处理可以令窗体即使被推入到屏幕四角,也可以保证只会对其中的一个隐藏方向进行处理,从而避免了前面出现的闪烁现象。


图五 Q Q 窗体自动充满屏幕两边


图六 Q Q 窗体自动充满屏幕两边的漏洞

  结合前面的分析,要实现如上的效果还是从拦截WM_MOVING消息入手。重写后的WMMOVING过程如下:

procedure TForm1.WMMOVING(var Msg: TMessage);
begin
 inherited;
 with PRect(Msg.LParam)^ do
begin
 if (akLeft in FAnchors) or (akRight in FAnchors) then
 begin
  if (Left > 0) and (Right < Screen.Width) then
  begin
   if rec_Position then
   begin
    Bottom := top + Lst_Height;
    Right := Left + Lst_Width;
    Height := Lst_Height;
    Width := Lst_Width;
   end;
   end else
   begin
    SetBarHeight;
    Top := Cur_Top;
    Bottom := Cur_Bottom;
    exit;
   end;
  end;
  Left := Min(Max(0, Left), Screen.Width - Width);
  ..
  if not Rec_Position then
  begin
   Lst_Height := form1.Height;
   Lst_Width := form1.width;
  end;
  FAnchors := [];
  ..
  if (akLeft in FAnchors) or (akRight in FAnchors) then
  begin
   Rec_Position := True;
   SetBarHeight;
   Top := Cur_Top;
   Bottom := Cur_Bottom;
  end else
   Rec_Position := False;
   Timer1.Enabled := FAnchors <> [];
  end;
end;

  在新的代码中,我们首先使用了三个新定义的全局变量,分别是:

Lst_Height : Integer; //记录窗体隐藏前的高度
Lst_Width : Integer; //记录窗体隐藏前的宽度
Rec_Position : Boolean; //是否启动窗体宽高记录标志

  然后加入了三个判断代码块。

  在第一个判断中首先判定窗体在移动前是否位于屏幕左右两边的隐藏区域。若为真,则判断窗体是否从隐藏区域向屏幕中央移动(注意,存在此判断的原因是因为我们还可能将窗体往屏幕两边推动)。若再为真,则恢复窗体隐藏前的大小;反之,强制设置矩形的Top和Bottom值并退出消息的处理。

  第二个判断在于记录窗体的宽高值。Rec_Position 是记录窗体宽高的标志,它的值在第三个判断中进行设置。若窗体在移动前位于屏幕两边的隐藏区域,则Rec_Position为True,此时窗体的高度已经固定,记录已经无意义。所以只在Rec_Position为False时才需要记录窗体的宽高。

  第三个判断位于Fanchors值设置之后。它根据窗体的位置对矩形的显示效果进行判断处理。判断也是基于窗体是否位于屏幕两边进行,为True则设置矩形的高度并设置Rec_Position的值为True。

  在第三个判断中使用了一个新定义的过程SetBarHeight,其代码如下:

procedure TForm1.SetBarHeight;
var
 AppBarData : TAPPBARDATA;
begin
 AppBarData.cbSize := SIZEOF(AppBarData);
 If SHAppBarMessage(ABM_GETSTATE,AppBarData) AND ABS_AUTOHIDE) <> 0 then
 begin
  Cur_Top := 1;
  Cur_Bottom := Screen.Height - 1;
 end else
 begin
  SHAppBarMessage(ABM_GETTASKBARPOS,AppBarData);
  case AppBarData.uEdge of
   ABE_TOP : begin
   Cur_Top := AppBarData.rc.Bottom + 1;
   Cur_Bottom := Screen.Height - 1;
  end;
  ABE_LEFT : begin
  Cur_Top := 1;
  Cur_Bottom := Screen.Height - 1;
 end;
 ABE_RIGHT : begin
 Cur_Top := 1;
 Cur_Bottom := Screen.Height - 1;
end;
ABE_BOTTOM : begin
Cur_Top := 1;
Cur_Bottom:=Screen.Height -
(AppBarData.rc.Bottom - AppBarData.
rc.Top) - 1;
end;
end;
end;
end;

  SetBarHeight用于计算矩形高度,计算后的结果通过Cur_Top和Cur_Bottom两个全局变量传递给矩形的Top和Bottom参数。

  在该过程中使用了一个Windows API 函数SHAppBarMessage。SHAppBarMessage 的作用是向系统传递系统工具栏消息,其函数原型为:

WINSHELLAPI UINT APIENTRY SHAppBarMessage(DWORD dwMessage,PAPPBARDATA pData);

  其中dwMessage 是发送给系统的工具栏消息; pData是指向PAPPBARDATA结构的指针,PAPPBARDATA结构返回的内容依据发出的消息而定。

  在过程中,我们首先传递ABM_GETSTATE参数去获取系统工具栏的状态是自动隐藏还是总在最前显示。

  然后我们再利用ABM_GETTASKBARPOS参数去获取系统工具栏的位置,此时AppBarData的返回值中将会是系统工具栏的位置ABE_TOP 、ABE_LEFT、ABE_RIGHT、ABE_BOTTOM四者之一。最后我们利用系统工具栏自身的拖动矩形参数计算出工具栏的高度。

  使用了SetBarHeight令窗体在屏幕两边随系统工具栏的位置和高度的改动而发生相应的变化。当然,你也可以直接给Cur_Top和Cur_Bottom这两个变量设置固定值以实现QQ效果。在测试中,Cur_Top可以是1,而Cur_Bottom 则是Screen.Width-30(Windows系统工
具栏的高度在默认情况下是30,这是不随分辨率改变的)。

  由于要使窗体在屏幕两边的高度与位置可以随系统工具栏的位置和高度的改动而发生相应的变化,因此OnTimer事件中的处理也要相应的改动,主要是显示窗体的时候要注意对窗体Top和Height属性的设置必须跟随与系统工具栏的位置和高度相协调,代码如下:

..
if akLeft in FAnchors then
begin
 Left := -Width + cOffset;
 SetBarHeight;
 Top := Cur_Top;
 Height := Cur_Bottom;
end;
if akRight in FAnchors then
begin
 Left := Screen.Width - cOffset;
 SetBarHeight;
 Top := Cur_Top;
 Height := Cur_Bottom;
end;
..

  最后,为了保证窗体在屏幕两边隐藏后高度保持不变,我们再添加一个WMSizing过程对WM_Sizing消息进行拦截处理。WMSizing过程的代码如下:

procedure TForm1.WMSizing(var Msg: TMessage);
begin
 inherited;
 if (akRight in FAnchors) then
 begin
  with PRect(Msg.LParam)^ do
  begin
   Left := Screen.Width - Width;
   Top := Cur_Top;
   Right := Screen.Width;
   Bottom := Cur_Bottom
  end;
 end else if (akLeft in FAnchors) then
 begin
  with PRect(Msg.LParam)^ do
  begin
   Left := 0;
   Top := Cur_Top;
   Right := Width;
   Bottom := Cur_Bottom;
  end;
 end;
end;

  WM_Sizing消息的语法结构与WM_MOVING消息相似,也包含了一个对矩形的指针。通过该指针我们可以对矩形的Top、Left、Right和Bottom参数进行设置,从而保证矩形高度不受用户操作影响。

  至此,一个窗体自动隐藏的程序就基本完成了,其实际效果已经和QQ相当接近了。运行效果如图七至图十所示。当然,从实际运行效果看还存在着一些小瑕疵,并且代码中并没有对窗体在隐藏后的宽度设置上进行处理,或者读者可以考虑继续进行完善此程序。


图七 窗体向屏幕右方移动


图八 窗体充满屏幕右方


图九 窗体位置随工具栏位置变化


图十 窗体离开屏幕右方恢复隐藏前大小

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章