65.9K
CodeProject 正在变化。 阅读更多。
Home

Alpha 混合(透明可用) TextBox 和 RichTextBox

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (22投票s)

2006年2月24日

8分钟阅读

viewsIcon

259634

downloadIcon

15198

本文展示了如何制作一个透明/半透明的 TextBox 和 RichTextBox。

See the transparency of both contols?

引言

如果您曾因为任何原因需要在 TextBoxRichTextBox 中实现透明度,并且尝试自己动手,您就会知道这有多么麻烦。现在,您可以直接使用 AlphaTextBoxAlphaRichTextBox 了。

背景

有一天晚上,我在网上看到了我老教授(Bob Bradley)的 AlphaBlendTextBox 控件。(请访问 Bob 的文章 此处,并访问他的网站 此处。)我非常喜欢这个想法,所以想自己尝试一下。我还注意到 RichTextBox 的透明度问题还没有得到很好的解决,我一直想找到那个解决方案。借鉴 Bob 关于 AlphaBlendTextBox 的文章,我开始了我的使命。我使用了他文章中的概念,但我并没有真正看过他的代码,我想找到我自己的解决方案。我还想完全在 .NET 中实现,而不使用 API 调用,不是因为 API 调用不好,而是因为我本来要使用 API 的功能 .NET 中已经内置了。我成功了。所以,这就是它...

AlphaTextBox

The AlphaTextBox.

我采用了在 TextBox 上覆盖一个控件来创建透明效果的相同概念。但我使用的是 Panel 而不是 PictureBoxPanel 类必须被继承以支持透明度,并将任何 Windows 通知消息传递到底层的 TextBox。因为我没有使用 API 调用,所以我在 AlphaTextBox 中提供了一个委托函数,允许 AlphaPanel 使用主控件的 DefWndProc 函数。AlphaPanel 的构造函数是这样的:

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 是外部类,其中包含 AlphaControls 的所有 Windows 消息常量、Win32 结构和“辅助方法”。

AlphaTextBox 的实现相当简单。BackColor 属性被重写,只能设置为 Color.Transparent,除非它是在内部设置的。创建了一个新的属性 AlphaBackColor 来设置 AlphaTextBox 的背景色。我想将两者尽可能地分开。当 AlphaTextBox 更新自身时,它会在内部将 base.BackColor 设置为 AlphaBackColor,通过 DefWndProc 发出 WM_PRINT 消息,然后使用 ColorMap 和新的 AlphaAmount 属性映射 AlphaBackColor。此位图存储在内存中,并克隆到 AlphaPanel.BackgroundImage。这在绘制插入符号时非常有用。

为了设置插入符号的位置,我通过 DefWndProc 发出了一个 EM_POSFROMCHAR 消息。这返回一个整数,其中低位字节包含 x 坐标,高位字节包含 y 坐标。使用位移和位与运算,我将两者分开,然后进行一些字符串测量以获得实际位置,因为插入符号实际上比其位置超前一个字符。

要绘制插入符号,我只是使用一个图形对象在给定位置以前景颜色绘制一个矩形,其厚度与字体大小成比例。这直接绘制在 AlphaPanel.BackgroundImage 上,无需额外的位图。当插入符号闪烁时,内存中的混合位图会作为克隆复制回 AlphaPanel.BackgrondImage。这样,您只需维护两个位图,即可节省内存。

所有鼠标事件都从 AlphaPanel 传递到 AlphaTextBox,以确保鼠标操作正常工作。为了准确复制 TextBox 的功能,许多方法和属性都必须被重写。

总而言之,这就是 AlphaTextBox。如果您想深入了解,请下载项目。

AlphaRichTextBox

The AlphaRichTextBox. Extended RichText control and Transparency.

哇!谁能想到 TextBoxRichTextBox 竟然如此不同?我本以为会有所不同,但没想到会差这么多。RichTextBox 完全是另一个级别的存在。WM_PRINT 消息对它不起作用,除非设置了 UserPaint 标志,在这种情况下,您只会得到背景色而没有文本。嗯...这不行。那 BitBlt 呢?Win32 复制控件到设备上下文的图形方法?不行... AlphaPanel 覆盖了 RichTextBox,所以您只会得到与之前相同的画面。我一直在想“总有什么办法能奏效”。最终,经过在 MSDN 网站深处无数小时的搜索,成功了!我找到了可以用的东西。RichTextBox 支持一个名为 FormatRange() 的函数,用于将 RichTextBox 的内容转储到设备上下文,通常用于打印。我立刻意识到了这个函数的潜力。所以在转换了 Win32 结构以进行调用后...

[ 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

...我创建了一个函数调用来获取 RichTextBox 的一部分到位图...

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

那个 if 语句是做什么用的?嗯,不幸的是,Win32 使用 twips 作为度量单位,而 .NET 使用像素。每 1 像素有 14.4 twips。然而,让我沮丧的是,指定边界区域的结构 STRUCT_RECT 只使用整数。这后来成为了一个**巨大的**问题,因为需要精确的度量。

所以现在,我的控件在一个位图中。接下来呢?嗯,控件的背景色没有被复制,只有文本及其属性(当然,高亮除外),所以当我将颜色映射到 AlphaRichTextBox 背景时,我只是将位图清除为 AlphaBackColor。然后,我将包含文本的位图绘制到那个位图上, voilà... 我得到了一个具有 Alpha 混合背景色的 AlphaRichTextBox!但这只是过程的第一步。

接下来是如何处理文本选中的高亮问题。对于 AlphaTextBox 来说这不是问题,因为 WM_PRINT 会让控件自行绘制到设备上下文,您会得到控件外观的复制。但对于 AlphaRichTextBox 就不是这样了。formatRange() 不会复制高亮属性,因此需要一个新方法来高亮文本。在位图上绘制(Drawing over the top of the bitmap)速度慢且占用内存,所以这个方法被排除了。我提出了以下解决方案:

我使用了 EM_SETCHARFORMAT 消息来更改 RichTextBox 中选定字符的属性。但在那之前,我必须做一些事情来跟踪每个字符在被更改之前的属性。为此,我设计了 RichTextInformationRichTextInformationCollection 类。RichTextInformation 包含一个字符的 FontBackColorForeColor 属性。RichTextInformationCollection 继承自 CollectionBase,并包含一个字符范围的属性。

有了这些,我转换了 Win32 的 CHARFORMAT2 结构...

[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

...并使用了 EM_SETCHARFORMAT 消息来高亮选中的文本,并将旧值存储在 RichTextInformation 对象中。现在,当选中更多文本,或者取消当前文本选中时,我只需要恢复属性,搞定,一个具有高亮功能的 AlphaRichTextBox 就完成了。

我需要做的最后几件事之一是关于插入符号。当一行中有两种不同的字体大小时,较大字体的基线会向下偏移。当您获取较小字体的插入符号位置时,插入符号会绘制在文本上方。这不好。所以对于这个问题,我使用了 EM_LINEINDEX 消息来获取插入符号所在行的起始索引,然后使用 EM_LINELENGTH 来获取行的长度。然后,我逐个字符地遍历该行,获取字体大小,建立一个基线,然后从该基线绘制插入符号。

//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

现在,回到上面的 if 语句。如前所述,STRUCT_RECT 结构只接受整数作为值,当将混合位图转换到 AlphaPanel 时会产生问题。由于不能使用浮点数,边距的测量会不准确,插入符号会绘制在控件外部,或者绘制在应该出现的位置的前一行或下一行。当滚动条设置为 Vertical 而不是 ForcedVertical 时,滚动条出现时会改变测量值。这变得一团糟。我不得不花一些时间来调整数字,才在两种情况下都得到了一些正确工作的测量值。那个 if 语句检查垂直滚动条是否存在,然后据此计算 twips。我已经对这些测量值进行了大量测试,它们似乎总是有效,但在某些情况下可能仍然会有些偏差。

基本上,AlphaRichTextBox 的问题就这样解决了。

关注点

我真心希望这对大家有所帮助。这真是太费劲了。AlphaRichTextBox 的开发中遇到的问题比我在这里列出的**多得多**。似乎我每解决一个问题,就会在别的地方引起新的问题。RichTextBox 确实是一个难以继承的控件。仍然存在一些问题,但如果有时间,我打算解决它们。欢迎大家提出评论和意见!

历史

  • 版本 1.0

    AlphaRichTextBox 的一些问题

    • 向下滚动时,插入符号有时会出现在最后一个可见行的上方。
    • 图像可见,但选中时没有边界框。
    • 插入图像后,插入符号并不总是能跳过图像,直到输入更多文本。

    这些问题正在解决中。

© . All rights reserved.