A way to print RTF text over image?

bonedoc

Well-known member
Joined
May 4, 2006
Messages
112
Programming Experience
Beginner
Ok, I just cannot find a way around my problem. It seems like there is some limitation no matter what I try. I had some code that would print text over graphics. Well, I wanted to be able to change fonts and colors and save as an RTF, so I changed the textbox to a RichTextBox. Because I did this, I had to completely change the way I printed the text. The code I use to print the RichTextBox text is below.

The problem is that the text printed completely covers the background image with white. I was hoping that I could change the RichTextBox background color to transparent, but that is not allowed with the control. Really, I am unsure how this code is actually printing the text from the RichTextBox. I thought I could maybe take the image it is getting with the text and make the white color completely transparent. Are there any suggestions?


VB.NET:
Option Explicit On 

Imports System
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Drawing.Printing

Namespace RichTextBoxPrintCtrl
   Public Class RichTextBoxPrintCtrl
      Inherits RichTextBox
      ' Convert the unit that is used by the .NET framework (1/100 inch) 
      ' and the unit that is used by Win32 API calls (twips 1/1440 inch)
      Private Const AnInch As Double = 14.4

      <StructLayout(LayoutKind.Sequential)> _
       Private Structure RECT
         Public Left As Integer
         Public Top As Integer
         Public Right As Integer
         Public Bottom As Integer
      End Structure

      <StructLayout(LayoutKind.Sequential)> _
      Private Structure CHARRANGE
         Public cpMin As Integer          ' First character of range (0 for start of doc)
         Public cpMax As Integer          ' Last character of range (-1 for end of doc)
      End Structure

      <StructLayout(LayoutKind.Sequential)> _
      Private Structure FORMATRANGE
         Public hdc As IntPtr             ' Actual DC to draw on
         Public hdcTarget As IntPtr       ' Target DC for determining text formatting
         Public rc As Rect                ' Region of the DC to draw to (in twips)
         Public rcPage As Rect            ' Region of the whole DC (page size) (in twips)
         Public chrg As CHARRANGE         ' Range of text to draw (see above declaration)
      End Structure

      Private Const WM_USER As Integer = &H400
      Private Const EM_FORMATRANGE As Integer = WM_USER + 57

      Private Declare Function SendMessage Lib "USER32" Alias "SendMessageA" (ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wp As IntPtr, ByVal lp As IntPtr) As IntPtr

      ' Render the contents of the RichTextBox for printing
      '	Return the last character printed + 1 (printing start from this point for next page)
      Public Function Print(ByVal charFrom As Integer, ByVal charTo As Integer, ByVal e As PrintPageEventArgs) As Integer

         ' Mark starting and ending character 
         Dim cRange As CHARRANGE
         cRange.cpMin = charFrom
         cRange.cpMax = charTo

         ' Calculate the area to render and print
         Dim rectToPrint As RECT
         rectToPrint.Top = e.MarginBounds.Top * AnInch
         rectToPrint.Bottom = e.MarginBounds.Bottom * AnInch
         rectToPrint.Left = e.MarginBounds.Left * AnInch
         rectToPrint.Right = e.MarginBounds.Right * AnInch

         ' Calculate the size of the page
         Dim rectPage As RECT
         rectPage.Top = e.PageBounds.Top * AnInch
         rectPage.Bottom = e.PageBounds.Bottom * AnInch
         rectPage.Left = e.PageBounds.Left * AnInch
         rectPage.Right = e.PageBounds.Right * AnInch

         Dim hdc As IntPtr = e.Graphics.GetHdc()

         Dim fmtRange As FORMATRANGE
         fmtRange.chrg = cRange                 ' Indicate character from to character to 
         fmtRange.hdc = hdc                     ' Use the same DC for measuring and rendering
         fmtRange.hdcTarget = hdc               ' Point at printer hDC
         fmtRange.rc = rectToPrint              ' Indicate the area on page to print
         fmtRange.rcPage = rectPage             ' Indicate whole size of page

         Dim res As IntPtr = IntPtr.Zero          

         Dim wparam As IntPtr = IntPtr.Zero
         wparam = New IntPtr(1)

         ' Move the pointer to the FORMATRANGE structure in memory
         Dim lparam As IntPtr = IntPtr.Zero
         lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange))
         Marshal.StructureToPtr(fmtRange, lparam, False)

         ' Send the rendered data for printing 
         res = SendMessage(Handle, EM_FORMATRANGE, wparam, lparam)

         ' Free the block of memory allocated
         Marshal.FreeCoTaskMem(lparam)

         ' Release the device context handle obtained by a previous call
         e.Graphics.ReleaseHdc(hdc)

         ' Return last + 1 character printer
         Return res.ToInt32()
      End Function

   End Class
End Namespace
 
This was also discussed here, you can't get it to draw just the text without background color. The code in that post (meant to extend the standard RichTextBoxEx class) enables to draw with the actual backcolor of RTB and not always white as the print graphics does, but it also enables to draw to any graphics so you can get a bitmap and use MakeTransparent method with the backcolor key. Then draw this image over the other.
 
i know this sounds stupid, but where in the code would I use makeTransparent? I cannot figure out where the text is even being draw, I am new a Marshall stuff.
 
Marshal? It is the graphics code you should concentrate about, the FormatRange function overload there is ready made to copy-paste.
VB.NET:
Sub testtext()
    'draw the rich text to a bitmap
    Dim bmp As New Bitmap(400, 400)
    Dim g As Graphics = Graphics.FromImage(bmp)
    Dim rctdraw As New Rectangle(50, 50, 150, 150)
    RTBx.FormatRange(g, rctdraw, 0, RTBx.TextLength)
    RTBx.FormatRangeDone()
    g.Dispose()

    bmp.MakeTransparent(RTBx.BackColor)

    'draw the now transparent rich text image over a 'background' image
    Dim back As Bitmap = My.Resources._141139
    g = Graphics.FromImage(back)
    g.DrawImageUnscaled(bmp, Point.Empty)
    g.Dispose()
    bmp.Dispose()
    Me.PictureBox1.Image = back
End Sub
 
I am having some trouble with this. The code you showed me is for graphics, and I am using it with PrintPageEventsArg. Most documents are multiple pages and it knows when to stop printing by CheckPrint. Well, checkprint is always 0 and it keeps going on forever if I substitute your method into my code. Does this make sense? I am not sure how to incorporate this into the code I have. Sorry for all the questions...I just can't get it to work. Here is the document print code that triggers the code I posted above.

VB.NET:
Private Sub PrintDocument1_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
      ' Print the content of the RichTextBox. Store the last character printed.
      checkPrint = RichTextBoxPrintCtrl1.Print(checkPrint, RichTextBoxPrintCtrl1.TextLength, e)

      ' Look for more pages
      If checkPrint < RichTextBoxPrintCtrl1.TextLength Then
         e.HasMorePages = True
      Else
         e.HasMorePages = False
      End If
   End Sub
 
To be able to do manipulations like you have requested you have to draw text to a bitmap first, rid the backcolor, then draw this image to the print. So when you have your transparent text image in variable 'bmp' you call e.graphics.drawimage(bmp..) in printpage handler. The rectangle to be provided drawrect parameter is each time e.MarginBounds. CharTo, CharFrom and the return value is the same as you are used to.
 
Hmmm....something isn't right. I basically copied and pasted the code into a simple form to start from scratch. It has a button, richtextbox, and a picturebox. When the button is clicked, the image is supossed to show up in the picture box with the text from the richtextbox displayed over it. All that happens is the picture appears with no text. I did the exact code you posted, but no luck.:confused:

VB.NET:
Option Explicit On

Imports System
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Drawing.Printing

Public Class Form1

    Private Structure STRUCT_RECT
        Public Left As Integer
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer
    End Structure

    Private Structure STRUCT_CHARRANGE
        Public cpMin As Integer          ' First character of range (0 for start of doc)
        Public cpMax As Integer          ' Last character of range (-1 for end of doc)
    End Structure

    Private Structure STRUCT_FORMATRANGE
        Public hdc As IntPtr             ' Actual DC to draw on
        Public hdcTarget As IntPtr       ' Target DC for determining text formatting
        Public rc As STRUCT_RECT                ' Region of the DC to draw to (in twips)
        Public rcPage As STRUCT_RECT            ' Region of the whole DC (page size) (in twips)
        Public chrg As STRUCT_CHARRANGE         ' Range of text to draw (see above declaration)
    End Structure

    Private Const WM_USER As Integer = &H400
    Private Const EM_FORMATRANGE As Integer = WM_USER + 57
    Private Declare Function SendMessage Lib "USER32" Alias "SendMessageA" (ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wp As IntPtr, ByVal lp As IntPtr) As IntPtr



    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        'draw the rich text to a bitmap
        Dim bmp As New Bitmap(400, 400)
        Dim g As Graphics = Graphics.FromImage(bmp)
        Dim rctdraw As New Rectangle(0, 0, 150, 150)
        FormatRange(g, rctdraw, 0, RichTextBox1.TextLength)
        g.Dispose()

        bmp.MakeTransparent(RichTextBox1.BackColor)

        'draw the now transparent rich text image over a 'background' image
        Dim back As Bitmap = Image.FromFile(Application.StartupPath & "/PRGicon.bmp")
        g = Graphics.FromImage(back)
        g.DrawImageUnscaled(bmp, Point.Empty)
        g.Dispose()
        bmp.Dispose()
        Me.PictureBox1.Image = back
    End Sub

    Public Function FormatRange(ByVal g As Graphics, ByVal drawrect As Rectangle, _
                            ByVal charFrom As Integer, ByVal charTo As Integer) As Integer
        ' Specify which characters to print
        Dim cr As STRUCT_CHARRANGE
        cr.cpMin = charFrom
        cr.cpMax = charTo
        ' Specify the area inside page margins
        Dim rc As STRUCT_RECT
        rc.Top = (drawrect.Top)
        rc.Bottom = (drawrect.Bottom)
        rc.Left = (drawrect.Left)
        rc.Right = (drawrect.Right)
        ' Get device context of output device
        Dim hdc As IntPtr = g.GetHdc()
        ' Fill in the FORMATRANGE structure
        Dim fr As STRUCT_FORMATRANGE
        fr.chrg = cr
        fr.hdc = hdc
        fr.hdcTarget = hdc
        fr.rc = rc
        fr.rcPage = rc
        Dim lParam As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(fr))
        Marshal.StructureToPtr(fr, lParam, False)
        Dim res As Integer = SendMessage(Handle, EM_FORMATRANGE, 1, lParam)
        Marshal.FreeCoTaskMem(lParam)
        g.ReleaseHdc(hdc)
        Return res
    End Function
End Class
 
You got the wrong Handle essentially, but I think you are on the wrong end of things on how to use this. The Win32 stuff and the FormatRange method has nothing to do in your form class.
The code in that post (meant to extend the standard RichTextBoxEx class)
The class is set up like this:
VB.NET:
class RichTextBoxEx
inherits RichTextBox

- lots of Win32 stuff you don't need to care about
- original FormatRange method

end class
This class already got a FormatRange method that takes print eventargs etc. The method I posted is a modification that gives more flexibility. It has a different signature so you just add it to the class as an overload.
VB.NET:
class RichTextBoxEx
inherits RichTextBox

- lots of Win32 stuff you don't need to care about
- original FormatRange method
> my FormatRange method overload

end class
Now you got an even better RichTextBoxEx control class that allow you to do what you want.
RichTextBox1
That should be "RichTextBoxEx1" if you have set this up correctly. It is the RichTextBoxEx control you add to form and use as RichText Box and not the standard .Net RichTextBox control.
 
But, shouldn't that code I posted last work? It is an exact copy of the example you showed me. I dont think I am quite as experienced as you think. Can I not have this code in a button click handle?
 
But, shouldn't that code I posted last work?
No, because you refer to the wrong window handle.
VB.NET:
It is an exact copy of the example you showed me.
Not exactly. I have also told you exactly where that FormatRange function belongs many times now.
 
Ok, so what you are saying if to make a class that inherits the richtextbox. Once I do that, add it to the form. Then, I can do a button click event with this in it:

VB.NET:
Public Class Form1

   
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        testtext()
    End Sub
    Sub testtext()
        'draw the rich text to a bitmap
        Dim bmp As New Bitmap(400, 400)
        Dim g As Graphics = Graphics.FromImage(bmp)
        Dim rctdraw As New Rectangle(50, 50, 150, 150)
        RichTextBoxEx1.FormatRange(g, rctdraw, 0, RichTextBoxEx1.TextLength)
        RichTextBoxEx1.FormatRangeDone()
        g.Dispose()

        bmp.MakeTransparent(RichTextBoxEx1.BackColor)

        'draw the now transparent rich text image over a 'background' image
        Dim back As Bitmap = Image.FromFile(Application.StartupPath & "/PRGicon.bmp")
        g = Graphics.FromImage(back)
        g.DrawImageUnscaled(bmp, Point.Empty)
        g.Dispose()
        bmp.Dispose()
        Me.PictureBox1.Image = back
    End Sub
End Class


The class I create should look like this?:

VB.NET:
Public Function FormatRange(ByVal g As Graphics, ByVal drawrect As Rectangle, _
                            ByVal charFrom As Integer, ByVal charTo As Integer) As Integer
        ' Specify which characters to print
        Dim cr As STRUCT_CHARRANGE
        cr.cpMin = charFrom
        cr.cpMax = charTo
        ' Specify the area inside page margins
        Dim rc As STRUCT_RECT
        ' rc.Top = HundredthInchToTwips(drawrect.Top)
        'rc.Bottom = HundredthInchToTwips(drawrect.Bottom)
        'rc.Left = HundredthInchToTwips(drawrect.Left)
        'rc.Right = HundredthInchToTwips(drawrect.Right)
        ' Get device context of output device
        Dim hdc As IntPtr = g.GetHdc()
        ' Fill in the FORMATRANGE structure
        Dim fr As STRUCT_FORMATRANGE
        fr.chrg = cr
        fr.hdc = hdc
        fr.hdcTarget = hdc
        fr.rc = rc
        fr.rcPage = rc
        Dim lParam As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(fr))
        Marshal.StructureToPtr(fr, lParam, False)
        Dim res As Integer = SendMessage(Handle, EM_FORMATRANGE, 1, lParam)
        Marshal.FreeCoTaskMem(lParam)
        g.ReleaseHdc(hdc)
        Return res
    End Function

One thing I cannot figure out. "FormatRangeDone is not a member of p.RichTextBoxEx". Where does this come from?
 
Ok, so what you are saying if to make a class that inherits the richtextbox.
No, Microsoft already did that. I don't know where you got your code snippets from, but save you some trouble, go to the link I have posted some times and download the complete class (from richtextboxprinting.exe), from that add the RichTextBoxEx.vb class to your project. Heck, here is the link once again. Then add my overload of the FormatRange function to that class. Once you got all that tucked away you can start using the class. Rebuild and add the RichTextBoxEx control to your form from Toolbox (yes, it is there, in the project components tab of Toolbox). Now you're cooking.
VB.NET:
The class I create should look like this?:
Public Function...
No, that is a Function, not a Class.
One thing I cannot figure out. "FormatRangeDone is not a member of p.RichTextBoxEx". Where does this come from?
See above.
 
Ahhh...I just got it! I did not see the download on the page...I was not making the connection. Got it now! Thanks!
 
Back
Top