为什么不画矩形?

时间:2009-12-11 15:52:21

标签: c# graphics bitmap

我正在创建一个矩形作为放置在Panel控件中的自定义控件对象。控件矩形由Panel的Paint事件创建并证明:

void myPanel_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;

    foreach(ControlItem item in controls)
        g.DrawRectangle(new Pen(Brushes.Blue), new Rectangle(item.Location, item.Size));

    g.Dispose();
}

为了删除上面的foreach,并提高渲染性能,我试图给自定义矩形控件一个类似的Paint事件(所以它不会与标准的Paint事件混淆,我正在调用我的活动渲染)。

自定义基类构造函数:

   { controlBase = new Bitmap(50, 25);  /*Create a default bitmap*/ }

自定义基类CreateGraphics:

   { return Graphics.FromImage(controlBase); }

显然,我需要一个用于渲染/绘画的事件处理程序:

public class RenderEventArgs : PaintEventArgs
{
    public RenderEventArgs(Graphics g, Rectangle rect) :
        base(g, rect)
    { }
}

但是,以下内容未提供预期结果:

void item_Render(object sender, RenderEventArgs e)
{
    Graphics g = e.Graphics;
    g.DrawRectangle(new Pen(Brushes.Blue),
        new Rectangle(((myBaseClass)sender).Location, ((myBaseClass)sender).Size));
    g.Dispose();
}

所以,我想弄清楚我错过了什么。包含矩形对象的Panel设置大小和位置。我不确定我是否掌握了所有相关信息,如果您有任何疑问,请随时提出。

...谢谢

编辑: @Henk Holterman - 我在派生的构造函数中引发了OnRender:

public ControlItem()
    : base()
{
    Graphics g = CreateGraphics();
    Pen p = new Pen(Color.Black, 2.5f);

    BackColor = Color.Beige;

    g.DrawRectangle(p, Bounds);
    OnRender(new RenderEventArgs(g, Bounds));

    g.Dispose();
}

我不确定我是否在正确的地方提出这个问题。当父Panel控件设置Location:

public Point Location
{ 
    get { return myRectangle.Location; }
    set 
    { 
        myRectangle.Location = value;
        DrawBase();
    }
}

... WHERE ...

private void DrawBase()
{
    Graphics g = CreateGraphics();
    g.DrawImage(controlBase, Location);
    g.Dispose();
}

2 个答案:

答案 0 :(得分:2)

使用渲染状态抽象内部项目非常适合组织目的,但不一定能提高性能。此外,在使用Graphics对象时,您只能在已使用上下文初始化的Graphics对象上绘制,无论是屏幕/显示,位图还是其他输出媒体(如Metafiles)。如果你没有使用屏幕外的上下文,那么在构造函数中初始化Graphics对象是不常见的。

要改善绘图代码,首先应尽量减少绘制的区域。要记住一个好的启发式方法是“绘制的像素数越少=我的显示越快”

考虑到这一点,您应该考虑使用PaintEventArgs对象中的ClipRectangle属性。这告诉您需要更新Graphics对象的区域。从这里开始,使用为您提供的任何Graphics对象来进行绘图更新工作。

此外,如果您需要重绘其中一个内部元素,则应该只需要重新绘制需要重绘的区域。这与重绘时使用ClipRectangle相结合,将减少正在执行的实际显示工作量。

PaintEventArgs.ClipRectangle Property (System.Windows.Forms) @ MSDN

我已经写了一个样本,您可以选择分开以查看上述所有内容。虽然不是最好的(例如,有些东西可以回收,比如钢笔和画笔),但它可以为你提供一些关于如何处理你的绘画的更好的想法。

请注意,此示例不使用事件。我相信如果你愿意,你可以弄清楚如何改变它来使用事件。我省略了它们,因为如果每次需要更新时都不重绘所有内容,则foreach()/枚举不一定是缺点。如果您绘制的数字太复杂而无法使用事件,那么最好创建新的用户控件。此外,无论是使用事件还是foreach(),您都需要检查每个内部项的重叠。

首先,我创建了一个名为DefinedRectangles的C#Forms应用程序。然后,我添加了一个DisplayRect类来表示矩形轮廓。

以下是表单的源代码。我在表单中添加了一个MouseDoubleClick处理程序,以允许更改矩形的状态。根据最后的状态,矩形可以是实线/虚线,并且在双击内部区域时来回翻转。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace DefinedRectangles
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            innerItems = new List<DisplayRect>();
            innerItems.Add(new DisplayRect(new Rectangle(0, 0, 50, 50), Color.Blue, true));
            innerItems.Add(new DisplayRect(new Rectangle(76, 0, 100, 50), Color.Green, false));
            innerItems.Add(new DisplayRect(new Rectangle(0, 76, 50, 100), Color.Pink, false));
            innerItems.Add(new DisplayRect(new Rectangle(101, 101, 75, 75), Color.Orange, true));
        }

        List<DisplayRect> innerItems;

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            foreach(DisplayRect dispItem in innerItems)
            {
                dispItem.OnPaint(this, e);
            }
        }

        private void Form1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            foreach (DisplayRect dispItem in innerItems)
            {
                dispItem.OnHitTest(this, e);
            }
        }
    }
}

以下是DisplayRect类的源代码。请注意,当我们需要无效时,我们必须调整更新矩形以弥补矩形边框和矩形区域之间的怪癖。

using System;
using System.Collections.Generic;
using System.Text;

using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace DefinedRectangles
{
    public class DisplayRect
    {
        public DisplayRect(Rectangle dispArea, Color dispColor, bool dashed)
        {
            m_area = dispArea;
            m_areaColor = dispColor;
            m_solidLines = !dashed;
        }

        Rectangle m_area;
        Color m_areaColor;
        bool m_solidLines;

        public Rectangle Bounds { get { return m_area; } }

        public void OnPaint(object sender, PaintEventArgs e)
        {
            if (!m_area.IntersectsWith(e.ClipRectangle)) { return; }

            Graphics g = e.Graphics;
            using (Pen p = new Pen(m_areaColor))
            {
                if (m_solidLines)
                {
                    p.DashStyle = DashStyle.Solid;
                }
                else
                {
                    p.DashStyle = DashStyle.Dot;
                }
                // This could be improved to just the border lines that need to be redrawn
                g.DrawRectangle(p, m_area);
            }
        }

        public void OnHitTest(object sender, MouseEventArgs e)
        {
            // Invalidation Rectangles don't include the outside bounds, while pen-drawn rectangles do.
            // We'll inflate the rectangle by 1 to make up for this issue so we can handle the hit region properly.
            Rectangle r = m_area;
            r.Inflate(1, 1);
            if (r.Contains(e.X, e.Y))
            {
                m_solidLines = !m_solidLines;
                Control C = (Control)sender;
                C.Invalidate(r);
            }
        }
    }
}

答案 1 :(得分:1)

一个非常大的红旗:你应该 在Paint事件处理程序中处理e.Graphics对象。

您这样做(以及您自己的渲染事件)会让您对此处的其他设计产生怀疑。但是,由于您没有显示如何/何时引发渲染事件(或:它如何替换foreach循环),我无法确定。

通常,只对自己创建的对象进行Dispose(),并使用using() {}语句在同一级别上进行。

重新编辑:

我可以告诉你(仅)在创建或调整大小/重新定位时绘制YourControls,当系统请求(重新)绘制时,

那不行。您的表单的位图未被保存或缓存。