【问题标题】:Drawing inside a window doesn't redraw and smears在窗口内绘图不会重绘和涂抹
【发布时间】:2014-12-10 02:26:47
【问题描述】:

我试图在窗口周围绘制一个矩形(窗口内部,而不是外部),但绘图有污点,在某些情况下不会重绘。

我将 HWND WndProc 子类化了(代码当然在 HWND 进程中运行):

class SubClasser : NativeWindow
{
 ...
    protected override void WndProc(ref Message m)
    {
        switch(m.Msg)
        {
            case 0x85: // WM_CPAINT
            case 0xf:  // WM_PAINT
            {
                base.WndProc(ref m);
                Rectangle r = GetWndRect(this.Handle);
                g.DrawRectangle(p, r);
                Trace.WriteLine("WM_PAINT: "+r.ToString());
            }
            break;

            default:
                Trace.WriteLine("0x" + m.Msg.ToString("X"));
                base.WndProc(ref m);
            break;
        }
    }
 ...
}

private Rectangle RECTtoRectangle(RECT r)
{
    return new Rectangle(r.Left, r.Top, r.Right, r.Bottom);
}
private Rectangle GetWndRect(IntPtr hwnd)
{
    RECT r = new RECT();
    GetClientRect(hwnd, out r);

    return RECTtoRectangle(r);
}

正如您在代码中看到的,我正在重绘 WM_PAINT 和 WM_CPAINT 上的“矩形”,但这还不够:

  • 当我使窗口变大(调整大小)时,矩形不会变大。
  • 当我缩小窗口(再次调整大小)时,矩形会变小,但是当我放大窗口时,矩形会拖到原来的大小(而不是更大)。
  • 当我将窗口置于可见屏幕之外时,矩形的一部分(仅底部)不会被重绘。

我应该指出,我确实收到了绘制消息,而且子类化似乎有效。

我真的被卡住了:-(

编辑:

哦,我也试过放置:

base.WndProc(ref m);

仅在 WndProc 结束时,得到了相同的结果。

【问题讨论】:

  • GetWndRect 到底发生了什么?
  • @Fratyx - 添加它:-) 谢谢!
  • 你的任务听起来更像WM_ERASEBKGND。阅读Using the WM_PAINT Message。您正在“跳过”许多 GDI 详细信息,例如 BeginPaint()/EndPaint()

标签: c# windows winapi gdi+


【解决方案1】:

我不确定,因为我无法测试它并且不知道成员变量g 的背景,但我认为这是剪切矩形的问题。因此,您可以在 WndProc 函数中尝试这样的操作,以确保重绘整个窗口:

case 0x05: // WM_SIZE
  InvalidateRect(this.Handle, GetWndRect(this.Handle), TRUE);
  break;

【讨论】:

    【解决方案2】:

    首先,您不能在一条消息上多次致电base.WndProc(ref m);

    其次,g 应在每次更改窗口大小时重新创建,以便可以在更大的表面上绘制。

    第三,在窗口上调用Invalidate 以在窗口大小更改时强制重绘。我使用SizeChanged 事件和ClientSize,因为我可以轻松编译代码(需要阅读文档来编写 GDI+/WINAPI 内容)。

    public partial class RedrawInWndProcForm : Form
    {
        public RedrawInWndProcForm()
        {
            InitializeComponent();
            p = new Pen(Color.Red, 2.0f);
            this.SizeChanged += (s, e) => { this.Invalidate(); };
        }
    
    
        Graphics g; 
        Pen p;
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case 0xf:  // WM_PAINT
                    {
                        g = Graphics.FromHwnd(this.Handle);
                        Rectangle r = GetWndRect(this.Handle);
                        g.DrawRectangle(p, r);
                        Trace.WriteLine("WM_PAINT: " + r.ToString());
                    }
                    break;
            }
            Trace.WriteLine("handled");
            base.WndProc(ref m);
        }
    
        private Rectangle GetWndRect(IntPtr hwnd)
        {
            return new Rectangle(0, 0, (int)this.ClientSize.Width, (int)this.ClientSize.Height);
        }
    }
    

    【讨论】:

      【解决方案3】:

      如果只需要画一个边框,那么我们可以有一个更简单的机制吗?

      1. 将调整大小时重绘设置为真。
      2. 覆盖OnPaint

      示例: 假设您要子分类的窗口是Form

          public partial class Form1 : Form
          {
              public Form1()
              {
                  InitializeComponent();
                  SetStyle(ControlStyles.ResizeRedraw, true); // this is important
              }
              protected override void OnPaint(PaintEventArgs e)
              {
                  base.OnPaint(e);
                  Rectangle rcBorder = e.ClipRectangle;
                  rcBorder.Inflate(-10, -10); // just to accentuate with red colored border
                  e.Graphics.DrawRectangle(Pens.Red, rcBorder); 
              }
          }
      

      【讨论】:

      • 感谢您的想法,但我将本机窗口(任何给定窗口)子类化
      • 抱歉,我完全错过了!
      【解决方案4】:

      感谢 kennyzx 和 Fratyx 的回答,我得到了一个可行的解决方案。

          protected override void WndProc(ref Message m)
          {
              base.WndProc(ref m);
      
              switch(m.Msg)
              {
                  case 0x85: // WM_CPAINT
                  case 0xf:  // WM_PAINT
                  {
                      g = Graphics.FromHwnd(this.Handle);
                      Rectangle r = GetWndRect(this.Handle);
                      g.DrawRectangle(p, r);
                      Trace.WriteLine("WM_PAINT: "+r.ToString());
                  }
                  break;
      
                  case 0x05: // WM_SIZE
                  {
                      InvalidateRect(this.Handle, IntPtr.Zero, true);
                      Trace.WriteLine("WM_SIZE");
                  }
                  break;
      
                  default:
                      Trace.WriteLine("0x" + m.Msg.ToString("X"));
                  break;
              }
          }
      

      注意base.WndProc在开头,因为我必须让应用程序先绘制,然后我需要绘制所以我会在上面。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-20
      • 2011-09-21
      • 1970-01-01
      • 2015-09-23
      • 1970-01-01
      • 2011-10-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多