现有图形到位图
我正在为一个交易软件(C#,winforms,.NET 3.5)编写一个插件,我想在一个面板(比如ChartPanel
)上绘制一个十字光标,其中包含可能很昂贵的数据。 到目前为止我所做的是:
- 我在面板中添加了一个
CursorControl
- 此
CursorControl
位于主绘图面板上方,以便覆盖整个区域 - 它的
Enabled = false
以便将所有输入事件传递给父ChartPanel
- 实现了
Paint
方法,以便在当前鼠标位置从上到下和从左到右绘制线条
- 此
- 当MouseMove事件被触发时,我有两种可能性:
- A)调用
ChartPanel.Invalidate()
,但正如我所说,底层数据绘制可能很昂贵,这会导致每次移动鼠标时都重绘一次,这是错误的(但这是我可以做到这一点的唯一方法)现在) - B)调用
CursorControl.Invalidate()
并在绘制光标之前,我将获取当前绘制数据的快照,并将其保留为光标的背景,每次光标需要重新绘制时,光标将被恢复…这是…… 我不知道该怎么做 。
- A)调用
2.B. 意思是:
- 将现有的
Graphics
对象转换为Bitmap
(它(Graphics)是通过Paint方法给我的,我必须在它上面绘制,所以我不能创建一个新的Graphics对象……也许我弄错了,但那就是我理解的方式) - 在绘制十字准线之前,从位图恢复图形内容并重新绘制十字准线
我无法控制绘制昂贵数据的过程。 我可以访问我的CursorControl
及其通过API调用的方法。
那么有没有办法将现有的图形内容存储到Bitmap中并在以后恢复? 或者有没有更好的方法来解决这个问题?
决议:经过几个小时的反复试验,我想出了一个有效的解决方案。 我使用的软件存在许多问题,一般无法讨论,但主要原则很明确:
- 已经绘制的东西的现有图形不能直接转换为Bitmap,而是我必须使用@ Gusman的答案中首次提到的
panel.DrawToBitmap
方法。 我知道它,我想避免它,但最后我不得不接受,因为它似乎是唯一的方式 - 我也希望避免每一帧的双重绘制,因此第一个十字准线颜色总是直接绘制到
ChartPanel
。 在鼠标移动而不更改图表图像后,我通过DrawToBitmap
进行DrawToBitmap
,并按照所选答案中的描述继续操作。 - 控件必须是不透明的(未启用透明背景),以便刷新它不会在它的父控件上调用Paint(这会导致整个图表重绘)
我每隔几秒左右仍会偶尔出现闪烁现象,但我想我能以某种方式解决这个问题。 虽然我选择了Gusman的答案,但我要感谢所有参与者,因为我使用了其他答案中提到的许多其他技巧,比如Panel.BackgroundImage,使用Plot()方法代替Paint()来锁定图像等。
为什么不通过CursorControl克隆ChartPanel中的所有图形?
这里的所有代码都必须放在CursorControl中。
首先,创建一个属性,该属性将保存对图表的引用并挂钩它的paint事件,如下所示:
ChartPanel panel; public ChartPanel Panel { get{ return panel; } set{ if(panel != null) panel.Paint -= CloneAspect; panel = value; panel.Paint += CloneAspect; } }
现在定义CloneAspect函数,只要在Chart面板中完成Paint操作,它就会将控件的外观呈现给位图:
Bitmap aspect; void CloneAspect(object sender, PaintEventArgs e) { if(aspect == null || aspect.Width != panel.Width || aspect.Height != panel.Height) { if(aspect != null) aspect.Dispose(); aspect = new Bitmap(panel.Width, panel.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); } panel.DrawToBitmap(aspect, new Rectangle(0,0, panel.Width, panel.Height); }
然后在OnPaint overriden方法中执行以下操作:
public override void OnPaint(PaintEventArgs e) { e.Graphics.DrawImage(aspect); //Now draw the cursor (...) }
最后,无论您在何处创建图表和自定义光标,您都可以:
CursorControl.Panel = ChartPanel;
瞧,您可以重绘您需要的次数,而无需重新计算图表的内容。
干杯。
这可以通过多种方式完成,始终将图形存储为Bitmap
。 最直接有效的方法是让Panel
为您完成所有工作。
这是一个想法:大多数winforms Controls
都有一个双层显示。
在Panel
的情况下,两个层是其BackgroundImage
及其Control
表面。 许多其他控件也是如此,例如Label, CheckBox, RadioButton
或Button
。
(一个有趣的例外是PictureBox
,它还有一个(前景) Image
。)
因此,我们可以将昂贵的东西移动到BackgroundImage
并在surcafe上绘制十字准线。 在我们的例子中, Panel
,所有很好的附加function都已到位,您可以选择BackgroundImageLayout
属性的所有值,包括Tile, Stretch, Center
或Zoom
。 我们选择None
。
现在我们为您的项目添加一个标志:
bool panelLocked = false;
以及根据需要设置的function:
void lockPanel( bool lockIt) { if (lockIt) { Bitmap bmp = new Bitmap(panel1.ClientSize.Width, panel1.ClientSize.Width); panel1.DrawToBitmap(bmp, panel1.ClientRectangle); panel1.BackgroundImage = bmp; } else { if (panel1.BackgroundImage != null) panel1.BackgroundImage.Dispose(); panel1.BackgroundImage = null; } panelLocked = lockIt; }
在这里,您可以看到工作中的魔力:在我们实际锁定Panel
执行昂贵的工作之前,我们告诉它创建其图形的快照并将其放入BackgroundImage
。
现在我们需要使用该标志来控制Paint
事件:
private void panel1_Paint(object sender, PaintEventArgs e) { Size size = panel1.ClientSize; if (panelLocked) { // draw a full size cross-hair cursor over the whole Panel // change this to suit your own needs! e.Graphics.DrawLine(Pens.Red, 0, mouseCursor.Y, size.Width - 1, mouseCursor.Y); e.Graphics.DrawLine(Pens.Red, mouseCursor.X, 0, mouseCursor.X, size.Height); } // expensive drawing, you insert your own stuff here.. else { List pens = new List (); for (int i = 0; i < 111; i++) pens.Add(new Pen(Color.FromArgb(R.Next(111), R.Next(111), R.Next(111), R.Next(111)), R.Next(5) / 2f)); for (int i = 0; i < 11111; i++) e.Graphics.DrawEllipse(pens[R.Next(pens.Count)], R.Next(211), R.Next(211), 1 + R.Next(11), 1 + R.Next(11)); } }
最后,我们编写Panel
的MouseMove
脚本:
private void panel1_MouseMove(object sender, MouseEventArgs e) { mouseCursor = e.Location; if (panelLocked) panel1.Invalidate(); }
使用第二个类级变量:
Point mouseCursor = Point.Empty;
您可以根据需要调用lockPanel(true)
或lockPanel(false)
。
如果你直接实现这一点,你会发现一些闪烁。 如果您使用双缓冲Panel
这将消失:
class DrawPanel : Panel { public DrawPanel() { this.DoubleBuffered = true; } }
这会以完美平滑的方式将十字准线移动到Panels
上。 您可能想要在MouseLeave
和MouseEnter
上打开和关闭鼠标光标。