持久图形 WinForms

Posted

技术标签:

【中文标题】持久图形 WinForms【英文标题】:Persistent graphics WinForms 【发布时间】:2013-02-20 16:59:26 【问题描述】:

我有一个 WinForms 应用程序,我必须在控件之间画一些线。这些行需要持久化,所以我覆盖了OnPaint() 事件的形式。

问题是,重新绘制的线条不是很流畅。

我正在按如下方式创建图形:

Graphics g;
g = this.CreateGraphics();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.FillRectangle(Brushes.White, this.ClientRectangle);

然后画线如下:

public void lineDraw(Control L1, Control L2)             
    using (Pen pen = new Pen(Color.Black, 4)) 
        pen.StartCap = System.Drawing.Drawing2D.LineCap.Flat;
        pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
        int x1, x2, y1, y2;
        //choose x/y coordinates
        g.DrawLine(pen, x1, y1, x2, y2);
    

我可以设置任何属性来提高绘制图形的平滑度吗?

【问题讨论】:

【参考方案1】:

目标

图像显示在控件(或窗体)上。

失效

任何时候控件(或窗体)被调整大小、最小化/最大化、部分遮挡或移动,它必须(部分)重绘。当这种情况发生时,必须重绘的控件部分被称为无效

当控件无效时,会执行如下操作:

    调用OnPaintBackground:这会用背景色填充无效区域。 调用OnPaint:这会在背景上绘制文本和图形。

为什么OnPaint会导致闪烁

您已经覆盖了控件的OnPaint method。每次重绘控件时,您都会看到控件的闪烁,其中仅绘制了其背景颜色。那是在调用OnPaintBackground 之后和调用OnPaint 之前。

解决方案

如果你有一个静态图像(即它永远不会改变):

    Load 事件中:创建一个新的Bitmap object。 用背景色填充并在其上绘制线条和形状。 将此Bitmap 对象分配给控件的BackgroundImage property。

如果您有一个在控件调整大小时必须调整大小的静态图像:

    覆盖OnResize method 并在其中创建新的Bitmap。使用控件的ClientSize property 作为Bitmap 的大小。 用背景色填充并在其上绘制线条和形状。 将此Bitmap 对象分配给控件的BackgroundImage 属性。

如果您有动画图像:

    Load 事件中,将控件的DoubleBuffered property 设置为true。设置此项可防止您看到的闪烁,因为它使用不可见的缓冲区来绘制控件。 覆盖控件的OnPaint method。获取控件的Graphics context,直接在控件上绘制线条和形状。 创建并启用一个新的Timer object,并在其回调方法中调用控件的Invalidate method,后跟Update method (as shown here)。例如,将计时器设置为每 40 毫秒触发一次。

【讨论】:

我从来不知道它的存在! +1 它不是块状的,但每次调用OnPaint() 事件时都会闪烁很多。例如,当我将其他控件移到线条上时。事实上这是有道理的,因为它必须在每次调用时重新绘制所有的行(可以是很多行,但对于少数行来说,方面是相同的)。 SmoothingMode.HighQuality 没有解决问题。 SmoothingMode.HighSpeed. @Virtlink - 您可能还想建议使用计时器来延迟重绘,这样它就不会超过刷新率。 没有一个解决方案具有良好的整体性能。当前的解决方案是将Graphics.DrawLine() 更改为LineShape 对象。 @guanabara:我无法想象我的解决方案 #1 的性能会很差。需要明确的是,它不使用OnPaint,它只在表单大小改变时设置一次图像。【参考方案2】:

你可能不应该在这里使用CreateGraphics,更重要的是,不要使用局部变量来存储图形对象。使用从绘制事件中获取的图形对象,并根据需要使之失效。

protected override void OnPaint(PaintEventArgs e) 
  e.Graphics.Clear(Color.White);
  e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

  using (Pen pen = new Pen(Color.Black, 4)) 
    pen.StartCap = Drawing2D.LineCap.Flat;
    pen.EndCap = Drawing2D.LineCap.ArrowAnchor;
    int x1, x2, y1, y2;
    //choose x/y coordinates
    e.Graphics.DrawLine(pen, x1, y1, x2, y2);
  

  base.OnPaint(e);

【讨论】:

【参考方案3】:

这是我的方式,它对我有用

//FormMain.cs
private const float DisplayRatio = 6;

private Bitmap _bmpDisp; //use an in-memory bitmap to Persistent graphics
private Graphics _grpDisp4Ctl;
private Graphics _grpDisp4Bmp;
private Point _ptOldDsp;


private void FormMain_Shown(object sender, EventArgs e)

    _grpDisp4Ctl = CreateGraphics();
    _grpDisp4Ctl.SetHighQulity();

    _bmpDisp = new Bitmap(ClientSize.Width, ClientSize.Height);
    _grpDisp4Bmp = Graphics.FromImage(_bmpDisp);
    _grpDisp4Bmp.SetHighQulity();

    _ptOldDsp = new Point(
        (int)((MousePosition.X - SystemInformation.VirtualScreen.Left) / DisplayRatio),
        (int)((MousePosition.Y - SystemInformation.VirtualScreen.Top) / DisplayRatio)
    );


private void UpdateDisplay(MouseHookEvent mhep) //your implement
        
    var ptNew = mhep.Position;
    ptNew.Offset(new Point(-SystemInformation.VirtualScreen.Left, -SystemInformation.VirtualScreen.Top));
    ptNew.X = (int)(ptNew.X / DisplayRatio);
    ptNew.Y = (int)(ptNew.Y / DisplayRatio);

    _grpDisp4Ctl.DrawLine(Pens.White, _ptOldDsp, ptNew); //draw smooth lines to mem and ui
    _grpDisp4Bmp.DrawLine(Pens.White, _ptOldDsp, ptNew);

    _ptOldDsp = ptNew;



private void FormMain_Paint(object sender, PaintEventArgs e)

    // like vb6's auto redraw :)
    e.Graphics.DrawImage(_bmpDisp, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);


//common.cs
internal static void SetHighQulity(this Graphics g)

    g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
    g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;

【讨论】:

【参考方案4】:

我知道这是一篇较旧的帖子,但您也可以尝试将表单的 DoubleBuffered 属性设置为 TRUE,阅读以下内容:

"缓冲图形可以减少或消除由以下原因引起的闪烁 逐步重绘显示表面的各个部分。缓冲的 图形要求首先将更新的图形数据写入 缓冲。然后将图形缓冲区中的数据快速写入 显示表面记忆。显示的切换比较快 图形内存通常会减少闪烁,否则 发生。”

【讨论】:

以上是关于持久图形 WinForms的主要内容,如果未能解决你的问题,请参考以下文章

在组件的持久化中实现 `Auto` 属性

缓存和持久化数据集

如何将数据从 PySpark 持久化到 Hive - 避免重复

Redis学习缓存持久化哨兵模式

Ubuntu关闭自动更新和GUI图形界面

Python学生信息管理系统图形化界面-老师端-学生端项目实现