Delphi按钮在Aero玻璃上显示白色边框

10

我一直在尝试使用Delphi 2010中的Aero找到一个好看的设计。显然可以将玻璃框架扩展到屏幕底部的OK / Cancel按钮中。但是我注意到,在Delphi 2010中,这看起来不太对-每个按钮周围都有白色边框。

这张图片展示了问题:前三个按钮是我的应用程序,后两个来自Paint.NET的图层属性对话框。

Delphi控件周围有白色边框

我尝试了各种组合的DoubleBuffered和一些在其他控件上放置控件的组合,但问题仍然存在。有什么想法吗?


2
你有能展示这种行为的最小应用程序的源代码吗?(一个简单窗体的 .dfm 和 .pas 文件就可以) - Jeroen Wiert Pluimers
@Jeroen:我可以通过一个简单的表单来重现这种行为,其中包含一个TButton并设置glassframe,以便按钮在玻璃上。我现在只是无法连接到QC,因此无法找出是否已经提交了类似的错误。 - Uwe Raabe
有趣的行为 - 当button.doublebuffered为true,但form doublebuffered为false时,我得到一个颜色(黑色)围绕着按钮,当两者都为true时,我得到另一种颜色(如上所述,灰色或clBtnFace),而当按钮不是双缓冲时,文本看起来变灰。因此,在Delphi 2010中在玻璃上使用按钮而不让它看起来像垃圾是不可能的吗? - Warren P
相关问题:WinForms的人们也没有通过玻璃获得任何乐趣:https://dev59.com/fnI-5IYBdhLWcg3wZ3jC - Warren P
4个回答

5

如果没有一个干净的解决方案,可以使用 TBitBtn 并将 DoubleBuffered = false 作为一种解决方法。


3
赞赏创新思维,但事实上情况并不那么简单。 当窗体没有启用双缓冲时,BitBtn看起来很好,没有双缓冲也没问题。但是一旦为窗体开启双缓冲,BitBtn看起来依然相同。关闭窗体的双缓冲,标签就会消失。太让人崩溃了! - Cobus Kruger
当我在这里尝试TSpeedButton时,它看起来很好。然而,它不接受键盘焦点。 - Warren P
在Delphi XE中,TBitBtn完美地工作 - 不管是在TBitBtn还是在窗体上的DoubleBuffered设置。这是我目前首选的解决方案。 - Cobus Kruger
DoubleBuffered属性设置为False是解决许多问题的方法! - David Heffernan

2
看起来唯一的解决方法是使用owner-draw或第三方按钮控件请查看Roy Klever的Glass Button,或者使用DoubleBuffered=false的TBitBtn,这也是上面提出的问题的被接受的答案。
这是Windows Aero DWM中的一个bug,或者是Windows公共控制中的一个bug,或者是VCL类层次结构在绘制玻璃时处理公共控制窗口消息和绘图的方式有问题。简而言之,Windows公共控件不能在玻璃上正确地绘制自己,或者说DWM合成(Aero)已经出问题了。毫不意外。
标准的VCL按钮组件使用Windows Common Control中的WINDOW BUTTON窗口类。
请注意,TSpeedButton不使用Windows公共控件,因此没有这个问题。但是,它也无法获得焦点。
Embarcadero似乎知道这个问题,这是QC#75246,它因为Common Controls库中真正的bug而被关闭,并建议使用TBitBtn。按钮并不孤单,这是包括面板和其他常用控件在内的QC报告组合的一部分。
但是我有一个商业TcxButton(开发人员表达式组件的一部分),它接受键盘焦点,并且没有绘制这个小问题。任何使用Win32公共控件按钮控件的代码似乎都存在这个问题。可能一个低级别的Win32 API黑客可以找到一个解决方法。我正在研究。如果我找出来了,我会更新这个答案。
有趣的细节之一:TcxButton有三种绘画风格,cxButton.LookAndFeel.Kind = {lfOffice11、lfFlat、lfStandard}。选择lfOffice11会加入这个小问题。看起来是Aero中的玻璃功能和常规控制/ XP theme按钮绘图代码之间的奇怪交互。
也许唯一的解决方法是使用完全的应用程序绘制的按钮控件,并且不在aero玻璃面板上使用Windows常用控件按钮或依赖于XP主题引擎绘制按钮的任何按钮控件。
编辑:7月28日,Embarcadero的某个人关闭了上述QC条目,这是个错误。我敦促他们重新打开它,即使仅仅是为了澄清是否确实存在Common Controls dll中的Windows bug。如果你想玩,从StdCtrls中复制TButton和TCustomButton类的VCL源代码,就像我在这里所做的那样,修改CNCtlColorBtn,以便强制执行三种事情中的一种-PerformEraseBackground、DrawParentBackground或继承,并查看结果。有趣的东西。
procedure TCustomGlassButton.CNCtlColorBtn(var Message: TWMCtlColorBtn);
begin
  PerformEraseBackground(Self, Message.ChildDC);
  Message.Result := GetStockObject(NULL_BRUSH);
(*
  with ThemeServices do
    if ThemesEnabled then
    begin
      if (Parent <> nil) and Parent.DoubleBuffered then
        PerformEraseBackground(Self, Message.ChildDC)
      else
        DrawParentBackground(Handle, Message.ChildDC, nil, False);
      { Return an empty brush to prevent Windows from overpainting we just have created. }
      Message.Result := GetStockObject(NULL_BRUSH);
    end
    else

      inherited;
  *)
end;

关于Vista时代的玻璃/DWM/aero APIs的一些有趣阅读材料(C++开发者博客)


1

这里我提供了一些代码,可以让TButton在玻璃效果下看起来很好。不幸的是,它会使窗体出现“点击抛出”的问题,所以我认为这不是一个好主意。但也许你可以找到一种方法来解决窗体的“点击抛出”问题。


0

如果您能够使用Win32 API,请尝试利用NM_CUSTOMDRAW通知(而不是ownerdraw),就像我一样(是的,按钮会发送它,包括单选框和复选框。对于这些控件最好使用WM_CTLCOLORSTATIC)。这是C++中的实现方式,但思路是相同的。虽然我的想法很好,但恰巧在程序执行期间,我的按钮会从窗口中消失一次,并且我需要将鼠标悬停在它们上面才能再次看到它们。这就是为什么我仍在寻找有关此问题的评论。请注意,在单窗体应用程序中很难重现按钮消失的情况。然而,我在每个项目中都遇到了这种行为。

case WM_NOTIFY:
  switch(((LPNMHDR)lParam)->code){
  case NM_CUSTOMDRAW:
    {
       NMHDR *nmh=(NMHDR*)lParam;
       //these 6000 through 6004 are button identifiers assigned by me
       if(nmh->idFrom >= 6000 && nmh->idFrom <= 6004){
         switch(((LPNMCUSTOMDRAW)nmh)->dwDrawStage){
           case CDDS_PREERASE:
             //BackgroundBrush is a HBRUSH used also as window background
             FillRect(((LPNMCUSTOMDRAW)nmh)->hdc, &((LPNMCUSTOMDRAW)nmh)->rc, BackgroundBrush);
             break;
         }
    }
    break;
}
break;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接