为什么矩形无法绘制?

发布于 2024-08-14 20:44:37 字数 1907 浏览 3 评论 0原文

我正在创建一个矩形作为放置在面板控件内的自定义控件对象。控件矩形是由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 事件混淆,我将我的事件称为 Render)。

自定义基类构造函数:

   { 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();
}

所以,我试图找出我缺少的内容。包含矩形对象的面板设置大小和位置。我不确定我是否掌握了所有信息,因此如果您有任何疑问,请随时询问。

谢谢...

编辑: @亨克霍尔特曼- 我在派生的构造函数中引发了 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();
}

I am creating a rectangle as a custom control object that is placed within a Panel control. The control rectangle is created and evidenced by the Panel's Paint event:

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();
}

In order to remove the foreach above, and to improve rendering performance, I am trying to give the custom rectangle control a similar Paint event (so it isn't confused with the standard Paint event, I am calling my event Render).

Custom base class constructor:

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

Custom base class CreateGraphics:

   { return Graphics.FromImage(controlBase); }

Obviously, I require an event handler for rendering/painting:

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

However, the following is not providing the expected results:

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();
}

So, I'm trying to figure out what I am missing. The Panel that contains the rectangle objects sets the size and location. I'm not sure if I have all the information here, so if you have any questions, please feel free to ask.

Thanks...

Edit:
@Henk Holterman -
I raise the OnRender in my derived Constructor:

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();
}

I am not sure that I am raising this in the right place(s). When the parent Panel control sets 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();
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

你如我软肋 2024-08-21 20:44:37

使用渲染状态抽象内部项目对于组织目的很有好处,但不一定会提高性能。此外,在使用 Graphics 对象时,您只能在已使用上下文初始化的 Graphics 对象上进行绘制,无论是屏幕/显示器、位图还是其他输出媒体(如图元文件)。如果不使用离屏上下文,则在构造函数中初始化 Graphics 对象是不常见的。

要改进绘图代码,您应该首先最小化正在绘制的区域。需要记住的一个很好的启发是“绘制的像素数越少=我的显示速度越快”

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

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

PaintEventArgs.ClipRectangle 属性 (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);
            }
        }
    }
}

Abstracting inner items with Render states is nice for organizational purposes, but doesn't necessarily improve performance. Also, when using the Graphics object, you can only draw on a Graphics object that has been initialized with a context, whether a screen/display, bitmap, or other output media (like Metafiles). It is unusual to initialize a Graphics object in a constructor if you're not making use of off-screen contexts.

To improve your drawing code, you should start by minimizing the areas that are being drawn. A good heuristic to keep in mind is "the lower the number of pixels drawn = the faster my display can be".

With that in mind, you should look at using the ClipRectangle property in the PaintEventArgs object. This tells you the region of the Graphics object that needs to be updated. From here, use whatever Graphics object is given to you to do your drawing update work.

Also, if you need to redraw one of your inner elements, you should just invalidate the area that needs to be redrawn. This, combined with the use of the ClipRectangle when redrawing, will keep down the amount of actual display work being performed.

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

I've written a sample that you can pick apart to see all of the above in action. While not the best possible (for example, some things can be recycled, like Pens and Brushes), it may give you some better ideas on how to approach your drawing.

Note that this sample doesn't use Events. I'm sure you can figure out how to alter it to use events if you like. I omitted them because foreach()/enumeration is not necessarily a drawback if you aren't redrawing everything every time you need an update. If your drawn figures are so complex that you want to use events, you may be better off creating new user controls. Also, you will need to check overlaps for each inner item whether you use events or foreach().

First, I created a C# Forms application called DefinedRectangles. Then, I added a DisplayRect class to represent rectangle outlines.

Here is the source code for the Form. I added a MouseDoubleClick handler to the form to allow changes to the state of the rectangles. Rectangles can be solid/dashed depending on the last state, and will flip back and forth as their inside area is double-clicked.

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);
            }
        }
    }
}

Here is the source code for the DisplayRect class. Note that when we need to invalidate, we have to adjust our update rectangle to make up for quirks between rectangle borders and rectangle areas.

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);
            }
        }
    }
}
陌生 2024-08-21 20:44:37

一个非常大的危险信号:您不应该在 Paint 事件处理程序中 Dispose() e.Graphics 对象。

事实上,您这样做(也在您自己的渲染事件中),使您对此处的其余设计产生怀疑。但由于您没有显示如何/何时引发渲染事件(或者:它如何替换 foreach 循环),我无法确定。

通常,仅对您自己创建的对象进行 Dispose(),并使用 using() {} 语句在同一级别执行此操作。

重新编辑:

据我所知,(仅)在创建或调整大小/重新定位时绘制 YourControls,而在系统请求(重新)绘制时

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

One very big red flag: You should not Dispose() the e.Graphics object in a Paint eventhandler.

The fact that you do (and in your own render event too), throws doubt on the rest of your design here. But as you don't show how/when the render event is raised (or: how it replaces the foreach loop), I can't be sure.

As a rule, only Dispose() of objects you created yourself, and do so on the same level with a using() {} statement.

Re the Edit:

From what I can tell you (only) draw the YourControls when created or resized/repositiones, and not when the system requests a (Re)Paint.

And that won't work. Your Form's bitmap is not being saved or cached.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文