使位图无效化为自己的位图

11

我希望能够将一个控件呈现到位图中,以便快速访问它。

不幸的是,Control.DrawToBitmap 似乎会将调用它的整个控件(包括所有子控件)都绘制出来。在内部,它使用自己提供的 DC 发送 WM_PRINT 消息到一个临时位图上,然后将这个位图复制到用户提供的位图上。这对我来说是不可接受的,我宁愿在需要更新位图时进行操作,以尽量减少性能影响。

在理想的情况下,我希望窗体的行为就像它在屏幕上是可见的(但它实际上是看不见的)。这意味着如果某个控件的 Text 属性被更改,窗体就会部分失效。捕获相关的消息/事件将允许我指定窗体要绘制的 DC 或者简单地将窗体的 DC 复制到我的 DC 上。

我已经尝试了一些方法:

  • OnPaint 中的 PaintEventArgs 参数似乎保存了一个成员 savedGraphicsState,也许可以用它来找出哪些部分不需要失效。
  • 将窗体显示在屏幕之外。这样控件就不会被绘制,但是这种方法不够可靠。
  • 手动调用窗口的 RedrawWindow(),效果与上一种类似。

我并不完全清楚为什么 DrawToBitmap(即 WM_PRINT 消息)对您的使用不可接受。您是担心性能吗?将绘图输出到位图中的速度几乎不可能比绘制到屏幕上慢。 - Cody Gray
我需要一个缓冲机制。DrawToBitmap 会从头开始绘制整个控件,而在普通窗口上使用的无效系统只会更新实际更改的区域。 - Frank Razenberg
@Frank,你考虑过使用Bitblt吗?http://www.codeproject.com/KB/GDI-plus/Bitblt_wrapper_class.aspx - Karthik Mahalingam
是的,但如果窗口实际上不可见,则无法正常工作。 - Frank Razenberg
@FrankRazenberg 似乎正在尝试创建一个控件,以在单独的 LCD 上显示。据推测,他有一个 API,允许他向该 LCD 发送位图,但它不是 Windows 认可的显示器,因此他无法简单地在该屏幕上显示控件。为此,他正在尝试获取离屏控件的图像,并在需要更新时得到通知,以便他可以将位图的更新部分发送到单独的屏幕。据推测,带宽受限,因此每次发送整个位图都不是一个选项。如果是这样,那就是真正的问题所在。 - Ben
几乎正确。LCD 只能采用全新的位图,但刷新速度足够快。瓶颈是绘制等待 WM_PRINT 绘制整个表单。相反,我想要表单的位图缓冲,但无法弄清楚何时更新缓冲区。 - Frank Razenberg
3个回答

1

我认为有两个问题:

  1. 找出控件的无效区域(没有Windows的帮助)
  2. 仅呈现无效部分。

对于第一个问题,我认为你基本上是自己解决。您应该跟踪哪些控件发生更改,并进行需要更新的簿记。

对于第二个问题,您可以尝试自己发送WM_PRINT消息,并提供引用仅小位图的DC。原始的DC API允许您偏移和剪辑HDC的有效绘图区域。如果您非常幸运,Windows将从HDC推断渲染区域,如果它没有,大多数完全落在位图之外的渲染命令应该很便宜,因为没有像素需要更改。

您可以通过打印到1x1位图并测试是否更快来验证这一点,和/或验证发送到WM_PAINT的剪辑区域是否缩小到位图大小。


0

我为您制作了一个示例项目,其中展示了一些onPaint事件。如果您无法通过这种方式解决问题,请更新示例。

祝好! OnPaint example

下载链接: http://www.goldengel.ch/temp/OnPaintExample.zip

Private Sub Button1_Paint(sender As System.Object, e As System.Windows.Forms.PaintEventArgs) Handles Button1.Paint
    Dim bm As New Bitmap(Me.Button1.Width, Me.Button1.Height, PixelFormat.Format32bppRgb)

    Button1.DrawToBitmap(bm, New Rectangle(0, 15, bm.Width -5, bm.Height+2))
    Using gr As Graphics = Graphics.FromImage(bm)
        gr.DrawString(DateTime.Now.ToLongTimeString, Me.Font, Brushes.Lime, 0, 0)
    End Using
    Me.PictureBox1.BackgroundImageLayout = ImageLayout.Tile
    Me.PictureBox1.BackgroundImage = bm

End Sub
    Public Class myTextBox
        Inherits System.Windows.Forms.TextBox


        Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
            MyBase.OnPaint(e)
            e.Graphics.Clear(Color.Yellow)
            e.Graphics.DrawString(DateTime.Now.ToLongTimeString, Me.Font, Brushes.Gray, 0, 0)
        End Sub

        Public Sub New()
            SetStyle(ControlStyles.UserPaint, True)
        End Sub
    End Class

0

我认为如果克隆控件,那么它将起作用,这样您就可以获得一个不在表单上且没有子控件的控件:

Control ctrl = ControlFactory.CloneCtrl(this.button3);
Bitmap bmp = new Bitmap(ctrl.Width, ctrl.Height);
ctrl.DrawToBitmap(bmp, new Rectangle(0, 0, ctrl.Width, ctrl.Height));
bmp.Save(@"C:\Users\Oli\Desktop\test.bmp");

我使用了由lxwde编写的ControlFactory,它可以在The Code Project上找到。

ControlFactory并不完美,但它足够简单,很容易进行改进。


抱歉,我不明白这与问题的相关性。 - Frank Razenberg
你说你的解决方案不幸地绘制了子控件。我的解决方案则不会绘制。我已经尝试过在一个包含按钮的面板上使用它。它仅将面板绘制到位图上而没有绘制子控件。当控件不在屏幕上可见时,它也可以正常工作。 - Olivier Jacot-Descombes
我不是很明白你想要实现什么。你说:“我希望这个表单的行为就像它在屏幕上显示一样(但实际上它不能被显示)”。为什么它不能在屏幕上显示?为什么你想渲染一个在屏幕上看不见的表单? - Olivier Jacot-Descombes
渲染表单的位图将被发送到单独的LCD。我希望控件在通常情况下自动验证自己,例如当更改其窗口文本时。我也想知道这是什么时候发生的。 - Frank Razenberg
1
控件中有一个“Invalidated”事件。您可以为所有控件订阅它。 - Olivier Jacot-Descombes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接