仅当文本不适合时,如何在 System.Windows.Forms.TextBox 上显示滚动条?
题
对于 Multiline=True 的 System.Windows.Forms.TextBox,我只想在文本不适合时显示滚动条。
这是一个只读文本框,仅用于显示。它是一个文本框,以便用户可以复制文本。有没有内置的东西支持滚动条的自动显示?如果不是,我应该使用不同的控件吗?或者我是否需要挂钩 TextChanged 并手动检查溢出(如果是这样,如何判断文本是否适合?)
对 WordWrap 和滚动条设置的各种组合没有任何运气。我希望最初没有滚动条,并且仅当文本不适合给定方向时才动态显示每个滚动条。
@nobugz,谢谢,这在禁用 WordWrap 时有效。我不想禁用自动换行,但这是两害相权取其轻。
@André Neves,好点,如果它是用户可编辑的,我会这样做。我同意一致性是 UI 直观性的基本规则。
解决方案
将新类添加到您的项目中并粘贴下面所示的代码。编译。将新控件从工具箱顶部拖放到窗体上。它不是很完美,但应该适合你。
using System;
using System.Drawing;
using System.Windows.Forms;
public class MyTextBox : TextBox {
private bool mScrollbars;
public MyTextBox() {
this.Multiline = true;
this.ReadOnly = true;
}
private void checkForScrollbars() {
bool scroll = false;
int cnt = this.Lines.Length;
if (cnt > 1) {
int pos0 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(0)).Y;
if (pos0 >= 32768) pos0 -= 65536;
int pos1 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(1)).Y;
if (pos1 >= 32768) pos1 -= 65536;
int h = pos1 - pos0;
scroll = cnt * h > (this.ClientSize.Height - 6); // 6 = padding
}
if (scroll != mScrollbars) {
mScrollbars = scroll;
this.ScrollBars = scroll ? ScrollBars.Vertical : ScrollBars.None;
}
}
protected override void OnTextChanged(EventArgs e) {
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnClientSizeChanged(EventArgs e) {
checkForScrollbars();
base.OnClientSizeChanged(e);
}
}
其他提示
当我想解决同样的问题时,我遇到了这个问题。
最简单的方法是更改为 System.Windows.Forms.RichTextBox。在这种情况下,Scrollbars属性可以留在RichTextboxsCrollbars.both的默认值中,该值指示“在需要时同时显示水平和垂直滚动条”。如果在文本框上提供了此功能,那将是很好的。
我也做了一些实验,发现竖线只要开启就会一直显示,横线只要开启就一直显示, WordWrap == false
.
我认为你在这里不会得到你想要的东西。不过,我相信用户会喜欢 Windows 的默认行为,而不是您试图强制执行的行为。如果我使用你的应用程序,如果我的文本框空间突然缩小,我可能会感到烦恼,因为它需要容纳意外的滚动条,因为我给了它太多的文本!
也许让您的应用程序遵循 Windows 的外观和感觉是一个好主意。
nobugz 的解决方案中有一个极其微妙的错误,会导致堆损坏,但前提是您使用 AppendText() 更新 TextBox。
从 OnTextChanged 设置 ScrollBars 属性将导致 Win32 窗口(句柄)被销毁并重新创建。但 OnTextChanged 是从 Win32 编辑控件 (EditML_InsertText) 的内部调用的,该控件此后立即期望该 Win32 编辑控件的内部状态保持不变。不幸的是,由于窗口被重新创建,该内部状态已被操作系统释放,从而导致访问冲突。
所以这个故事的寓意是:如果您要使用 nobugz 的解决方案,请不要使用 AppendText()。
我使用下面的代码取得了一些成功。
public partial class MyTextBox : TextBox
{
private bool mShowScrollBar = false;
public MyTextBox()
{
InitializeComponent();
checkForScrollbars();
}
private void checkForScrollbars()
{
bool showScrollBar = false;
int padding = (this.BorderStyle == BorderStyle.Fixed3D) ? 14 : 10;
using (Graphics g = this.CreateGraphics())
{
// Calcualte the size of the text area.
SizeF textArea = g.MeasureString(this.Text,
this.Font,
this.Bounds.Width - padding);
if (this.Text.EndsWith(Environment.NewLine))
{
// Include the height of a trailing new line in the height calculation
textArea.Height += g.MeasureString("A", this.Font).Height;
}
// Show the vertical ScrollBar if the text area
// is taller than the control.
showScrollBar = (Math.Ceiling(textArea.Height) >= (this.Bounds.Height - padding));
if (showScrollBar != mShowScrollBar)
{
mShowScrollBar = showScrollBar;
this.ScrollBars = showScrollBar ? ScrollBars.Vertical : ScrollBars.None;
}
}
}
protected override void OnTextChanged(EventArgs e)
{
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnResize(EventArgs e)
{
checkForScrollbars();
base.OnResize(e);
}
}
Aidan 所描述的几乎正是我所面临的 UI 场景。由于文本框是只读的,我不需要它来响应 TextChanged。我希望延迟自动滚动重新计算,这样在调整窗口大小时它就不会每秒触发数十次。
对于大多数 UI,同时具有垂直和水平滚动条的文本框是邪恶的,所以我在这里只对垂直滚动条感兴趣。
我还发现 MeasureString 生成的高度实际上大于所需的高度。使用没有边框的文本框的 PreferredHeight 作为行高可以获得更好的结果。
以下内容似乎工作得很好,无论有或没有边框,并且它都可以在 WordWrap 上使用。
只需在需要时调用 AutoScrollVertically(),并可选择指定 recalculateOnResize。
public class TextBoxAutoScroll : TextBox
{
public void AutoScrollVertically(bool recalculateOnResize = false)
{
SuspendLayout();
if (recalculateOnResize)
{
Resize -= OnResize;
Resize += OnResize;
}
float linesHeight = 0;
var borderStyle = BorderStyle;
BorderStyle = BorderStyle.None;
int textHeight = PreferredHeight;
try
{
using (var graphics = CreateGraphics())
{
foreach (var text in Lines)
{
var textArea = graphics.MeasureString(text, Font);
if (textArea.Width < Width)
linesHeight += textHeight;
else
{
var numLines = (float)Math.Ceiling(textArea.Width / Width);
linesHeight += textHeight * numLines;
}
}
}
if (linesHeight > Height)
ScrollBars = ScrollBars.Vertical;
else
ScrollBars = ScrollBars.None;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
finally
{
BorderStyle = borderStyle;
ResumeLayout();
}
}
private void OnResize(object sender, EventArgs e)
{
m_timerResize.Stop();
m_timerResize.Tick -= OnDelayedResize;
m_timerResize.Tick += OnDelayedResize;
m_timerResize.Interval = 475;
m_timerResize.Start();
}
Timer m_timerResize = new Timer();
private void OnDelayedResize(object sender, EventArgs e)
{
m_timerResize.Stop();
Resize -= OnResize;
AutoScrollVertically();
Resize += OnResize;
}
}