Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Alpha Blended (Transparent Capable) TextBox and RichTextBox

0.00/5 (No votes)
24 Feb 2006 2  
This article shows how to make a transparent/translucent TextBox and RichTextBox.

See the transparency of both contols?

Introduction

If you have ever needed transparency in a TextBox or RichTextBox for any reason, and have tried to attempt it yourself, you know what a pain it can be. Well... now available for your use is the AlphaTextBox and AlphaRichTextBox.

Background

I was online one night looking at my old professor's (Bob Bradley) AlphaBlendTextBox control. (Please visit Bob's article here and visit his website here.) I really liked the idea, so thought I would attempt it myself. I also saw that the RichTextBox transparency issue had not been solved in a good way, and really wanted to find that solution. Going off Bob's article on the AlphaBlendTextBox, I set out on my mission. I used his concepts based on his article, but have not really looked at his code, wanting to find my own solution. I also wanted this to be done purely in .NET with no API calls, not because API calls are bad or anything, just because what I would use the APIs for is already built into .NET. I succeeded. So here it is...

AlphaTextBox

The AlphaTextBox.

I used the same concept of using a control on top of the TextBox to create a transparency effect. Instead of a PictureBox, however, I am using a Panel. The Panel class had to be subclassed to support transparency, and pass along any Windows Notification messages to the underlying TextBox. Because I am not using API calls, I had to supply a delegate function in the AlphaTextBox that will allow the AlphaPanel to use the master control's DefWndProc function. The constructor for the AlphaPanel looks this way:

protected internal AlphaPanel(AlphaTextBox Master)
{
    MasterTb=Master;
    this.Size=Master.Size;
    this.Location=new Point(0, 0);

    ToMasterDel=MasterTb.STClientDel;
            
    //panels by default can have transparent backcolors

    //so you dont need to set that control style

    this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    this.SetStyle(ControlStyles.UserPaint, true);
    this.SetStyle(ControlStyles.DoubleBuffer,true);
    this.SetStyle(ControlStyles.Selectable, false);

    PUtils=new Utilities(ToMasterDel, MasterTb);
}//AlphaPanel Constructor For AlphaTextBox

Utilities is the external class that contains all Windows message constants, Win32 structures, and "helper methods" for the AlphaControls.

The AlphaTextBox was fairly simple to implement. The BackColor property was overridden, and can only be set to Color.Transparent, unless it is being set internally. A new property, AlphaBackColor, was created to set the back color of the AlphaTextBox. I wanted to separate the two as much as possible. When the AlphaTextBox updates itself, it internally sets base.BackColor to AlphaBackColor, issues a WM_PRINT message through DefWndProc, and then maps the AlphaBackColor using a ColorMap and the new AlphaAmount property. This bitmap is stored in memory and cloned to the AlphaPanel.BackgroundImage. This comes in handy when drawing the caret.

To set the caret position, I issued a EM_POSFROMCHAR message through DefWndProc. This returns an integer that contains the x coordinate in the low order byte and the y coordinate in the high order byte. Using Bit Shifts and Bit &, I separated the two, then did some string measurements to get the actual position, because the caret is actually one character ahead of where the position is.

To draw the caret, I simply use a graphics object to draw a rectangle at the given position in the foreground color, with a thickness relative to the font size. This is drawn right onto the AlphaPanel.BackgroundImage eliminating the need for another bitmap. When the caret blinks, the blended bitmap in memory is copied back to AlphaPanel.BackgrondImage as a clone. In this way, you save memory by only ever having two bitmaps.

All mouse events are passed from the AlphaPanel to the AlphaTextBox so that mouse operations work. Many methods and properties had to be overridden for the duplication of the TextBox workings to be accurate.

In a nutshell, that is the AlphaTextBox. For more in-depth looks, download the project.

AlphaRichTextBox

The AlphaRichTextBox. Extended RichText control and Transparency.

Wow!! Who would have thought that a TextBox and RichTextBox could be so different? I thought it would be different, but not this different. The RichTextBox is a completely different animal. A WM_PRINT message does not work for this guy, unless the UserPaint flag is set, in which case, all you get is the background color and no text. Hmmm.... That's not going to work. What about BitBlt, the Win32 graphics method to copy controls to device contexts? Nope... The AlphaPanel covers the RichTextBox, so all you get is a good shot of the same thing you had. I kept thinking "Something has got to work for this." Well, eventually, after countless hours of searching the bowels of the MSDN website, success! Something I could use. The RichTextBox supports a function called FormatRange() to dump the contents of a RichTextBox into a device context, usually for printing. Immediately, I recognized the potential of this function. So after some conversions of the Win32 structs for the call.....

[ StructLayout( LayoutKind.Sequential)]
private struct STRUCT_RECT 
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}//STRUCT_RECT


[ StructLayout( LayoutKind.Sequential)]
private struct STRUCT_CHARRANGE
{
    public int cpMin;
    public int cpMax;
}//STRUCT_CHARRANGE


[ StructLayout( LayoutKind.Sequential)]
private struct STRUCT_FORMATRANGE
{
    public IntPtr hdc; 
    public IntPtr hdcTarget; 
    public STRUCT_RECT rc; 
    public STRUCT_RECT rcPage; 
    public STRUCT_CHARRANGE chrg; 
}//STRUCT FORMATRANGE

.....I created a function call to get a portion of the RichTextBox to a bitmap...

protected internal void FormatRange(Graphics g, 
                        int startChar, int endChar)
{
    STRUCT_CHARRANGE charRange;
    charRange.cpMin=startChar;
    charRange.cpMax=endChar;

    STRUCT_RECT rc;
    rc.top=0;
    rc.bottom=ToTwips(MasterControl.ClientSize.Height+40);
    rc.left=0;

    if(MasterControl.Size.Width-MasterControl.ClientSize.Width==20)
    //VScrollbar present

        rc.right=ToTwips(MasterControl.ClientSize.Width+
                        (MasterControl.ClientSize.Width/80F)+4);
    else
        //VScrollbar not present

        rc.right=ToTwips(MasterControl.ClientSize.Width + 
                        (MasterControl.ClientSize.Width/100F)+5);
    
    STRUCT_RECT rcPage;
    rcPage.top=0;
    rcPage.bottom=ToTwips(MasterControl.Size.Height);
    rcPage.left=0;
    rcPage.right=ToTwips(MasterControl.Size.Width);
    IntPtr hdc = g.GetHdc();

    //This is what specifies all the information

    //for drawing to the bitmap

    STRUCT_FORMATRANGE forRange;
    forRange.chrg=charRange;
    forRange.hdc=hdc;
    forRange.hdcTarget=hdc;
    forRange.rc=rc;
    forRange.rcPage=rcPage;

    //We have to send and IntPtr as the lParam. You cant simply 

    //make a pointer to forRange, so allocate memory and Marshal

    //it to an IntPtr

    IntPtr lParam=Marshal.AllocCoTaskMem(Marshal.SizeOf(forRange)); 
    Marshal.StructureToPtr(forRange, lParam, false);

    SendMessageToMaster(EM_FORMATRANGE, (IntPtr)1, lParam, 1);
    SendMessageToMaster(EM_FORMATRANGE, 
                        IntPtr.Zero, IntPtr.Zero, -1);

    //release resources

    Marshal.FreeCoTaskMem(lParam);
    g.ReleaseHdc(hdc);
}//FormatRange

What is that if statement for? Well, unfortunately, the Win32 uses twips as its measurement, while .NET uses pixels. There are 14.4 twips in 1 pixel. To my dismay, however, the struct specifying bounding regions, STRUCT_RECT, only uses integers. This became a huge problem later when the measurements had to be precise. We will come back to this.

So now, I have my control in a bitmap. What now? Well, the background color of the control is not copied, only the text and its attributes, except for highlighting, of course, so when I map the colors to the AlphaRichTextBox background, I just clear the bitmap to the AlphaBackColor. Next, I draw the bitmap containing the text onto that bitmap, and wallah... I have an AlphaRichTextBox with an AlphaBlended background color! But this is only the first step of the process.

Next came the problem of highlighting text when it is selected. Not a concern with the AlphaTextBox because WM_PRINT has the control paint itself to the device context, so you get a duplicate of what the control looks like. Not so with the AlphaRichTextBox. Highlight attributes are not copied with formatRange(), making necessary a new method for highlighting the text. Drawing over the top of the bitmap is slow and hogs memory, so that was out. I came up with this solution...

I used the EM_SETCHARFORMAT message to change the attributes of the characters selected in the RichTextBox. But before that can work, I had to do something to keep track of the attributes of each character before they were changed. For that, I came up with the RichTextInformation and RichTextInformationCollection classes. RichTextInformation contains a Font, BackColor, and ForeColor attribute for one character. RichTextInformationCollection extends CollectionBase and contains attributes for a range of characters.

With that in place, I converted the Win32 CHARFORMAT2 structure...

[StructLayout(LayoutKind.Sequential)]
private struct CHARFORMAT2 
{
    public int cbSize;
    public int dwMask;
    public int dwEffects;
    public int yHeight;
    public int yOffset;
    public int crTextColor;
    public byte bCharSet;
    public byte bPitchAndFamily;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] 
    public string szFaceName;
    public UInt16 wWeight;
    public UInt16 sSpacing;
    public int crBackColor;
    public int lcid;
    public int dwReserved;
    public UInt16 sStyle;
    public UInt16 wKerning;
    public byte bUnderlineType;
    public byte bAnimation;
    public byte bRevAuthor;
    public byte bReserved1;
}//struct charformat2

...and used the EM_SETCHARFORMAT message to highlight the selected text, and stored the old values in a RichTextInformation object. Now, when more text is selected, or when the current text is deselected, all I had to do was restore the attributes, and bingo, an AlphaRichTextBox with highlighting capability.

One of the last things I had to do was with the caret. When you have two different font sizes on one line, the baseline shifts down for the larger font. When you get the caret position for the smaller font, it draws the caret above the text. Not good. So for that problem, I used the EM_LINEINDEX message to get the start index of the line with the caret, then EM_LINELENGTH to get the length of the line. Then, I went character by character through the line, getting the font sizes, established a baseline, and drew the caret from that baseline.

//get the first char in the current line,

// and the length of the current line

int lineStart = (int)(IntPtr)TBUtils.SendMessageToMaster(
               TBUtils.EM_LINEINDEX, (IntPtr)(-1), IntPtr.Zero, 1);
int lineEnd = lineStart + (int)(IntPtr)TBUtils.SendMessageToMaster(
              TBUtils.EM_LINELENGTH, (IntPtr)lineStart, IntPtr.Zero, 1);

int height=0;

//mark the selection properties before selecting new 

//text or you wont ever be able to change anything.

Color back=Color.Empty, fore=Color.Empty;
TBUtils.GetRTHighlight(ref back, ref fore, AlphaBackColor);
Font selFnt=this.SelectionFont;

//get the tallest charachter in the line so you know 

//how tall to draw the caret.

while(lineStart<lineEnd)
{
    this.Select(lineStart, 1);
    if(this.SelectionFont.Height>height)
        height=this.SelectionFont.Height;

    lineStart++;
}//while

Now, back to the if statement from above. As stated earlier, the STRUCT_RECT structure only takes integers as values, creating a problem when transposing the blended bitmap to the AlphaPanel. Because you can't use floats, the measurements for margins become inaccurate, and the caret is drawn either out of the control all together, or on the next or previous line from where it should. When the scrollbar is set to Vertical, and not ForcedVertical, it changes the measurements around when the scrollbar appears. This became a mess. I had to play around with the numbers for a while before I got something that worked correctly in both situations. The if statement checks to see weather the vertical scrollbar is present or not, then calculates the twips based on that. I have done a lot of testing with these measurements, and they appear to work all of the time, but they still may be a bit off in some instances.

That is pretty much the problem of the AlphaRichTextBox solved.

Points of Interest

I really hope this helps someone. It was a bit of a pain in the you know what. There were way more issues in the development of the AlphaRichTextBox than I listed here. It seemed like every time I got something to work, it caused a problem somewhere. The RichTextBox definitely is a hard control to inherit. There are still a few issues, but I do intend to resolve them when I get time. Your comments and opinions are welcome!

History

  • Version 1.0

    Some issues with the AlphaRichTextBox:

    • When you scroll down, the caret sometimes gets put above the last visible line.
    • Images are visible, but don't have the bounding box when selected.
    • The caret doesn't always advance past an image when inserted, until more text is entered.

    These issues are being resolved.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here