精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
锐英源精品开源心得,转载请注明:“锐英源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.
在我第一次尝试开发弥补缺乏“内部填充”属性的富文本框控件时,我跟着微软的建议,增加了一个富文本框面板,模拟边界。一旦我做了,我决定创建一个用户控件,这样我就白费力气做重复工作。我叫控件为“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.
在这篇文章中,我已经创建了一个第二版本、更加成熟和根据读者反馈解决问题的方案。,而我在这,我添加了一些额外的亮点,即,能够选择性地显示一个第二、可调节内边界的文本,同时用户可指定颜色。RichTextBox PaddedTextBox子类并增加了一些额外的属性: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[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. 矩形[0]是拟议的新客户的位置。
Rect[1] is the source rectangle or the current window, in case you want to preserve the graphics that are already drawn there. 矩形[1]是源矩形或当前窗口,以防止如果您想保存已绘制图形。
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. 矩形[2]是目标矩形,是源图形将被复制进的矩形。如果这个矩形是一个不同大小的来源,顶部和左将被复制,但图形将裁剪,不调整大小。例如,你可以只复制当前客户的相关子集到新的地方。
The class System.Windows.Forms.RichTextBox provides a method, protected override void WndProc(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.
类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.下面的代码说明了如何实现由鲍勃·鲍威尔所定义的案例。请注意,我们必须使用Marshal 类方法将非托管数据转换为托管数据,反之亦然。
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.
当Wparam为0时,我们仅仅缩小窗口的客户区部分,该部分由边框大小指定的矩形来定义。
When WParam is 1, we do basically the same thing. The major difference is the struct referenced in the LParam field of the Message struct. In this case, the struct is a bit more complicated, to wit.
当Wparam为1时,我们所做同样的事情。主要区别是消息结构的LParam成员引用结构体字段。在这种情况下,结构更加复杂,要小心处理。
[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.)
有效,这种结构包含一个向量的三个矩形结构,第一个,rect0,如上修改重置客户区。为了这个目的,其余的结构可以安全地忽略。(注意,我们不能用一个实际的矩形阵列结构,因为这会在堆上分配矩形而不是结构本身的一部分)。
The following is the message handling code that I’ve used to adjust the appropriate RECT structs accordingly: 以下是我使用的消息处理代码相应地调整适当的矩形结构:
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.
我们有两个更具体的消息处理。每当画控件时,我们想要油漆我们的边界。每当非客户区是画,我们设置一个标志,这最终将导致调用一个通用程序称为PaintBorderRect(见下文)使用用户指定的边界宽度和颜色。
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.
我也添加了的行为只要文本框被标记为只读的,插入符号是隐藏的。我不想要一个可见uneditable文本插入符号,这似乎是一个合理的事情。
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.
PaintBorderRect例子演示了绘制边框。要画两个潜在的空心矩形。首先是标准的文本框。调用者所定义的宽度参数,决定了笔的宽度,进而决定了每个矩形边内像素的数量,这些像素绘制在由控件大小决定的矩形范围内。。
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 not FixedSingle, a null value is passed in lieu of a color, and a Color being
内部边界只有当FixedSingle边框样式才绘制。它允许您添加一个不同颜色的、可变大小和轮廓文本(它覆盖边框矩形的最里面的像素)。注意,这意味着BorderWidth属性必须至少和内在线的线宽边界一样大。你可以改变新的FixedSingleLineWidth属性,如果你想要一个更重的或薄的内在线边界。这个矩形的颜色定义的新属性,FixedSingleLineColor,通过borderLineColor的论证
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 BorderStyle property is changed.
最后,重绘控件,我添加了一个重绘程序,基本就是设置一个能最终迫使调用SetWindowPos或者Fixed3D风格的标志来调用控件的RecreateHandle方法。在后一种情况下,我发现这是唯一一个当边框类型是Fixed3D时,能够确保边框样式没有任何工件的情况下正确重绘的可靠方式。顺便说一下,如果你看看RichTextBox的反汇编代码,您将看到调用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.
重新绘制调用响应变化的新边界属性,以及每当控件大小。注意,重绘本身必须被调用后,窗口大小,而不是实际的调整代码。保证调整已经完成,我最初发布的应用程序定义的消息OnSizeChanged事件,然后做实际的重绘一旦收到消息在消息处理程序。然而,我最终选择了一种更一般的方法,下面描述。
You must have noticed that we still haven’t called PaintBorderRect, SetWindowPos, or RecreateHandle anywhere 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 WndProc message 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女士被调用)解决了许多问题。它让我们处理上面的调整问题,而不必定义应用程序特定的指向消息,但更重要的是,它消除了需要调用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调用时只需要Fixed3D边框样式。因为我想象,大多数用户的控件将使用另外两个边界样式之一,主要是不管怎样,多余的开销和闪烁这不该是个问题造成的,在实践中。对于其他边界样式都和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.xml from the supplied zip file.
首先,编译控件,并将由此产生的DLL放在您所选择的文件夹,最好是一个包含可重用的组件。或者,你可以从提供的zip文件中提取PaddedRichTextBox.dll文件和PaddedRichTextBox.xml。
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.
打开一个项目并显示工具箱。去共同控件部分,单击右键,并选择“选择项目”。在“选择工具箱项目”对话框中,点击浏览按钮,去包含PaddedTextBox.dll的文件夹。并选择您的项目。现在,你可以把这个控件当成rich文本框,还有一些额外的属性。