在 OnPaint() 事件中,如何检查给定的矩形是不是与无效区域相交?

Posted

技术标签:

【中文标题】在 OnPaint() 事件中,如何检查给定的矩形是不是与无效区域相交?【英文标题】:In OnPaint() event, how to check if a given rectangle intersects with the invalidated region?在 OnPaint() 事件中,如何检查给定的矩形是否与无效区域相交? 【发布时间】:2022-01-06 06:38:39 【问题描述】:

在 Winforms 中,可以创建一个不是矩形的区域并以这种方式使其无效:

Region region = new Region(new Rectangle(...));
region.Union(new Rectangle(...));                   
Invalidate(region);     

那么在OnPaint()事件中,只会再次绘制上面失效的区域:

protected override void OnPaint(PaintEventArgs e)
   
    //will only repaint the region invalidated above
    //even if ClipRectangle area is bigger than that
    e.Graphics.FillRectangle(Brushes.Blue, e.ClipRectangle); 

OnPaint() 事件中,有没有办法检查给定的矩形是否与无效区域相交?

我可以使用rectangle.Intersect(e.ClipRectangle),但这可能会产生误报。

编辑:似乎我想要的可以使用GetUpdateRgn Win32 函数(AFAIK 没有直接等效于该函数的 Winforms)

【问题讨论】:

在阅读了关于 Control.Invalidate 和 Control.Paint Event 的页面后,我不清楚实际上只有 Invalidate 中指定的区域被重新绘制。似乎有可能在多次调用 Invalidate 之后,重新绘制一个包含所有指定区域的矩形。这可以解释为什么 PaintEventArgs 包含一个矩形而不是一个更通用的区域。你对此有更深入的了解吗? 在 OnPaint() 中使用随机颜色绘制清楚地表明只有无效区域被重新绘制(而不是 FillRectangle 或其他绘制函数参数中指定的区域)。 我明白了,这很有趣。在这种情况下,您可能必须跟踪您无效的所有矩形。如果在引发 Paint 事件时设置了它们,则必须单独检查与所有这些矩形的交集。如果有任何相交,则重新绘制图形。 【参考方案1】:
private List<Rectangle> rectangleList;    

当你无效时,你设置你的 rectangleList:

rectangleList = getRectangles(...);
Region region = new Region(new Rectangle(...));
foreach(Rectangle rect in rectangleList)

    region.Union(rect);  
                 
Invalidate(region);     

在paint方法中,检查矩形列表中是否有任何矩形相交,然后将其清除:

protected override void OnPaint(PaintEventArgs e)
   
    bool intersection = false;
    foreach(Rectangle rect in rectangleList)
    
        if(e.ClipRectangle.Intersect(rect) 
        
            intersection = true;
            break;
        
     

    if(intersection)
    
        rectangleList.Clear();
        DoIntersectionStuff();
    
    else
    
        DoNonIntersectionStuff();
    

【讨论】:

如果 Windows 向 Winforms 应用程序发送 WM_PAINT 消息怎么办?某些区域不会被绘制,因为 rectangleList 将为空。 在相交的情况下你想实现什么,如果不相交怎么办? 我想避免/跳过对 DrawText() 的一些不需要的调用(ClipRectangle 很大,但只需要重新绘制几个区域) 在什么情况下?我相信我给了你一个好主意,如何确定你的特定无效区域是否与重绘的矩形相交,所以我相信我回答了这个问题。在确定这些区域是否相交之后,您所做的事情超出了问题的范围。我相应地调整了代码。【参考方案2】:

我回答我自己的问题:

在调用BeginPaint() 之前,可以通过在WM_PAINT 事件中调用GetUpdateRgn() 函数来获取更新区域。 要知道矩形是否在区域内,使用IsVisible() 方法。这是一个 GDI api 调用(与 Rectangle.Intersect() 不同),因此它通常与直接调用 GDI 绘图函数一样慢(例如:DrawText())并在必要时让 GDI 完成丢弃工作。

private Region region;    
protected override void WndProc(ref Message m) 
   
    switch (m.Msg)
    
        case WM_PAINT:
            region = null;
            IntPtr hrgn = CreateRectRgn(0, 0, 0, 0);
            try
            
                int result = GetUpdateRgn(Handle, hrgn, false);
                if (result == SIMPLEREGION || region == COMPLEXREGION)
                
                    region = Region.FromHrgn(hrgn); 
                
            
            finally
            
                DeleteObject(hrgn);
            
            break;                
     
    base.WndProc(ref m);


protected override void OnPaint(PaintEventArgs e)
       
    var rectangle = ...
    if (region != null && region.IsVisible(rectangle))
    
        //...
    

这是原生的 win32 函数声明:

[DllImport("gdi32.dll")]
static extern IntPtr CreateRectRgn(int left, int top, int right, int bottom);

[DllImport("user32.dll")]
static extern int GetUpdateRgn(IntPtr hWnd, IntPtr hRgn, bool bErase);

[DllImport("gdi32.dll")]
static extern bool DeleteObject(IntPtr hObject);

const int WM_PAINT = 0x000F;
const int SIMPLEREGION = 2;
const int COMPLEXREGION = 3;

【讨论】:

以上是关于在 OnPaint() 事件中,如何检查给定的矩形是不是与无效区域相交?的主要内容,如果未能解决你的问题,请参考以下文章

如何编写读取一对坐标的代码并检查给定点是不是在一个圆内和一个矩形外?

什么是,以及如何通过绘制矩形来确定这个奇怪异常的原因?

如何根据给定的长度确定矩形?

如何在蒙特卡洛积分中查找点x,y是不是在给定矩形内

winform中怎么绘制圆角选项卡?

在 MFC C++ 程序中同时出现 OnPaint() 和 MouseMove 事件