在自己处理WM_NCPAINT时强制重新绘制TMainMenu

时间:2014-06-03 16:51:59

标签: delphi vcl delphi-xe3 titlebar nonclient-area

我是在一个有很多表格的相当大的应用程序中自定义绘制我的标题栏,并决定尝试通过自己处理一些消息并在我处理WM_NCPAINT时填写绘图时的老式方法。

这幅画本身很顺利,基本上是有效的。有一件事是行不通的是绘制TMainMenu。要么我有默认的WM_NCACTIVATE处理程序绘制整个非客户区域(我必须在我的WM_NCPAINT处理程序中执行WM_NCACTIVATE消息),然后再绘制它,这会导致看似无法帮助的闪烁。或者我可以尝试让WM_NCPAINT的默认处理程序仅绘制包含TMainMenu的Rect,这会产生平滑的结果,但不会重新绘制菜单。

我的问题是:

  • 当我自己处理WM_NCPAINT时,如何才有TMainMenu并且只重新绘制了一个TMainmenu?

我已经给了它一些,我认为我走的是正确的方式,但我正在撞墙;我对我正在做的事情知之甚少,似乎无法找到关于它的明确文档。我的代码中最重要的部分是:

RedrawWindow(Handle, nil, MenuRegion, RDW_INVALIDATE or RDW_FRAME or RDW_NOERASE or RDW_ALLCHILDREN or RDW_UPDATENOW);

我认为这正是出了什么问题。 (或者更确切地说,我计算'MenuRegion'的代码。我不知道是否出错,因为我使用了错误的坐标系,或者是因为我的方式完全是错误的。

这是我的代码的简化版本,它将在delphi(xe3)中按原样编译和运行:

unit Unit3;
interface
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus;
type
  TForm3 = class(TForm)
  private
    FDrawMenu: Boolean;
    function CalcFrameRegion: HRGN;
    function CalcMenuRegion: HRGN;
    procedure DrawMenu;
    procedure FormFrame(minimal: Boolean = false);
    procedure WMNCActivate(var message: TWMNCActivate); message WM_NCACTIVATE;
    procedure WMNCPaint(var message: TMessage); message WM_NCPAINT;
    procedure WMSIZE(var message : TWMSIZE); message WM_SIZE;
    constructor Create(AOwner: TComponent); override;
  public
    { Public declarations }
  end;
var
  Form3: TForm3;
implementation
{$R *.dfm}
{ TForm3 }
function TForm3.CalcFrameRegion: HRGN;
var
  YCaption, YFrame, XFrame: Integer;
begin
  YCaption := GetSystemMetrics(SM_CYCaption);
  YFrame := GetSystemMetrics(SM_CYFRAME);
  XFrame := GetSystemMetrics(SM_CXFRAME);
  Result :=  CreateRectRgn(0, 0, YCaption + YFrame, Width);
  Result := Result +  CreateRectRgn(0, 0, Height, XFrame);
  Result := Result + CreateRectRgn(0, Height - YFrame, Width, Height);
  Result := Result + CreateRectRgn(Width - XFrame, 0, Width, Height);
end;
function TForm3.CalcMenuRegion: HRGN;
var
  XFrame, YFrame, YCaption, YMenu: Integer;
begin
  XFrame := GetSystemMetrics(SM_CXFRAME);
  YFrame := GetSystemMetrics(SM_CYFRAME);
  YCaption := GetSystemMetrics(SM_CYCAPTION);
  YMenu := GetSystemMetrics(SM_CYMENU);
  Result := CreateRectRgn(XFrame, YFrame + YCaption, Width - XFrame, YFrame + YCaption + YMenu);
end;
constructor TForm3.Create(AOwner: TComponent);
var
  testItem: TMenuItem;
begin
  inherited;
  // Creating a MainMenu and attatching it to the form.
  Menu := TMainMenu.Create(self);
  // The menu need san item.
  testItem := TMenuItem.Create(Menu);
  testItem.Caption := 'test';
  Menu.Items.Add(testItem);
  FDrawMenu := false;
end;
procedure TForm3.FormFrame(minimal: Boolean);
var
  YCaption, YFrame, XFrame: Integer;
begin
  YCaption := GetSystemMetrics(SM_CYCaption);
  YFrame := GetSystemMetrics(SM_CYFRAME);
  XFrame := GetSystemMetrics(SM_CXFRAME);
  Canvas.Handle := GetWindowDC(Handle);
  Canvas.Pen.Style := psClear;
  Canvas.Brush.Style := bsSolid;
  Canvas.Brush.Color := clRed;
  if not minimal then begin
    Canvas.Rectangle(0, 0, Width + 1, YCaption + YFRame + 1);
    Canvas.Rectangle(0, YCaption + YFRame, XFrame + 1, Height + 1);
    Canvas.Rectangle(XFrame, Height - YFrame, Width + 1, Height + 1);
    Canvas.Rectangle(Width - XFrame, YCaption + YFRame, Width + 1, Height - YFrame + 1);
  end;
end;
procedure TForm3.DrawMenu;
var
  MenuRegion: HRGN;
begin
  if Assigned(Menu) then begin
    MenuRegion := CalcMenuRegion;
    FDrawMenu := true; // Make sure the inherited handler gets called.
    // Force a redraw of the region defined by MenuRegion.
    RedrawWindow(Handle, nil, MenuRegion,
                  RDW_INVALIDATE or RDW_FRAME or RDW_NOERASE or RDW_ALLCHILDREN or RDW_UPDATENOW);
    FDrawMenu := false; // Use the FormFrame function again.
  end;
end;
procedure TForm3.WMNCActivate(var message: TWMNCActivate);
begin
  FormFrame;
  message.Result := 1; // This makes sure the message gets handled properly.
end;
procedure TForm3.WMNCPaint(var message: TMessage);
begin
  if FDrawMenu then
    inherited // Gets called when the Menu has to be drawn.
  else
    FormFrame; // Gets called in all other cases.
end;
procedure TForm3.WMSIZE(var message: TWMSIZE);
begin
  inherited;
  DrawMenu;
end;
end.

1 个答案:

答案 0 :(得分:5)

“当我自己处理WM_NCPAINT时,我怎么能有一个TMainMenu并且只重新绘制了一个TMainmenu?”

理论上,您可以修改传递的区域WM_NCPAINT。定义与菜单栏区域对应的区域,省略其余区域。以下是概念证明:

procedure TForm3.WMNCPaint(var Message: TWMNCPaint);
var
  R: TRect;
  MenubarInfo: TMenuBarInfo;
  MenuRgn: HRGN;
begin
  FormFrame( whatever );

  MenubarInfo.cbSize := SizeOf(MenubarInfo);
  GetMenuBarInfo(Handle, OBJID_MENU, 0, MenubarInfo);

  MenuRgn := CreateRectRgnIndirect(MenubarInfo.rcBar);
  if Message.RGN <> 1 then
    DeleteObject(Message.RGN);
  Message.RGN := MenuRgn;

  inherited;
end;


在实践中,你最终会放弃这条路线。请注意上面示例中的“1”测试。 documentation中未提及更新区域的伪句柄。然而,对于普通窗口,区域句柄始终为“1”。事实上,非客户端处理从未被正确记录过。我的猜测是,那是因为操作系统本身不符合规则。例如,在WM_NCACTIVATE的默认处理期间正在绘制NC区域。激活与NC绘画有什么关系。操作系统为什么要背后隐藏?

我的建议是,采取VCL风格采取的路线。一旦你在绘制NC区域,绘制所有。菜单栏区域是非客户区域的一部分。