【问题标题】:Why would FillRect not draw when LineTo succeeds?为什么 LineTo 成功时 FillRect 不绘制?
【发布时间】:2015-06-13 16:30:16
【问题描述】:

我正在尝试change the background color of a DateTimePicker,但我的问题与我正在尝试做的事情无关。

我正在捕捉一个窗口的WM_PAINT 消息,让默认的绘图实现发生(即 ComCtrl.dll 中的那个),然后随之而来并在其顶部涂鸦。

最初我的代码很简单:

TDateTimePicker = class(Vcl.ComCtrls.TDateTimePicker)
protected
   procedure WMPaint(var Message: TMessage); message WM_PAINT;
end;

procedure TDateTimePicker.WMPaint(var Message: TMessage);
begin
   inherited;
end;

我什么都不做,控件正常绘制:

现在有趣的开始

现在我将进行一些实际的绘图。这不是我想要的图,但它证明了它的工作原理。我会在控件的矩形上画一个纵横交错

procedure TDateTimePicker.WMPaint(var Message: TMessage);
var
    dc: HDC;
    rc: TRect;
    p: HPEN;
begin
    inherited;

    //Get the device context to scribble on    
    dc := GetDC(Self.Handle);
    if dc = 0 then
        Exit;
    try
        rc := Self.GetClientRect;

        //Create a pen to draw a criss-cross
        p := CreatePen(PS_SOLID, 0, ColorToRGB(clLime));
        p := SelectObject(dc, p); //select the pen into the dc
        Winapi.Windows.MoveToEx(dc, rc.Left, rc.Top, nil);
        Winapi.Windows.LineTo(dc, rc.Right, rc.Bottom);
        Winapi.Windows.MoveToEx(dc, rc.Right, rc.Top, nil);
        Winapi.Windows.LineTo(dc, rc.Left, rc.Bottom);
        P := SelectObject(dc, p); //restore old pen
        DeleteObject(p); //delete our pen
    finally
        ReleaseDC(Self.Handle, dc);
    end;
end;

这很简单:

  • 获取我们将使用的HDC
  • 获取控件的客户端矩形
  • 制作一支纯红色的笔
  • 选择笔进入DC
  • 画十字
  • 恢复旧笔
  • 删除我们的笔

而且它有效!

当然可以。

现在填充整个矩形

我不想画十字,我想填充背景。首先,我将演示一种使用可怕的可怕方法来实现我的目标的方法:

我会用很粗的笔画出控件的宽度

这是一件可怕的事情,但它具有实际工作的优点:

procedure TDateTimePicker.WMPaint(var Message: TMessage);
var
    dc: HDC;
    rc: TRect;
    p: HPEN;
begin
    inherited;

    dc := GetDC(Self.Handle);
    if dc = 0 then
        Exit;
    try
        rc := Self.GetClientRect;

        //Fill a rectangle using a pen (cause FillRect doesn't work)
        p := CreatePen(PS_SOLID, rc.Height, ColorToRGB(clRed));
        p := SelectObject(dc, p);
        Winapi.Windows.MoveToEx(dc, rc.Left, (rc.Bottom+rc.Top) div 2, nil); //middle of left edge
        Winapi.Windows.LineTo(dc, rc.Right, (rc.Bottom+rc.Top) div 2); //middle of right edge
        P := SelectObject(dc, p); //restore old pen
        DeleteObject(p); //delete our pen


        //Create a pen to draw a criss-cross
        p := CreatePen(PS_SOLID, 0, ColorToRGB(clLime));
        p := SelectObject(dc, p); //select the pen into the dc
        Winapi.Windows.MoveToEx(dc, rc.Left, rc.Top, nil);
        Winapi.Windows.LineTo(dc, rc.Right, rc.Bottom);
        Winapi.Windows.MoveToEx(dc, rc.Right, rc.Top, nil);
        Winapi.Windows.LineTo(dc, rc.Left, rc.Bottom);
        P := SelectObject(dc, p); //restore old pen
        DeleteObject(p); //delete our pen
    finally
        ReleaseDC(Self.Handle, dc);
    end;
end;

这很简单:

  • 创建一支 23 像素高的笔
  • 从左到右划过选择器的整个宽度

它有效:

当然可以!

但我不想消灭一切

我不想删除 datetimepicker 中的所有内容,只删除“客户”区域。所以我调整了矩形:

  • 从顶部、左侧和底部减去 2 个像素
  • 为 datetimepicker 下拉按钮从右边缘减去 34 个像素

使用代码 sn-p:

rc := Self.GetClientRect;

//rc := GetRectOfThePartIWant;
rc.Left := 2;
rc.Top := 2;
rc.Bottom := rc.Bottom-2;
rc.Right := rc.Right-34; //button width is 34 (use DateTime_GetDateTimePickerInfo.rcButton)

//Fill a rectangle using a pen (cause FillRect doesn't work)
//p := CreatePen(PS_SOLID, rc.Height, ColorToRGB(clRed));
br := Default(TLogBrush);
br.lbStyle := BS_SOLID;
br.lbColor := ColorToRGB($00CCCCFF);
br.lbHatch := 0; //ignored for a BS_SOLID brush
p := ExtCreatePen(PS_SOLID or PS_GEOMETRIC or PS_ENDCAP_FLAT, rc.Height, br, 0, nil);
if p <> 0 then
begin
    p := SelectObject(dc, p);
    Winapi.Windows.MoveToEx(dc, rc.Left, (rc.Bottom+rc.Top) div 2, nil); //middle of left edge
    Winapi.Windows.LineTo(dc, rc.Right, (rc.Bottom+rc.Top) div 2); //middle of right edge
    P := SelectObject(dc, p); //restore old pen
    DeleteObject(p); //delete our pen
end;

它有效:

当然可以!

FillRect 有什么问题?

原来我只是用FillRect,只是它坚持只画白色;而不是任何颜色:

procedure TDateTimePicker.WMPaint(var Message: TMessage);
var
    dc: HDC;
    rc: TRect;
    br: TLogBrush;
    b: HBRUSH;
    le: Integer;
    p: HPEN;
begin
    inherited;

    dc := GetDC(Self.Handle);
    if dc = 0 then
        Exit;
    try
        rc := Self.GetClientRect;

        b := CreateSolidBrush(ColorToRGB(clRed));
        if b <> 0 then
        begin
            b := SelectObject(dc, b); //select the brush into the DC
            if b <> 0 then
            begin
                le := FillRect(dc, rc, b);
                if le = 0 then
                begin
                    //Draw failed
                    if IsDebuggerPresent then
                        DebugBreak;
                end;
                SelectObject(dc, b); //restore the old brush
            end;
            DeleteObject(b);
        end;

        //Create a pen to draw a criss-cross
        p := CreatePen(PS_SOLID, 0, ColorToRGB(clLime));
        p := SelectObject(dc, p); //select the pen into the dc
        Winapi.Windows.MoveToEx(dc, rc.Left, rc.Top, nil);
        Winapi.Windows.LineTo(dc, rc.Right, rc.Bottom);
        Winapi.Windows.MoveToEx(dc, rc.Right, rc.Top, nil);
        Winapi.Windows.LineTo(dc, rc.Left, rc.Bottom);
        P := SelectObject(dc, p); //restore old pen
        DeleteObject(p); //delete our pen
    finally
        ReleaseDC(Self.Handle, dc);
    end;
end;

它不起作用:

当然不行。它试图让我的生活变得困难。如果它有效,那么我就不会花 9 个小时在上面了。

我尝试只填充矩形的上半部分;确保我的来源是正确的:

        rc := Self.GetClientRect;
        rc2 := rc;
        rc2.Bottom := (rc2.Top + rc2.Bottom) div 2;

        b := CreateSolidBrush(ColorToRGB(clRed));
        if b <> 0 then
        begin
            b := SelectObject(dc, b); //select the brush into the DC
            if b <> 0 then
            begin
                le := FillRect(dc, rc2, b);
                if le = 0 then
                begin
                    //Draw failed
                    if IsDebuggerPresent then
                        DebugBreak;
                end;
                SelectObject(dc, b); //restore the old brush
            end;
            DeleteObject(b);
        end;

        //Create a pen to draw a criss-cross
        p := CreatePen(PS_SOLID, 0, ColorToRGB(clLime));
        p := SelectObject(dc, p); //select the pen into the dc
        Winapi.Windows.MoveToEx(dc, rc.Left, rc.Top, nil);
        Winapi.Windows.LineTo(dc, rc.Right, rc.Bottom);
        Winapi.Windows.MoveToEx(dc, rc.Right, rc.Top, nil);
        Winapi.Windows.LineTo(dc, rc.Left, rc.Bottom);
        P := SelectObject(dc, p); //restore old pen
        DeleteObject(p); //delete our pen

它不起作用:

当然不行。

为什么它不起作用?

为什么它不起作用?

奖金聊天

您不能使用 Delphi Styles Engine,because the Styles Engine is not enabled when using Windows themes(仅在使用自定义主题时)。

【问题讨论】:

  • @DavidHeffernan 我的第一次尝试是处理WM_ERASEBKGND 来擦除背景。不幸的是,客户端绘制覆盖了整个背景。所以我唯一的选择是在默认油漆之后之后涂鸦。

标签: delphi winapi gdi delphi-xe6 common-controls


【解决方案1】:
    b := CreateSolidBrush(ColorToRGB(clRed));
    if b <> 0 then
    begin
        b := // *** original brush gets overwritten here ***
             SelectObject(dc, b); //select the brush into the DC
        if b <> 0 then
        begin
            le := FillRect(dc, rc, b);

您不需要将画笔选择到设备上下文中,因为您将它作为参数传递。然后选择它,将返回的值分配回画笔变量,然后使用错误的画笔参数执行FillRect(这就是它据称不起作用的原因)。

【讨论】:

  • 更准确地说,SelectObject() 返回之前选择到您的 DC 中的对象。如果这是您对给定对象类型的第一次调用 SelectObject(),您将获得 DC 的默认对象。 (当你完成对restore the DC to its initial state 的绘图时使用它。)所以在覆盖b 之后,你正在使用DC 的初始画笔进行绘制,它恰好是白色的。当然,我看你已经知道这一切了;您的错误只是无意中重用了b
  • 我什至让同事校对了我的问题,所以我看起来不像个白痴!至少观察到的行为是完全合理的!
  • 很少有关于单行答案的冗长而详细的问题。更多情况下,它[预计]是相反的。
猜你喜欢
  • 2020-03-18
  • 1970-01-01
  • 1970-01-01
  • 2013-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-17
相关资源
最近更新 更多