现有图形到位图

我正在为一个交易软件(C#,winforms,.NET 3.5)编写一个插件,我想在一个面板(比如ChartPanel )上绘制一个十字光标,其中包含可能很昂贵的数据。 到目前为止我所做的是:

  1. 我在面板中添加了一个CursorControl
    • CursorControl位于主绘图面板上方,以便覆盖整个区域
    • 它的Enabled = false以便将所有输入事件传递给父ChartPanel
    • 实现了Paint方法,以便在当前鼠标位置从上到下和从左到右绘制线条
  2. 当MouseMove事件被触发时,我有两种可能性:
    • A)调用ChartPanel.Invalidate() ,但正如我所说,底层数据绘制可能很昂贵,这会导致每次移动鼠标时都重绘一次,这是错误的(但这是我可以做到这一点的唯一方法)现在)
    • B)调用CursorControl.Invalidate()并在绘制光标之前,我将获取当前绘制数据的快照,并将其保留为光标的背景,每次光标需要重新绘制时,光标将被恢复…这是…… 我不知道该怎么做

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, RadioButtonButton

(一个有趣的例外是PictureBox ,它还有一个(前景) Image 。)

因此,我们可以将昂贵的东西移动到BackgroundImage并在surcafe上绘制十字准线。 在我们的例子中, Panel ,所有很好的附加function都已到位,您可以选择BackgroundImageLayout属性的所有值,包括Tile, Stretch, CenterZoom 。 我们选择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)); } } 

最后,我们编写PanelMouseMove脚本:

 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上。 您可能想要在MouseLeaveMouseEnter上打开和关闭鼠标光标。

在此处输入图像描述