现有图形到位图

时间:2014-12-23 15:30:20

标签: c# .net winforms bitmap

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

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

    • 将现有的CursorControl.Invalidate()对象转换为Graphics(它(图形)通过Paint方法提供给我,我必须在其上绘画,所以我无法创建一个新的Graphics对象...也许我弄错了,但这就是我理解它的方式)
    • 在绘制十字准线之前,从位图恢复图形内容并重新绘制十字准线

    我无法控制绘制昂贵数据的过程。我可以访问通过API调用的Bitmap及其方法。

    那么有没有办法将现有的Graphics内容存储到Bitmap中并在以后恢复?或者有更好的方法来解决这个问题吗?


    决议:经过数小时的反复试验后,我想出了一个有效的解决方案。我使用的软件存在许多问题,无法对其进行一般性讨论,但主要原则很明确:

    • 已经绘制过的东西的现有图形无法直接转换为Bitmap,而是必须使用@ Gusman的答案中首次提到的CursorControl方法。我知道它,我想避免它,但最后我不得不接受,因为它似乎是唯一的方式
    • 我也希望避免每一帧的双重绘制,因此第一个十字准线颜色总是直接绘制到panel.DrawToBitmap。在鼠标移动而不更改图表图像后,我会通过ChartPanel进行快照,然后按照所选答案中的说明继续操作。
    • 控件必须是不透明的(未启用透明背景),以便刷新它不会在它的父控件上调用Paint(这将导致整个图表重新绘制)

    我每隔几秒左右仍会遇到偶尔的闪烁,但我想我能以某种方式解决这个问题。虽然我选择了Gusman的答案,但我要感谢所有参与者,因为我使用了其他答案中提到的许多其他技巧,比如Panel.BackgroundImage,使用Plot()方法代替Paint()来锁定图像等等。

2 个答案:

答案 0 :(得分:3)

这可以通过多种方式完成,始终将图形存储为Bitmap。最直接有效的方法是让Panel为您完成所有工作。

以下是这个想法:大多数winforms Controls都有两层显示。

Panel的情况下,两个图层是BackgroundImage及其Control表面。 许多其他控件也是如此,例如Label, CheckBox, RadioButtonButton

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

因此我们可以将昂贵的东西移到BackgroundImage并在surcafe上绘制十字准线。 在我们的情况下,Panel,所有不错的附加内容都已到位,您可以选择BackgroundImageLayout属性的所有值,包括Tile, Stretch, CenterZoom。我们选择None

现在我们为您的项目添加一个标志:

bool panelLocked = false;

以及根据需要设置的功能:

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<Pen> pens = new List<Pen>();
        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));
    }

}

最后,我们编写MouseMove的{​​{1}}脚本:

Panel

使用第二个类级变量:

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; } } 上方。你可能想打开&amp;关闭鼠标光标PanelsMouseLeave ..

enter image description here

答案 1 :(得分:2)

为什么不通过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;

瞧,您可以在不重新计算图表内容的情况下重绘您需要的次数。

干杯。