我正在创建一个矩形作为放置在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();
}
答案 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,当系统请求(重新)绘制时,不。
那不行。您的表单的位图未被保存或缓存。