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;
}
}