If you have ever needed transparency in a
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
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...
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 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)
this.Location=new Point(0, 0);
PUtils=new Utilities(ToMasterDel, MasterTb);
Utilities is the external class that contains all Windows message constants, Win32 structures, and "helper methods" for the AlphaControls.
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
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.
Wow!! Who would have thought that a
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;
[ StructLayout( LayoutKind.Sequential)] private struct STRUCT_CHARRANGE
public int cpMin;
public int cpMax;
[ 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;
.....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)
IntPtr hdc = g.GetHdc();
Marshal.StructureToPtr(forRange, lParam, false);
SendMessageToMaster(EM_FORMATRANGE, (IntPtr)1, lParam, 1);
IntPtr.Zero, IntPtr.Zero, -1);
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
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 contains a
ForeColor attribute for one character.
CollectionBase and contains attributes for a range of characters.
With that in place, I converted the Win32
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;
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;
...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.
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);
Color back=Color.Empty, fore=Color.Empty;
TBUtils.GetRTHighlight(ref back, ref fore, AlphaBackColor);
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
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!
Aaron Dilliard is a software developer specializing in .NET development including, but not limited to, C#, VB.NET, ASP, ADO, C++. Other interests include DX programming, C and C++.