djs*_*oft 11 delphi delphi-xe2 vcl-styles
我不知道它是不是一个bug ...但是当我设置除"Windows"之外的任何其他VCL样式时,窗口宽度会减小.
-
这有什么解决方案吗?
更新 我把它提交给QC:http://qc.embarcadero.com/wc/qcmain.aspx?d = 103697 希望他们能解决它...
这不是vcl样式的bug,这就是vcl样式的工作方式,每个样式(skin)都有自己的边框宽度和高度,有时与本机windows边框大小不匹配.
检查下一张图片

碳风格的边框宽度和高度为5像素

Amakrits风格的边框宽度和高度为6像素

您可以使用以下方法检查每种样式的边框样式大小 VCL Styles Designer
因此,根据上述属性,表单的Style钩子重新计算Client区域的边界.
好的-我进行了更多调查,发现了此错误的根本问题(跳至最后的解决方法)。散布在Internet上并在此消息之前进行讨论的大多数/所有其他解决方法似乎只是掩盖了bug的症状,而没有真正找到根本原因-这些其他解决方法可能会产生其他不良后果或局限性(正如他们的一些作者所指出的)。
根本问题是,当参数TFormStyleHook.WMNCCalcSize为时,WM_NCCALCSIZE消息不提供任何消息处理。wParamFALSE 该功能基本上是不完整的。因此,默认的窗口处理程序称为 Windows提供的默认处理程序,它当然会返回Windows默认样式的客户端rect,而不是用户指定的VCL样式。要修复此错误,Embarcadero必须添加对WM_NCCALCSIZEwhen wParamis的处理,FALSE以便仍返回VCL样式信息。对于他们来说,这将是一个非常容易的修复程序,并且既然我已经为他们调查并发现了问题,我希望可以将该修复程序应用于下一个VCL版本。
为了证明这是问题的原因,我记录了所有发送到表单的消息(通过重写WndProc),并为每条消息记录了Win32提供的rect客户端GetClientRect对于VCL样式是否正确。我还注意到了进行的WM_NCCALCSIZE函数调用的类型(的值为wParam)。最后,我注意到WM_NCCALCSIZE处理程序返回的新客户端rect 。
我发现,在应用程序运行时,几乎每条WM_NCCALCSIZE消息都已wParam设置为TRUE(它确实可以正常工作),因此该错误被隐藏并且不会发生。这就是为什么Embarcadero到目前为止已经摆脱了这个错误。但是,该消息仅在wParam设置为的FALSE情况下发送,并且发生在关键时刻:就在ClientWidth/ ClientHeight属性通过设置为DFM文件中的值之前TCustomForm.ReadState。该TControl.SetClientSize函数通过GetClientRect从当前总窗口宽度中减去当前客户端宽度(由Windows度量)来进行操作,然后添加新的客户端宽度。 换一种说法,TControl.SetClientSize要求当前窗口客户端rect是准确的,因为它使用它来计算新的客户端rect。 由于不是这样,因此表单的宽度设置错误,其余的就是历史记录。
哦,您想知道为什么宽度受到影响而不是高度受到影响吗?这很容易证明-事实证明,后ClientWidth设置,但之前ClientHeight设置,另一个WM_NCCALCSIZE发送-这个时间wParam的TRUE。VCL样式正确处理它,并将客户端大小设置回正确的值-因此,ClientHeight因此计算正确。
请注意,Windows的未来版本可能会更严重地损坏:如果Microsoft决定更定期地发送设置为的WM_NCCALCSIZE消息,即使该窗体可见,则对于VCL来说,情况将非常严重。wParamFALSE
该错误很容易通过手动发送WM_NCCALCSIZE到表单来证明。重现步骤:
TButton控件添加到窗体。将以下代码添加到按钮的OnClick事件中:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
// Compute the current cumulative width of the form borders:
int CurrentNonClientWidth = Width - ClientWidth;
// Get the current rectangle for the form:
TRect rect;
::GetWindowRect(Handle, &rect);
// Ask the window to calculate client area from the window rect:
SendMessage(Handle, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
// Calculate the new non-client area given by WM_NCCALCSIZE. It *should*
// match the value of CurrentNonClientWidth.
int NewNonClientWidth = Width - rect.Width();
if (CurrentNonClientWidth == NewNonClientWidth) {
ShowMessage("Test pass: WM_NCCALCSIZE with wParam FALSE gave "
"the right result.");
} else {
ShowMessage(UnicodeString::Format(L"Test fail: WM_NCCALCSIZE with "
"wParam FALSE gave a different result.\r\n\r\nCurrent NC width: %d"
"\r\n\r\nNew NC width: %d", ARRAYOFCONST((
CurrentNonClientWidth, NewNonClientWidth))));
}
}
Run Code Online (Sandbox Code Playgroud)运行项目,然后单击按钮。如果通过测试,则表示VCL样式的NC宽度恰好与默认的Windows NC宽度一致。更改窗体的边框样式或将VCL样式更改为其他样式,然后重试。
当然,解决方法是找到一种方法来拦截“ is” WM_NCCALCSIZE消息,然后将其转换为“ is” 消息。实际上,这可以在全局范围内完成:我们可以从中派生出可解决问题的类,然后在全局范围内使用该钩子-这将解决所有窗体上的问题,包括VCL创建的窗体(例如,来自Vcl.Dialogs单元) )。在上面显示的示例项目中,如下修改主项目:wParamFALSEwParamTRUETFormStyleHookProject1.cpp
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <tchar.h>
#include <string.h>
#include <Vcl.Forms.hpp>
//---------------------------------------------------------------------------
#include <Vcl.Styles.hpp>
#include <Vcl.Themes.hpp>
USEFORM("Unit1.cpp", Form1);
//---------------------------------------------------------------------------
class TFixedFormStyleHook : public TFormStyleHook
{
public:
__fastcall virtual TFixedFormStyleHook(TWinControl* AControl)
: TFormStyleHook(AControl) {}
protected:
virtual void __fastcall WndProc(TMessage &Message)
{
if (Message.Msg == WM_NCCALCSIZE && !Message.WParam) {
// Convert message to format with WPARAM == TRUE due to VCL styles
// failure to handle it when WPARAM == FALSE. Note that currently,
// TFormStyleHook only ever makes use of rgrc[0] and the rest of the
// structure is ignored. (Which is a good thing, because that's all
// the information we have...)
NCCALCSIZE_PARAMS ncParams;
memset(&ncParams, 0, sizeof(ncParams));
ncParams.rgrc[0] = *reinterpret_cast<RECT*>(Message.LParam);
TMessage newMsg;
newMsg.Msg = WM_NCCALCSIZE;
newMsg.WParam = TRUE;
newMsg.LParam = reinterpret_cast<LPARAM>(&ncParams);
newMsg.Result = 0;
this->TFormStyleHook::WndProc(newMsg);
if (this->Handled) {
*reinterpret_cast<RECT*>(Message.LParam) = ncParams.rgrc[0];
Message.Result = 0;
}
} else {
this->TFormStyleHook::WndProc(Message);
}
}
};
//---------------------------------------------------------------------------
int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
// Register our style hook. An audit of C++ Builder XE8 VCL source code
// for registration of the existing TFormStyleHook shows that these are
// the only two classes we need to register for.
TCustomStyleEngine::RegisterStyleHook(__classid(TForm),
__classid(TFixedFormStyleHook));
TCustomStyleEngine::RegisterStyleHook(__classid(TCustomForm),
__classid(TFixedFormStyleHook));
Application->Initialize();
Application->MainFormOnTaskBar = true;
TStyleManager::TrySetStyle("Carbon");
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
return 0;
}
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
现在运行项目并单击按钮;您会看到WM_NCCALCSIZE现在已正确处理。您还将看到,如果ClientWidth在DFM文件中显式设置a ,则现在可以正确使用它了。
For those looking for a really clever solution for this very strange behaviour, take a look on the James Johnston answer. I've applied it on my project and it is working flawlessly. Below is the Delphi translation from the James answer. Thank you James!
program Solve;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1},
Windows,
Messages,
Vcl.Themes,
Vcl.Styles;
type
TFixedFormStyleHook = class(TFormStyleHook)
protected
procedure WndProc(var AMessage: TMessage); override;
end;
{ TFixedFormStyleHook }
procedure TFixedFormStyleHook.WndProc(var AMessage: TMessage);
var
NewMessage: TMessage;
ncParams: NCCALCSIZE_PARAMS;
begin
if (AMessage.Msg = WM_NCCALCSIZE) and (AMessage.WParam = 0) then
begin
// Convert message to format with WPARAM = TRUE due to VCL styles
// failure to handle it when WPARAM = FALSE. Note that currently,
// TFormStyleHook only ever makes use of rgrc[0] and the rest of the
// structure is ignored. (Which is a good thing, because that's all
// the information we have...)
ZeroMemory(@ncParams,SizeOf(NCCALCSIZE_PARAMS));
ncParams.rgrc[0] := TRect(Pointer(AMessage.LParam)^);
NewMessage.Msg := WM_NCCALCSIZE;
NewMessage.WParam := 1;
NewMessage.LParam := Integer(@ncParams);
NewMessage.Result := 0;
inherited WndProc(NewMessage);
if Handled then
begin
TRect(Pointer(AMessage.LParam)^) := ncParams.rgrc[0];
AMessage.Result := 0;
end;
end
else
inherited;
end;
{$R *.res}
begin
// Register our style hook. An audit of Delphi XE8 VCL source code
// for registration of the existing TFormStyleHook shows that these are
// the only two classes we need to register for.
TCustomStyleEngine.RegisterStyleHook(TForm,TFixedFormStyleHook);
TCustomStyleEngine.RegisterStyleHook(TCustomForm,TFixedFormStyleHook);
Application.Initialize;
Application.MainFormOnTaskbar := True;
TStyleManager.TrySetStyle('Glossy Light');
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Run Code Online (Sandbox Code Playgroud)
With this code, the ClientWidth / ClientHeight dimensions are respected and the inside contents are shown correctly. Of course the external size of Window will be bigger to accomodate the ClientWidth / ClientHeight dimensions, but this is not so bad because normally the window contents is more important.
You may want to put the code inside a separate unit to use it on any project. Here is only the direct raw solution.
| 归档时间: |
|
| 查看次数: |
3382 次 |
| 最近记录: |