3

If I have a richTextBox and run DrawToBitmap on it, it doesn't draw any of the text inside of the richTextBox.

Bitmap b = new Bitmap(rtb.Width, rtb.Height);
inputControl.DrawToBitmap(b, new Rectangle(0, 0, b.Width, b.Height));

Is there any way to fix this?

sooprise
  • 22,657
  • 67
  • 188
  • 276

6 Answers6

5

I know this is relatively old, but a working solution that I found at http://www.windows-tech.info/3/8ffaf21eed5de2d4.php:

public static Bitmap RtbToBitmap(RichTextBox rtb)
{
    rtb.Update(); // Ensure RTB fully painted
    Bitmap bmp = new Bitmap(rtb.Width, rtb.Height);
    using (Graphics gr = Graphics.FromImage(bmp))
    {
        gr.CopyFromScreen(rtb.PointToScreen(Point.Empty), Point.Empty, rtb.Size);
    }
    return bmp;
}
nurchi
  • 770
  • 11
  • 24
  • This works for me; I'm using windows forms. I have a custom transparent panel with rich text box right below it so that I can write text and draw on the same 'page'. Absolutely perfect solution! – Murat Zazi May 27 '15 at 18:20
  • This doesn't work ... It draws the image, but draws the SaveFileDialog-UI with it. Meaning, it draws the text, but if you load it into, say, Paint, then you will see the SaveFileDialog-Window faded into the background. Check out my answer :) – Momoro Oct 25 '19 at 00:56
  • No, I didn't "add my own crap to it" ... When using your solution, it would also paint the `SaveFileDialog` with it. I painted the `RichTextBox` beforehand, so that it would not paint the `SaveFileDialog`-UI with it. – Momoro Oct 30 '19 at 19:17
  • @Momoro, yes, you did. Call my function BEFOREHAND, after that, save the image and you'll see that the save file dialogue is not a part of the image. Once again: CALL THE FUNCTION **BEFORE** CALLING SaveFileDialog!!! – nurchi Nov 22 '19 at 18:04
4

This thread came up second in Google. Seems to have exactly what you want. Because I imagine you're using this inside your function from this question Accepting Form Elements As Method Arguments?, it's probably best to do something like this.

if(inputControl is RichTextBox)
{
    //do specifc magic here
}
else
{
    //general case
}

You can check for a Control containing RichTextBox recursively

bool ContainsOrIsRichTextBox(Control inputControl)
{
    if(inputControl is RichTextBox) return true;
    foreach(Control control in inputControl.Controls)
    {
        if(ContainsOrIsRichTextBox(control)) return true;
    }
    return false;
}

I haven't compiled this, and there's a way of doing it without risking a StackOverflowException, but this should get you started.

Community
  • 1
  • 1
Novikov
  • 4,399
  • 3
  • 28
  • 36
4

From the MSDN Library article for RichTextBox.DrawToBitmap():

This method is not relevant for this class.

A crummy way to say that the native Windows richedit control doesn't support WM_PRINT. Taking a screen shot is an option, Novikov gave you a link to my answer.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
3

For what it's worth, the later version of the RichTextBox control supports the DrawToBitmap method properly; it also improves performance and has more features.

internal class RichTextBox5: RichTextBox
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr LoadLibrary(string lpFileName);

    protected override CreateParams CreateParams
    {
        get
        {
           CreateParams cparams = base.CreateParams; 
           if (LoadLibrary("msftedit.dll") != IntPtr.Zero)
           {
              cparams.ClassName = "RICHEDIT50W";
           }
           return cparams;
         }
    }
}
EricLaw
  • 56,563
  • 7
  • 151
  • 196
2

I found a related answer here: how to print Rich text box contents on any device contenxt with proper formatting?

I changed this to render my off screen RichTextBox to a bitmap. This way I could create a bitmap off screen and then send it to OpenGL.

    // Convert the unit used by the .NET framework (1/100 inch) 
    // and the unit used by Win32 API calls (twips 1/1440 inch)
    private const double anInch = 14.4;

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct CHARRANGE
    {
        public int cpMin;               // First character of range (0 for start of doc)
        public int cpMax;               // Last character of range (-1 for end of doc)
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct FORMATRANGE
    {
        public IntPtr    hdc;           // Actual DC to draw on
        public IntPtr    hdcTarget;     // Target DC for determining text formatting
        public RECT      rc;            // Region of the DC to draw to (in twips)
        public RECT      rcPage;        // Region of the whole DC (page size) (in twips)
        public CHARRANGE chrg;          // Range of text to draw (see earlier declaration)
    }

    private const int WM_USER        = 0x0400;
    private const int EM_FORMATRANGE = WM_USER + 57;

    [DllImport("USER32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

    /// <summary>
    /// Render the specified RichTextBox onto the specified bitmap
    /// </summary>
    /// <param name="textBox">RichTextBox to render</param>
    /// <param name="bitmap">Bitmap to render the RichTextBox onto</param>
    public void RenderToBitmap(RichTextBox textBox, Bitmap bitmap)
    {
        // Set area to render to be entire bitmap
        RECT rect;
        rect.Left   = 0;
        rect.Top    = 0;
        rect.Right  = (int)(bitmap.Width  * anInch);
        rect.Bottom = (int)(bitmap.Height * anInch);

        Graphics g   = Graphics.FromImage(bitmap);
        IntPtr   hdc = g.GetHdc();

        FORMATRANGE fmtRange;
        fmtRange.chrg.cpMin = textBox.GetCharIndexFromPosition(new Point(0,0));
        fmtRange.chrg.cpMax = textBox.GetCharIndexFromPosition(new Point(bitmap.Width,bitmap.Height));
        fmtRange.hdc        = hdc;                  // Use the same DC for measuring and rendering
        fmtRange.hdcTarget  = hdc;
        fmtRange.rc         = rect;
        fmtRange.rcPage     = rect;

        IntPtr lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
        Marshal.StructureToPtr(fmtRange, lparam, false);

        // Render the control to the bitmap
        SendMessage(textBox.Handle, EM_FORMATRANGE, new IntPtr(1), lparam);

        // Clean up
        Marshal.FreeCoTaskMem(lparam);
        g.ReleaseHdc(hdc);
    }
Community
  • 1
  • 1
AZDean
  • 1,774
  • 2
  • 17
  • 24
  • Funny thing. Here I am 4 years later looking how to do this and what do I find? I find my own answer to this question right here. I didn't even remember writing this code! Even funnier, I found the answer below to be an even better way to do this. – AZDean Sep 25 '18 at 17:52
  • I'm attempting to use this method, but am having some trouble. I want to save the bitmap to a file, but VS is throwing error 'cannot convert type 'void' to type 'bitmap'' - Is there a fix? Thanks :) – Momoro May 12 '20 at 19:52
0

I tested the methods above, and whenever I load the saved Bitmap into an ImageViewer(Like Paint), it would have the SaveFileDialog-UI faded into the background of the text. Luckily, I found an easy fix:

SaveFileDialog bfsd = new SaveFileDialog();       
var rtb = richTextBox1;

        bfsd.Filter = "Bitmap (*.bmp)|*.bmp|All Files (*.*)|*.*";
        bfsd.Title = "Save your text as a Bitmap File";

        rtb.Update(); // Ensure RTB fully painted
        Bitmap bmp = new Bitmap(rtb.Width, rtb.Height);
        using (Graphics gr = Graphics.FromImage(bmp))
        {
            gr.CopyFromScreen(rtb.PointToScreen(Point.Empty), Point.Empty, rtb.Size);
        }

        if (bfsd.ShowDialog()==DialogResult.OK)
        {
         Draw:
            try
            {
                bmp.Save(bfsd.FileName);

                bmp.Dispose();
            }
            catch (Exception)
            {
                DialogResult dr = MessageBox.Show("An error ocurred while attempting to save your Image...", "Error! Error!", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);

                if (dr == DialogResult.Retry)
                {
                    goto Draw;
                }
                else if (dr == DialogResult.Cancel)
                {
                    return;
                }
            }
  • That way, it paints the picture before you even press Save(Don't worry, it won't actually save the image until you press Save)

Pressing Cancel doesn't effect the process, because when you press the Button or MenuStripItem to save it, it will update & re-paint it :)

I implemented a try-catch method, so that it will catch an error if one occurs, rather than the app just (Not Responding)

The catch method is a Retry Button

It will catch the error, and give you the choice to either Cancel the whole Operation, or Retry

I used a goto to be able to just rewind, and make another attempt to save the file, rather than having the SaveFileDialog appear again.

I hope this helps you :)

Momoro
  • 599
  • 4
  • 23