Search code examples
vb.netwinformsscreenshotrichtextbox

Take a screenshot of a Control


I want to take a screenshot of a RichTextBox using the following code.
The problem is it takes a screenshot of another part of the Form:

Dim memoryImage As Bitmap
Dim myGraphics As Graphics = Me.CreateGraphics()
Dim s As Size = RichTextBox2.Size
memoryImage = New Bitmap(s.Width, s.Height, myGraphics)

Dim memoryGraphics As Graphics = Graphics.FromImage(memoryImage)
memoryGraphics.CopyFromScreen(RichTextBox2.Bounds.X, RichTextBox2.Bounds.Y, 0, 0, s)
memoryImage.Save(audiooutputfolder & name & ".png")

Solution

  • Graphics.CopyFromScreen() requires that you specify screen coordinates.
    You can transform local coordinates into screen coordinates using the Control.RectangleToScreen() and Control.PointToScreen() methods.
    Other methods do the opposite, see the Docs.

    To compute the client area of a Control in screen coordinates, you can use its RectangleToScreen() method and pass the value of the ClientRectangle property:

    Dim clientRectToScreen = [Control].RectangleToScreen([Control].ClientRectangle)
    

    To include the non-client area (e.g., the borders of a Control, including the Scrollbars, if any), you need the screen coordinates of its Bounds.
    There are different ways to do this. A simple method is to ask the Parent of a Control to get them, passing to the Parent's RectangleToScreen() method the Bounds of a child Control.
    If you want to print a Form, which is a Top-Level Control, so it has no Parent, just use its Bounds directly: these measures already express screen coordinates.

    It's shown in the ControlToBitmap() method:

    Private Function ControlToBitmap(ctrl As Control, clientAreaOnly As Boolean) As Bitmap
        If ctrl Is Nothing Then Return Nothing
        Dim rect As Rectangle
    
        If clientAreaOnly Then
            rect = ctrl.RectangleToScreen(ctrl.ClientRectangle)
        Else
            rect = If(ctrl.Parent Is Nothing, ctrl.Bounds, ctrl.Parent.RectangleToScreen(ctrl.Bounds))
        End If
    
        Dim img As New Bitmap(rect.Width, rect.Height)
        Using g As Graphics = Graphics.FromImage(img)
            g.CopyFromScreen(rect.Location, Point.Empty, img.Size)
        End Using
        Return img
    End Function
    

    To take a screenshot of a Control, call this method, passing the Control you want to print to a Bitmap and specify whether you just want its content (the client area) or you want to include the non-client area (for example, if the control to print is a Form, you want to include the Caption and borders).

    • Important: use Path.Combine() to build a path:

      Path.Combine(audiooutputfolder, $"{imageName}.png"
      

      if string interpolation is not available ($"{variable} other parts"), you can glue the file extension to the file name:

      Path.Combine(audiooutputfolder, imageName & ".png")
      
    ' Get the screenshot, client area only
    Dim controlImage = ControlToBitmap(RichTextBox2, True)
    ' Save the image to the specified Path using the default PNG format
    controlImage.Save(Path.Combine(audiooutputfolder, $"{imageName}.png"), ImageFormat.Png)
    ' [...] when done with the bitmap
    controlImage.Dispose()
    

    Side note:
    If your app is not DpiAware, you may get wrong screen coordinates.
    See these notes about this.