【问题标题】:how to disable WM_ONPAINT message to be recieved by my window in C++ Builder?如何在 C++ Builder 中禁用我的窗口接收的 WM_PAINT 消息?
【发布时间】:2011-04-21 18:17:39
【问题描述】:

我正在窗口上的另一个线程中进行绘画,当我的表单收到 WM_PAINT(或 WM_ERASE...)时,表单被擦除以开始绘画。我需要阻止此消息从我的窗口接收。如何做到这一点?

当收到 WM_APP+1 消息时,此代码只是在主窗体上以字符串类型绘制当前时间和星期几。

void __fastcall TForm1::handleCustomMessage(TMessage &Message)
{

    static long id = 0;

    static DWORD sttick = 0;
    char tm[1024];
    DWORD curtick = GetTickCount()-sttick;
    SYSTEMTIME st;
    GetLocalTime(&st);
    static char sep[2] = ":";

    curtick = GetTickCount();
    if (curtick >= sttick+300)
    {
        sttick = curtick;
        sep[0] = (sep[0] == 0x20?':':0x20);
    }
    sprintf(tm, "%02d%s%02d%s%02d", st.wHour, sep, st.wMinute, sep, st.wSecond);
    // cnv->TextOutA(0, 0, tm);
    PAINTSTRUCT ps;
    // BeginPaint(hwnd,&ps);
    // if (!TextOutA(cnv->Handle, 0, 500, tm, strlen(tm)))
    // deb("textout: %s", fmterr());

    LOGFONT logFont;

    logFont.lfHeight = -(0.5 + 1.0 * Form1->Font->Size * 96 / 72);
    logFont.lfWidth = 0;
    logFont.lfEscapement = 0;
    logFont.lfOrientation = 0;
    logFont.lfWeight = Form1->Font->Style.Contains(fsBold) ? 700:400;
    logFont.lfItalic = Form1->Font->Style.Contains(fsItalic) ? TRUE:FALSE;
    logFont.lfUnderline = Form1->Font->Style.Contains(fsUnderline) ? TRUE:FALSE;
    logFont.lfStrikeOut = Form1->Font->Style.Contains(fsStrikeOut) ? TRUE:FALSE;
    logFont.lfCharSet = DEFAULT_CHARSET;
    logFont.lfOutPrecision = OUT_TT_PRECIS;
    logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
    logFont.lfQuality = NONANTIALIASED_QUALITY; // 1
    logFont.lfPitchAndFamily = FIXED_PITCH;
    char str[2035];
    strcpy(logFont.lfFaceName, deunicode(Form1->Font->Name.c_str(), str, sizeof(str)));
    HFONT hFont = NULL, hFontOld = NULL;
    hFont = CreateFontIndirect(&logFont);
    if (!hFont)
        deb("failed to create font: %s", fmterr());
    // HDC dc = GetWindowDC(Form1->Handle);
    hFontOld = (HFONT)SelectObject(Form1->pb->Canvas->Handle, hFont); // 2
    // Form1->Canvas->TextOut(0, 0, tm);
    //
    Form1->pb->Canvas->Font->Color = clWhite;
    Form1->pb->Canvas->Brush->Style = bsClear;
    // Form1->pb->Canvas->TextOutA(1,1,tm);
    // deb("out %s",tm);
    SetTextColor(Form1->pb->Canvas->Handle, clWhite);

    RECT rect;
    rect.left = 0;
    rect.right = Form1->pb->Width;
    rect.top = 1;
    rect.bottom = Form1->pb->Height;
    // FillRect(Form1->pb->Canvas->Handle,&rect, (HBRUSH) (COLOR_WINDOW+1));
    if(!TextOutA(Form1->pb->Canvas->Handle, 3,0,tm,strlen(tm)))
        deb("textout: %s",fmterr());
    //LockWindowUpdate(NULL);
    //int ret = DrawText(Form1->pb->Canvas->Handle, tm, strlen(tm), &rect, DT_CENTER|DT_NOCLIP|DT_EDITCONTROL);
    //if (!ret)
    //    deb("drawtext: %s", fmterr());
    // Form1->tedit->Text = tm;
    switch(st.wDayOfWeek)
    {
        case 1:
        strcpy(tm, "Понедельник");
        break;
        case 2:
        strcpy(tm, "Вторник");
        break;
        case 3:
        strcpy(tm, "Среда");
        break;
        case 4:
        strcpy(tm, "Четверг");
        break;
        case 5:
        strcpy(tm, "Пятница");
        break;
        case 6:
        strcpy(tm, "Суббота");
        break;
        case 7:
        strcpy(tm, "Воскресенье");
        break;
    }
    rect.top = Form1->Canvas->TextHeight(tm)+1;

    DeleteObject(hFont);
    logFont.lfHeight = -(0.5 + 1.0 * Form1->Font->Size * 96 / 90);
    logFont.lfWidth = 0;
    logFont.lfEscapement = 0;
    logFont.lfOrientation = 0;
    logFont.lfWeight = Form1->Font->Style.Contains(fsBold) ? 700:400;
    logFont.lfItalic = Form1->Font->Style.Contains(fsItalic) ? TRUE:FALSE;
    logFont.lfUnderline = Form1->Font->Style.Contains(fsUnderline) ? TRUE:FALSE;
    logFont.lfStrikeOut = Form1->Font->Style.Contains(fsStrikeOut) ? TRUE:FALSE;
    logFont.lfCharSet = DEFAULT_CHARSET;
    logFont.lfOutPrecision = OUT_TT_PRECIS;
    logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
    logFont.lfQuality = NONANTIALIASED_QUALITY; // 1
    logFont.lfPitchAndFamily = FIXED_PITCH;

    strcpy(logFont.lfFaceName, "tahoma");

    hFont = CreateFontIndirect(&logFont);
    if (!hFont)
        deb("failed to create font: %s", fmterr());
    // HDC dc = GetWindowDC(Form1->Handle);
    hFontOld = (HFONT)SelectObject(Form1->pb->Canvas->Handle, hFont);
    //if(!TextOutA(Form1->pb->Canvas->Handle, 0,Form1->Canvas->TextHeight(tm)+1,tm,strlen(tm)))
    //    deb("textout: %s",fmterr());
    int ret = DrawText(Form1->pb->Canvas->Handle, tm, strlen(tm), &rect, DT_CENTER|DT_NOCLIP|DT_EDITCONTROL);
    if (!ret)
        deb("drawtext: %s", fmterr());
    // Form1->dedit->Text = tm;
    // SelectObject(dc, hFontOld);
    // ReleaseDC(Form1->tedit->Handle,dc);
    DeleteObject(hFont);
    SelectObject(Form1->pb->Canvas->Handle, hFontOld);

【问题讨论】:

  • 你能给我们一些细节吗?我会评论说,绘画需要在 VCL 应用程序的主线程中完成。
  • 不一定是大卫,甚至可以从另一个进程完成。就像 DirectShow 正在画视频一样。
  • 是的,我正在使用 Synchronize() 函数在线程中执行此操作,但即使没有此功能,在画布上绘画也是线程安全的。每次我有新数据要绘制时,我都会绘制,但我得到了闪烁效果。在此之后我发现这是因为窗口接收到 WM_PAINT 或 WM_ERASEBACKGROUND 消息,而我的线程在大约 100-200 毫秒内没有任何东西可以绘制,所以表格是刚刚删除。
  • 如果您需要绘画,只需调用 InvalidateRect ,他们就会处理随后的绘画消息。如果您忽略绘图消息,您将遇到空白窗口和闪烁。
  • 不要从单独的线程在画布上绘画!它不是线程安全的。处理此问题的正确方法是让线程将其更新绘制到内存中的位图,然后让WM_PAINT 处理程序绘制位图中当前的任何内容。

标签: c++ windows winapi c++builder


【解决方案1】:

假设您有一个 WIn32 应用程序,那么您将在某个地方有类似的代码: 在代码中查找我的 cmets。

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;

    switch (message)
    {
    case WM_COMMAND:
        wmId    = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    case WM_ERASEBKGND: // NOT ONLY ON PAINT BUT EVEN HERE
    case WM_PAINT:
            // DO NOTHING
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

我不知道 CBuilder,但 Win32 应用程序都有一个 Window 消息处理程序。您必须找到它是如何完成的以及在您的表单中的哪个位置,然后捕获这两条消息并且什么都不做。

【讨论】:

  • 谢谢 Marino 是的,这是我需要的,但我不知道如何在我的函数中做 DO_NOTHING 来捕获 WM_PAINT 和 WM_ERASEBKGN,因为在 C++ Builder 中它没有参数并且没有返回值!跨度>
  • 这不是真的。您可以完全访问消息参数和返回值。只需覆盖虚拟TForm::WndProc() 方法。它传递了一个TMessage 结构,其中包含信息。要“什么都不做”,只需不调用基类WndProc() 方法即可。
  • -1 表示未从 WM_PAINT 处理程序调用 BeginPaint/EndPaint。
  • 克里斯,不调用 BeginPaint 可能是一个设计决策,具体取决于您的需要。事实上,我有一个电影播放器​​,它不仅不调用 BeginPaint,甚至还调用整个区域的 ValidateRect。你可能会问为什么。因为另一个进程正在表单上绘制,并且如果另一个窗口将在表单上移动,则标准窗口无效将创建视频伪影。所以你给了我一个-1,因为我已经非常了解并且不是一成不变的......
【解决方案2】:

忽略C++ builder的canvas对象的线程安全问题...

您应该始终使用正确的 WM_PAINT 方法进行绘画。虽然 Windows 7 上的窗口管理器可以在 BeginPaint/EndPaint 之外进行绘制,但它会调用纯粹为了向后兼容而存在的低性能代码路径。

此外,用户可以触发许多异步事件(通过移动窗口等),这些事件会使窗口区域无效,如果窗口没有被 WM_PAINT 更新,那么在窗口被更新之前会有一个可见的延迟重新粉刷。

桌面窗口管理器(实现 Aero Glass)确实需要您使用正确的方法来重绘动画窗口:使用调用 InvalidateRect 的计时器来使内容变化的区域无效:并且只需处理 WM_PAINT 来处理重绘新状态。

【讨论】:

    【解决方案3】:

    您可以使用类似这样的方式有条件地覆盖绘制事件

    
    protected virtual void OnPaint(PaintEventArgs e)
    {
    // Paint the control
    // ...
    // Raise the Paint event.
    if (Paint != null)
    Paint(this, e);
    }
    

    当然这不是 C++ 代码,但想法是,在 C 构建器中,您可以单击表单的事件选项卡,并且应该有一个名为 onPaint 的事件,一旦双击绘制就可以被禁用。我使用 Delphi 和 C++ Builder 是同一家公司,所以可以应用相同的想法。但这可能不是解决方案..

    一般来说,这个令人讨厌的事情不是重绘,而是重绘导致闪烁的背景。您可以在表单上启用使用双缓冲来防止一些闪烁。或者尝试执行erasebkgnd 事件,然后愚弄windows 以为它已被擦除。

    
    private:
    void __fastcall WMEraseBkgnd(TWMEraseBkgnd &Message);
    
    

    void __fastcall TParentForm::WMEraseBkgnd(TWMEraseBkgnd& Message) { Message.Result = true; // We didn't erase the background but we tell Windows we did }

    【讨论】:

    • 在 CBuilder 中我无法接收绘制参数或返回值。或者我不知道该怎么做..
    • 谢谢,在答案上写了更多。
    猜你喜欢
    • 2010-11-01
    • 2020-10-22
    • 2022-11-12
    • 2013-05-03
    • 2010-10-30
    • 2023-02-24
    • 2013-10-03
    • 2016-01-11
    • 1970-01-01
    相关资源
    最近更新 更多