自己处理 WM_NCPAINT 时强制重新绘制 TMainMenu
-
21-12-2019 - |
题
我在一个相当大的具有多种形式的应用程序中自定义绘制我的标题栏,并决定尝试用老式的方式来完成它,通过自己处理一些消息并在处理 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.
解决方案
“当我自己处理 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”的测试。更新区域的伪句柄在 文档. 。然而对于普通窗口来说,区域句柄始终为“1”。事实上,非客户处理从未被正确记录。我的猜测是,这是因为操作系统本身不遵守规则。以在默认处理过程中绘制 NC 区域的事实为例 WM_NCACTIVATE
. 。激活与NC喷漆有什么关系?为什么操作系统会在你背后作画?
我的建议是,走VCL风格已经走的路线。进入绘图 NC 区域后,将其全部绘制。菜单栏区域是非客户区域的一部分。