精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
锐英源精品开源心得,转载请注明:“锐英源www.wisestudy.cn,孙老师作品,电话13803810136。需要全文内容也请联系孙老师。
One property that the text box controls are lacking is an “inner padding” property, which I have added here. This allows us to place a border around the actual text, which can improve the look of the text box. The border color may be a different color from the text background, in which case, it acts to “frame” the text, or it can be the same color, in which case, it acts as a uniform margin.
In my first attempt to remedy the lack of “inner padding” for the rich text box control, I followed Microsoft’s recommendations and added a rich text box to a panel and simulated a border. Once I had done that, I decided to create a user control so that I wouldn’t have to reinvent the wheel in the future. I called the control a “PaddedTextBox” (even though it was a wrapper for a rich text box), and the code for it can be found here.
文本框控件缺少的一个属性是“内部填充”属性,我在此处添加了该属性。这允许我们在实际文本周围放置边框,这可以改善文本框的外观。边框颜色可以是与文本背景不同的颜色,在这种情况下,它用于“框架”文本,或者它可以是相同的颜色,在这种情况下,它用作均匀的边距。
在我第一次尝试修复富文本框控件缺少“内部填充”时,我遵循了Microsoft的建议,并在面板中添加了一个富文本框并模拟了边框。一旦我这样做了,我决定创建一个用户控件,以便将来不必重新改。我将控件称为“PaddedTextBox”(尽管它是富文本框的包装器),并且可以在此处找到它的代码。
I got a few constructive comments about this control and its limitations, to wit, the rich text box has to be accessed indirectly via the user control, and the fact that it is not the most optimal solution to the problem. The recommendation was to subclass the rich text box instead and handle the drawing of the border in the subclass.
In this article, I have created a second, more sophisticated, solution to the problem based on reader feedback. And, while I was at it, I added some additional eye candy, viz., the ability to optionally display a second, adjustable inner-border of the text with a user-specified color. The PaddedTextBox subclasses the RichTextBox and adds some additional properties: BorderWidth, BorderColor, FixedSingleLineColor, and FixedSingleLineWidth.
我对这个控件及其局限性有一些建设性的评论,即富文本框必须通过用户控件间接访问,并且它不是问题的最佳解决方案。建议改为对富文本框进行子类化,并处理子类中边框的绘制。
在本文中,我基于读者反馈创建了第二个更复杂的问题解决方案。而且,当我在它的时候,我添加了一些额外的好功能,即,能够选择性地显示文本的第二个可调整的内边框和用户指定的颜色。该PaddedTextBox子类RichTextBox,并增加了一些额外的属性:BorderWidth,BorderColor,FixedSingleLineColor,和FixedSingleLineWidth。
The key to adding a user defined border around a control is to handle the WM_NCCALCSIZE window message and make the client area of the control smaller to accommodate the border. According to the MSDN documentation:在控件周围添加用户定义边框的关键是处理WM_NCCALCSIZE窗口消息并使控件的客户区域更小以容纳边框。根据MSDN文档:
The WM_NCCALCSIZE message is sent when the size and position of a window's client area must be calculated. By processing this message, an application can control the content of the window's client area when the size or position of the window changes.在WM_NCCALCSIZE当必须计算一个窗口的客户区的大小和位置被发送的消息。通过处理该消息,当窗口的大小或位置改变时,应用程序可以控件窗口的客户区域的内容。
From Bob Powell, an MVP:来自鲍勃鲍威尔,MVP:
There are two ways that WM_NCCALCSIZE is raised.WM_NCCALCSIZE提出了两种方法。
- with wParam = 0
In this case, you should adjust the client rectangle to be some sub-rectangle of the window rectangle, and return zero.在这种情况下,您应该将客户端矩形调整为窗口矩形的某个子矩形,并返回零。
- with wParam = 1
In this case, you have an option. You can simply adjust the first rectangle in the array of RECTs in the same way as you did for the first case, and return zero. If you do this, the current client rectangle is preserved, and moved to the new position specified in Rect[0].在这种情况下,您有一个选项。您可以RECT按照与第一种情况相同的方式调整s 数组中的第一个矩形,并返回零。如果执行此操作,将保留当前客户端矩形,并将其移动到指定的新位置。Rect[0]
-or-要么-
You can return any combination of the WVR_XXX flags to specify how the window should be redrawn. One of these flags is WVR_VALIDRECTS, which means that you must also update the rectangles in the rest of the NCCALCSIZE_PARAMS structure so that:您可以返回任何WVR_XXX标志组合以指定应如何重绘窗口。其中一个标志是WVR_VALIDRECTS,这意味着您还必须更新NCCALCSIZE_PARAMS结构其余部分中的矩形,以便:
- Rect[0] is the proposed new client position.
- Rect[1] is the source rectangle or the current window, in case you want to preserve the graphics that are already drawn there.
- Rect[2] is the destination rectangle where the source graphics will be copied to. If this rectangle is a different size to the source, the top and left will be copied, but the graphics will be clipped, not resized. You can, for example, copy only a relevant subset of the current client to the new place.
The class System.Windows.Forms.RichTextBox provides a method, protected override voidWndProc(ref System.Windows.Forms.Message m), which enables us to handle messages directed to this control instance. I’ve utilized that to handle the WM_NCCALCSIZE event.
- Rect[0] 是拟议的新客户区位置。
- Rect[1] 是源矩形或当前窗口,以防您想要保留已在那里绘制的图形。
- Rect[2]是将源图形复制到的目标矩形。如果此矩形与源的大小不同,则会复制顶部和左侧,但图形将被剪切,而不会调整大小。例如,您可以仅将当前客户端的相关子集复制到新位置。
该类System.Windows.Forms.RichTextBox提供了一种方法protected override void WndProc(ref System.Windows.Forms.Message m),使我们能够处理定向到此控件实例的消息。我用它来处理这个事件。WM_NCCALCSIZE
The code below illustrates how to implement the cases as defined by Bob Powell. Note that we must use the Marshal class methods to move unmanaged data structures to managed code, and vice versa.
When WParam is 0, we merely shrink the client portion of the window as defined by the RECT structure by the specified size of the border.
When WParam is 1, we do basically the same thing. The major difference is the struct referenced in the LParamfield of the Message struct. In this case, the struct is a bit more complicated, to wit.
下面的代码说明了如何实现Bob Powell定义的案例。请注意,我们必须使用Marshal类方法将非托管数据结构移动到托管代码,反之亦然。
当WParam为0时,我们只是RECT按照指定的边框大小缩小窗口的客户端部分。
什么时候WParam是1,我们基本上做同样的事情。主要区别在于struct的LParam字段中引用的Messagestruct。在这种情况下,结构稍微复杂一点。
[StructLayout(LayoutKind.Sequential)]
// This is the default layout for a structure
public struct NCCALCSIZE_PARAMS
{
// Can't use an array here so simulate one
public RECT rect0, rect1, rect2;
public IntPtr lppos;
}
This struct contains, effectively, a vector of three RECT structures, the first of which, rect0, is modified as above to reset the client area. For this particular purpose, the rest of the structure can be safely ignored. (Note that we cannot use an actual array of RECTs in the structure, since that would allocate the RECTs on the heap and not as part of the structure itself.)
The following is the message handling code that I’ve used to adjust the appropriate RECT structs accordingly:
该结构实际上包含三个RECT结构的向量,第一个结构,rect0如上所述被修改以重置客户区域。出于这个特殊目的,可以安全地忽略结构的其余部分。(注意,我们不能RECT在结构中使用实际的s 数组,因为这会RECT在堆上分配s而不是结构本身的一部分。)
以下是我用于相应调整相应RECT结构的消息处理代码:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case (int)Win32Messages.WM_NCCALCSIZE:
int adjustment = this.BorderStyle == BorderStyle.FixedSingle ? 2 : 0;
if ((int)m.WParam == 0) // False
{
RECT rect = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
// Adjust (shrink) the client rectangle to accommodate the border:
rect.Top += m_BorderWidth - adjustment;
rect.Bottom -= m_BorderWidth - adjustment;
rect.Left += m_BorderWidth - adjustment;
rect.Right -= m_BorderWidth - adjustment;
Marshal.StructureToPtr(rect, m.LParam, false);
m.Result = IntPtr.Zero;
}
else if ((int)m.WParam == 1) // True
{
nccsp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam,
typeof(NCCALCSIZE_PARAMS));
// Adjust (shrink) the client rectangle to accommodate the border:
nccsp.rect0.Top += m_BorderWidth - adjustment;
nccsp.rect0.Bottom -= m_BorderWidth - adjustment;
nccsp.rect0.Left += m_BorderWidth - adjustment;
nccsp.rect0.Right -= m_BorderWidth - adjustment;
Marshal.StructureToPtr(nccsp, m.LParam, false);
m.Result = IntPtr.Zero;
}
base.WndProc(ref m);
break;
There are two more specific messages that we handle here. Whenever the control is painted, we want to paint our border as well. Whenever the non-client area is to be painted, we set a flag, which will ultimately result in a call to a general purpose routine called PaintBorderRect (see below) to do this using the user specified border widths and colors.
Also, I have added the behavior that whenever the textbox is marked as readonly, the caret is hidden. I don’t want a visible caret for uneditable text, so this seemed like a reasonable thing to do.
我们在这里处理两个更具体的消息。每当控件被绘制时,我们也想绘制边框。每当要绘制非客户区域时,我们设置一个标志,最终将调用一个名为PaintBorderRect(见下文)的通用例程,使用用户指定的边框宽度和颜色来执行此操作。
此外,我添加了一个行为,即只要文本框被标记为readonly,就会隐藏插入符号。我不希望看到不可编辑文本的可见插入符号,所以这似乎是一件合理的事情。
case (int)Win32Messages.WM_PAINT:
// Hide the caret if the text is readonly:
hideCaret = this.ReadOnly;
base.WndProc(ref m);
break;
case (int)Win32Messages.WM_NCPAINT:
base.WndProc(ref m);
doPaint = true;
break;
Note that we don’t actually do anything when these messages are detected. We merely set a couple of flags to indicate that something needs to get done. I’ll discuss the rationale below.请注意,检测到这些消息时,我们实际上并未执行任何操作。我们只是设置了几个标志来表明需要完成某些事情。我将在下面讨论基本原理。
default: base.WndProc(ref m); break; } }
The PaintBorderRect routine does the actual drawing of the borders. There are two potential hollow rectangles to draw. First is the standard border around the text box. The width argument, as defined by the caller, determines the pen width which, in turn, determines the number of pixels for each edge of the rectangle to actually draw within the Width and Height of the rectangle defining the control’s size.
The inner border is only drawn when the BorderStyle is FixedSingle. It allows you to add a differently colored, variable size, outline around the text which overlays the innermost pixels of the border rectangle. Note that this implies that the BorderWidth property must be at least as large as the line width of the inner line border. You can change the new FixedSingleLineWidth property if you want a heavier or thinner inner line border. The color of this rectangle is defined by the new property, FixedSingleLineColor, which is passed via the argument borderLineColor. (borderLineColor is defined as an object since, if BorderStyle is notFixedSingle, a null value is passed in lieu of a color, and a Color being a struct, cannot be null.)
该PaintBorderRect程序确实完成边界的实际绘图。有两个潜在的空心矩形可供绘制。首先是文本框周围的标准边框。width参数,就如由调用者所定义,确定笔宽度,进而,确定用于矩形的每个边缘像素的数量,来实际绘制,这绘制在限定控件的尺寸矩形的Width和Height范围内进行。
内边框仅在BorderStyleis为FixedSingle时绘制。它允许您在文本周围添加不同颜色,可变大小的轮廓,该轮廓覆盖边框矩形的最内层像素。请注意,这意味着该BorderWidth属性必须至少与内部线边框的线宽一样大。如果您想要更重或更薄的内线边界,可以更改新FixedSingleLineWidth属性。此矩形的颜色由new属性定义,该属性FixedSingleLineColor通过参数传递borderLineColor。(borderLineColor被定义为object,因为如果BorderStyle是不 FixedSingle,一个null值被代替的颜色的传递,和一个Color为一个结构,不能null)。
private void PaintBorderRect(IntPtr hWnd, int width, Color color,
object borderLineColor)
{
if (width == 0) return; // Without this test there may be artifacts
IntPtr hDC = GetWindowDC(hWnd);
using (Graphics g = Graphics.FromHdc(hDC))
{
using (Pen p = new Pen(color, width))
{
p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
// 2634 -- Start
// There is a bug when drawing a line of width 1
// so we have to special case it and adjust
// the height and width down 1 to circumvent it:
int adjustment = (width == 1 ? 1 : 0);
g.DrawRectangle(p, new Rectangle(0, 0, Width - adjustment,
Height - adjustment));
// 2634 -- End
// Draw the border line if a color is specified and there is room:
if (borderLineColor != null && width >= m_FixedSingleLineWidth
&& m_FixedSingleLineWidth > 0) // 2635
{
p.Color = (Color)borderLineColor;
p.Width = m_FixedSingleLineWidth;
// Overlay the inner border edge with the border line
int offset = width - m_FixedSingleLineWidth;
// 2634 -- Start
// There is a bug when drawing a line of width 1
// so we have to special case it and adjust
// the height and width down 1 to circumvent it:
adjustment = (m_FixedSingleLineWidth == 1 ? 1 : 0);
g.DrawRectangle(p, new Rectangle(offset, offset,
Width - offset - offset - adjustment,
Height - offset - offset - adjustment));
// 2634 -- End
}
}
}
ReleaseDC(hWnd, hDC);
}
Finally, to redraw the control, I’ve added a Redraw routine, which basically sets a flag to ultimately force a call to SetWindowPos or, for the Fixed3D style, to call the control’s RecreateHandle method. In the latter case, I found that this was the only reliable way to ensure that the borders would be redrawn correctly without any artifacts when the BorderStyle is Fixed3D. By the way, if you look at the disassembled code for the RichTextBox, you’ll see a call to RecreateHandle under certain circumstances when the BorderStyleproperty is changed.最后,为了重新绘制控件,我添加了一个Redraw例程,它基本上设置了一个标志,最终强制调用SetWindowPos或者为Fixed3D样式调用控件的RecreateHandle方法。在后一种情况下,当BorderStyle是Fixed3D时,我发现,这是为了确保边界将被重绘正确没有任何错误,唯一可靠的方法。顺便说一句,如果你看一下反汇编的代码RichTextBox,你会看到当BorderStyle属性修改时,RecreateHandle被调用。
/// <span class="code-SummaryComment"><summary></span>
/// This is needed to get the control to repaint correctly.
/// UpdateStyles is NOT sufficient since
/// it leaves artifacts when the control is resized.
/// <span class="code-SummaryComment"></summary></span>
private void Redraw()
{
// Make sure there is no recursion while recreating the handle:
if (!this.RecreatingHandle) doRedraw = true;
// doRedraw = !this.RecreatingHandle;
}
Redraw is invoked in response to a change in one of the new border properties, as well as whenever the control is resized. Note that Redraw itself has to be invoked after the window has been resized, not in the actual resize code. To guarantee that the resize had been completed, I originally posted an application defined message in the OnSizeChanged event, and then did the actual redraw once the message was received in the message handler. However, I ultimately opted for a more general approach, described below.Redraw调用以响应其中一个新边框属性的更改,以及调整控件的大小。请注意,Redraw必须在调整窗口大小后调用自身,而不是在实际的调整大小代码中调用。为了保证调整大小已经完成,我最初在OnSizeChanged事件中发布了一个应用程序定义消息,然后在消息处理程序中收到消息后进行实际重绘。但是,我最终选择了一种更通用的方法,如下所述。
You must have noticed that we still haven’t called PaintBorderRect, SetWindowPos, or RecreateHandleanywhere in the code samples. Instead, we have merely set the flags doRedraw or doPaint. Also, to hide the caret, I’ve just set the hideCaret flag. So, where and when are these functions actually being performed? The answer is in a timer routine.你一定已经注意到,我们还没有叫PaintBorderRect,SetWindowPos或RecreateHandle代码样本中的任何地方。相反,我们只设置了标志doRedraw或doPaint。另外,为了隐藏插入符号,我只是设置了hideCaret标记。那么,这些功能何时何地实际执行?答案是在计时器例程中。
void timer_Tick(object sender, EventArgs e)
{
if (hideCaret)
{
hideCaret = false;
HideCaret(this.Handle);
}
if (doPaint)
{
doPaint = false;
// Draw the inner border if BorderStyle.FixedSingle
// is selected. Null means no border.
PaintBorderRect(this.Handle, m_BorderWidth, m_BorderColor,
(BorderStyle == BorderStyle.FixedSingle) ?
(object)FixedSingleLineColor : null);
}
if (doRedraw)
{
// 2633 -- Start
// We use RecreateHandle for the Fixed3D border
// style to force the control to be recreated.
// It calls DestroyHandle and CreateHandle setting
// RecreatingHandle to true. The downside of this is that it
// will cause the control to flash.
if (BorderStyle == BorderStyle.Fixed3D)
{
// This is only needed to prevent
// artifacts for the Fixed3D border style
RecreateHandle();
}
else
{
// The SWP_FRAMECHANGED (SWP_DRAWFRAME) flag will
// generate WM_NCCALCSIZE and WM_NCPAINT messages among others.
// uint setWindowPosFlags = (uint)(SWP.SWP_NOMOVE |
// SWP.SWP_NOSIZE | SWP.SWP_NOZORDER | SWP.SWP_FRAMECHANGED)
SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0, setWindowPosFlags);
}
// 2633 -- End
doRedraw = false; // This must follow RecreateHandle()
}
}
Doing the actual work in a timer routine (it is arbitrarily set to be invoked every 200 ms.) solves a number of problems. It lets us deal with the resize issue above without having to define an application specific WndProcmessage but, more importantly, it obviates the need to call SetWindowPos / RecreateHandle and PaintBorderRect unnecessarily. This is particularly important in the case of RecreateHandle since this causes the control to flash, so the goal was to minimize calls, both for efficiency and appearance. 200 ms. is a very long time, relatively, and allows multiple redraws and border paints to be compressed into a single call.在定时器例程中执行实际工作(任意设置为每200 ms调用一次)解决了许多问题。它允许我们处理上面的调整大小问题,而不必定义特定于应用程序的WndProc消息,但更重要的是,它消除了调用SetWindowPos/ RecreateHandle和PaintBorderRect不必要的需要。这一点尤其重要,RecreateHandle因为这会导致控制闪烁,因此目标是尽量减少调用,无论是效率还是外观。200毫秒。相对而言,这是一个非常长的时间,并允许将多个重绘和边框绘制压缩为单个调用。
Please note that the RecreateHandle call is only needed when the BorderStyle is Fixed3D. Since I imagine that most users of this control will be using one of the other two border styles primarily anyway, the excess overhead and flashing caused by this shouldn’t be an issue, in practice. For the other border styles of None and FixedSingle, the SetWindowPos call with the SWP_DRAWFRAME / SWP_FRAMECHANGED flag set will cause a WM_NCPAINT message, which will set doPaint to true.请注意,RecreateHandle当只需要调用BorderStyle的Fixed3D。由于我认为此控件的大多数用户将主要使用其他两种边框样式中的一种,因此在实践中由此引起的多余开销和闪烁不应成为问题。对于其他的边框样式None和FixedSingle中,SetWindowPos与呼叫SWP_DRAWFRAME/ SWP_FRAMECHANGED标志设置会造成WM_NCPAINT邮件,将设置doPaint到true。
The attached demo will let you play with the control’s properties so that you can see what the various combinations of colors, sizes, and border styles will display. It’s a useful program in its own right to help in selecting the appropriate text box border styles, colors, and sizes if you are going to use the control in your own programs. Check out the images at the beginning of the article for some examples.附带的演示将让您使用控件的属性,以便您可以看到颜色,大小和边框样式的各种组合将显示什么。如果您要在自己的程序中使用该控件,它本身就是一个有用的程序,可以帮助您选择合适的文本框边框样式,颜色和大小。有关示例,请查看本文开头的图像。
First, compile the control, and put the resulting DLL in the folder of your choice, preferably, one containing your reusable assemblies. Alternatively, you can just copy the files PaddedRichTextBox.dll and PaddedRichTextBox.xmlfrom the supplied zip file.
Open a project and display the Toolbox. Go to the Common Controls section, right click, and select “Choose items”. In the “Choose Toolbox Items” dialog box, press the Browse button, go to the folder containing PaddedTextBox.dll, and select it for your project. Now, you can treat this control as if it were the built-in rich text box with a few additional properties.
首先,编译控件,并将生成的DLL放在您选择的文件夹中,最好是包含可重用程序集的文件夹。或者,您可以从提供的zip文件中复制PaddedRichTextBox.dll和PaddedRichTextBox.xml文件。
打开一个项目并显示工具箱。转到“ 公共控制”部分,右键单击,然后选择“选择项目”。在“选择工具箱项”对话框中,按“ 浏览”按钮,转到包含PaddedTextBox.dll的文件夹,然后为您的项目选择它。现在,您可以将此控件视为具有一些其他属性的内置富文本框。