将 TProgressBar 放置在 TStatusBar 上的方法不再有效
Method for placing a TProgressBar on a TStatusBar no longer works
过去我使用 here 中描述的方法将 TProgressBar 放置在 Delphi 中的 TStatusBar 上:
procedure TForm1.FormCreate(Sender: TObject);
var
ProgressBarStyle: integer;
begin
//enable status bar 2nd Panel custom drawing
StatusBar1.Panels[1].Style := psOwnerDraw;
//place the progress bar into the status bar
ProgressBar1.Parent := StatusBar1;
//remove progress bar border
ProgressBarStyle := GetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE);
ProgressBarStyle := ProgressBarStyle - WS_EX_STATICEDGE;
SetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE, ProgressBarStyle);
end;
procedure TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel;
const Rect: TRect);
begin
if Panel = StatusBar.Panels[1] then
with ProgressBar1 do
begin
Top := Rect.Top;
Left := Rect.Left;
Width := Rect.Right - Rect.Left;
Height := Rect.Bottom - Rect.Top;
end;
end;
但是(在最近的 Windows 更新之后?)这不再有效,即旧程序仍然按预期工作,但新编译的程序却不行。我在 Windows 10.
上使用相同的 Delphi 版本 XE8
这是否意味着这种方法不合适?正确的做法是什么?
如果您删除处理边框的线条,它会起作用:
// remove these lines
ProgressBarStyle := GetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE);
ProgressBarStyle := ProgressBarStyle - WS_EX_STATICEDGE;
SetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE, ProgressBarStyle);
生成的双边框看起来不太好,因此 David 在 OnDrawPanel 中调用 FillRect 的解决方案可能是更好的解决方案。这有一个额外的好处,你终于可以摆脱那个丑陋的绿色:-)。
procedure TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel;
const Rect: TRect);
var
R: TRect;
begin
if Panel = StatusBar.Panels[1] then
begin
StatusBar.Canvas.Brush.Color := clBtnFace;
StatusBar.Canvas.FillRect(Rect);
R := Rect;
R.Right := Round(R.Left + (R.Right - R.Left) * FProgress {0..1});
StatusBar.Canvas.Brush.Color := clGrayText;
StatusBar.Canvas.FillRect(R);
end;
end;
注意:您必须调用 StatusBar 的 Invalidate
方法,以便执行 ONDrawPanel 事件处理程序。
我对行为变化唯一明显的解释是这段代码是错误的:
ProgressBarStyle := ProgressBarStyle - WS_EX_STATICEDGE;
此代码假定 WS_EX_STATICEDGE
已在样式中。但如果不是,那么你正在破坏 window 风格。该代码需要使用位运算:
ProgressBarStyle := ProgressBarStyle and not WS_EX_STATICEDGE;
另请注意,如果重新创建 window,此 window 样式将丢失,这在 VCL 下确实会发生。更好的选择是子class 进度条class 并直接在覆盖CreateParams
中设置样式。
正如其他人所解释的那样,您对 TProgressBar
的 window 样式的管理不善是造成问题的原因。
我想补充一点,您根本不需要(也不应该使用)TStatusBar.OnDrawPanel
事件来定位 TProgressBar
。它是绘图事件,而不是对象管理事件。如果您不打算在 TStatusBar.Canvas
上手动绘制进度条,那么您应该完全摆脱 OnDrawPanel
处理程序。
您可以在启动时定位 TProgressBar
一次,方法是使用 SB_GETRECT
消息获取面板的坐标和尺寸,然后相应地定位 TProgressBar
,例如:
uses
CommCtrl;
procedure TForm1.FormCreate(Sender: TObject);
var
...
R: TRect;
begin
// no need to set the panel's Style to psOwnerDraw!
...
//place the progress bar into the status bar
SendMessage(StatusBar1.Handle, SB_GETRECT, 1, LPARAM(@R));
ProgressBar1.Parent := StatusBar1;
ProgressBar1.SetBounds(R.Left, R.Top, R.Width, R.Height);
...
end;
如果您的表单可以调整大小,您可以使用 TStatusBar.OnResize
事件在面板调整大小时重新定位 TProgressBar
:
uses
CommCtrl;
procedure TForm1.FormCreate(Sender: TObject);
begin
// no need to set the panel's Style to psOwnerDraw!
...
//place the progress bar into the status bar
ProgressBar1.Parent := StatusBar1;
StatusBar1Resize(nil);
...
end;
procedure TForm1.StatusBar1Resize(Sender: TObject);
var
R: TRect;
begin
//place the progress bar over the 2nd panel
SendMessage(StatusBar1.Handle, SB_GETRECT, 1, LPARAM(@R));
ProgressBar1.SetBounds(R.Left, R.Top, R.Width, R.Height);
end;
过去我使用 here 中描述的方法将 TProgressBar 放置在 Delphi 中的 TStatusBar 上:
procedure TForm1.FormCreate(Sender: TObject);
var
ProgressBarStyle: integer;
begin
//enable status bar 2nd Panel custom drawing
StatusBar1.Panels[1].Style := psOwnerDraw;
//place the progress bar into the status bar
ProgressBar1.Parent := StatusBar1;
//remove progress bar border
ProgressBarStyle := GetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE);
ProgressBarStyle := ProgressBarStyle - WS_EX_STATICEDGE;
SetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE, ProgressBarStyle);
end;
procedure TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel;
const Rect: TRect);
begin
if Panel = StatusBar.Panels[1] then
with ProgressBar1 do
begin
Top := Rect.Top;
Left := Rect.Left;
Width := Rect.Right - Rect.Left;
Height := Rect.Bottom - Rect.Top;
end;
end;
但是(在最近的 Windows 更新之后?)这不再有效,即旧程序仍然按预期工作,但新编译的程序却不行。我在 Windows 10.
上使用相同的 Delphi 版本 XE8这是否意味着这种方法不合适?正确的做法是什么?
如果您删除处理边框的线条,它会起作用:
// remove these lines
ProgressBarStyle := GetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE);
ProgressBarStyle := ProgressBarStyle - WS_EX_STATICEDGE;
SetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE, ProgressBarStyle);
生成的双边框看起来不太好,因此 David 在 OnDrawPanel 中调用 FillRect 的解决方案可能是更好的解决方案。这有一个额外的好处,你终于可以摆脱那个丑陋的绿色:-)。
procedure TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel;
const Rect: TRect);
var
R: TRect;
begin
if Panel = StatusBar.Panels[1] then
begin
StatusBar.Canvas.Brush.Color := clBtnFace;
StatusBar.Canvas.FillRect(Rect);
R := Rect;
R.Right := Round(R.Left + (R.Right - R.Left) * FProgress {0..1});
StatusBar.Canvas.Brush.Color := clGrayText;
StatusBar.Canvas.FillRect(R);
end;
end;
注意:您必须调用 StatusBar 的 Invalidate
方法,以便执行 ONDrawPanel 事件处理程序。
我对行为变化唯一明显的解释是这段代码是错误的:
ProgressBarStyle := ProgressBarStyle - WS_EX_STATICEDGE;
此代码假定 WS_EX_STATICEDGE
已在样式中。但如果不是,那么你正在破坏 window 风格。该代码需要使用位运算:
ProgressBarStyle := ProgressBarStyle and not WS_EX_STATICEDGE;
另请注意,如果重新创建 window,此 window 样式将丢失,这在 VCL 下确实会发生。更好的选择是子class 进度条class 并直接在覆盖CreateParams
中设置样式。
正如其他人所解释的那样,您对 TProgressBar
的 window 样式的管理不善是造成问题的原因。
我想补充一点,您根本不需要(也不应该使用)TStatusBar.OnDrawPanel
事件来定位 TProgressBar
。它是绘图事件,而不是对象管理事件。如果您不打算在 TStatusBar.Canvas
上手动绘制进度条,那么您应该完全摆脱 OnDrawPanel
处理程序。
您可以在启动时定位 TProgressBar
一次,方法是使用 SB_GETRECT
消息获取面板的坐标和尺寸,然后相应地定位 TProgressBar
,例如:
uses
CommCtrl;
procedure TForm1.FormCreate(Sender: TObject);
var
...
R: TRect;
begin
// no need to set the panel's Style to psOwnerDraw!
...
//place the progress bar into the status bar
SendMessage(StatusBar1.Handle, SB_GETRECT, 1, LPARAM(@R));
ProgressBar1.Parent := StatusBar1;
ProgressBar1.SetBounds(R.Left, R.Top, R.Width, R.Height);
...
end;
如果您的表单可以调整大小,您可以使用 TStatusBar.OnResize
事件在面板调整大小时重新定位 TProgressBar
:
uses
CommCtrl;
procedure TForm1.FormCreate(Sender: TObject);
begin
// no need to set the panel's Style to psOwnerDraw!
...
//place the progress bar into the status bar
ProgressBar1.Parent := StatusBar1;
StatusBar1Resize(nil);
...
end;
procedure TForm1.StatusBar1Resize(Sender: TObject);
var
R: TRect;
begin
//place the progress bar over the 2nd panel
SendMessage(StatusBar1.Handle, SB_GETRECT, 1, LPARAM(@R));
ProgressBar1.SetBounds(R.Left, R.Top, R.Width, R.Height);
end;