我为TextBox创建了一个子类,并在单独的测试项目中测试了以下方法。
internal static int NumberOfPhysicalLinesInTextBox(TextBox tb)
{
int lc = 0;
while (tb.GetFirstCharIndexFromLine(lc) != -1)
{
++lc;
}
return lc;
}
代码运行良好,但是在我的子类中却没有。仅从方法UpdateVisibleScrollBars
中调用上述静态方法,该方法仅在以下位置调用:
OnTextChanged
OnFontChanged
OnResize
此子类的唯一特色是,当用户未在TextBox和此UpdateVisibleScrollBars
中输入任何内容时,它具有占位符。在此子类NumberOfPhysicalLinesInTextBox
中不返回,它无限期地循环,因为当文本为占位符时,GetFirstCharIndexFromLine
始终返回0:“在此处输入文本...”。
更新:我不使用“行”,因为我需要物理行(“换行”后产生的行),因此我可以知道是否需要显示或隐藏垂直滚动条。文本框设置为WordWrap = true
。这是GetFirstCharIndexFromLine
方法的官方文档。
更新2:所有班级的代码都在下面(没有非英语注释):
class EnhancedTextBox : TextBox
{
internal string PlaceholderText = "Enter text here...";
internal string ActualText
{
get
{
return PlaceholderShown ? "" : Text;
}
set
{
if (value == "" || value == null)
{
if (Text == PlaceholderText)
{
PlaceholderShown = true;
ActualTextChanged?.Invoke(this, EventArgs.Empty);
}
else
{
if (!Focused)
{
BeforeActualTextChanged?.Invoke(this, EventArgs.Empty);
ProgrammaticTextChange = true;
Text = PlaceholderText;
ProgrammaticTextChange = false;
PlaceholderShown = true;
ActualTextChanged?.Invoke(this, EventArgs.Empty);
}
else
{
PlaceholderShown = false;
ActualTextChanged?.Invoke(this, EventArgs.Empty);
}
}
}
else
{
if (Text != value)
{
BeforeActualTextChanged?.Invoke(this, EventArgs.Empty);
ProgrammaticTextChange = true;
Text = value;
ProgrammaticTextChange = false;
}
PlaceholderShown = false;
ActualTextChanged?.Invoke(this, EventArgs.Empty);
}
}
}
internal Color _PlaceholderForeColor = Utils.GrayByPercent(50);
internal Color PlaceholderForeColor
{
get
{
return _PlaceholderForeColor;
}
set
{
if (_PlaceholderForeColor != value)
{
_PlaceholderForeColor = value;
Invalidate();
}
}
}
internal Color _NormalForeColor = Color.Empty;
internal Color NormalForeColor
{
get
{
return _NormalForeColor;
}
set
{
if (_NormalForeColor != value)
{
_NormalForeColor = value;
Invalidate();
}
}
}
internal bool _PlaceholderShown = true;
internal bool PlaceholderShown
{
get
{
return _PlaceholderShown;
}
set
{
if (_PlaceholderShown != value)
{
_PlaceholderShown = value;
ForceUpdatePlaceholderShown(value);
}
}
}
internal void ForceUpdatePlaceholderShown(bool value)
{
ForeColor = value ? PlaceholderForeColor :
NormalForeColor;
Invalidate();
}
public EnhancedTextBox() : base()
{
NormalForeColor = ForeColor;
ProgrammaticTextChange = true;
Text = PlaceholderText;
ProgrammaticTextChange = false;
PlaceholderShown = true;
ForceUpdatePlaceholderShown(true);
UpdateVisibleScrollBars();
}
protected override void OnEnter(EventArgs e)
{
HidePlaceholder();
base.OnEnter(e);
}
private void HidePlaceholder()
{
if (PlaceholderShown)
{
ProgrammaticTextChange = true;
Text = "";
ProgrammaticTextChange = false;
PlaceholderShown = false;
}
}
protected override void OnLeave(EventArgs e)
{
ShowPlaceholder();
base.OnLeave(e);
}
private void ShowPlaceholder()
{
if (Text == "")
{
ProgrammaticTextChange = true;
Text = PlaceholderText;
ProgrammaticTextChange = false;
PlaceholderShown = true;
}
}
internal static int NumberOfPhysicalLinesInTextBox(TextBox tb)
{
int lc = 0;
while (tb.GetFirstCharIndexFromLine(lc) != -1)
{
++lc;
}
return lc;
}
internal bool ProgrammaticTextChange = false;
/// <summary>
/// Do not use this event using handlers. Use ActualTextChanged instead.
/// </summary>
/// <param name="e"></param>
protected override void OnTextChanged(EventArgs e)
{
if (ProgrammaticTextChange)
{
return;
}
ActualText = Text;
base.OnTextChanged(e);
UpdateVisibleScrollBars();
}
private bool busy = false;
private void UpdateVisibleScrollBars()
{
if (busy) return;
busy = true;
bool c1 = false; // chars == Text.Length; // TODO: this not working for WordWrap = false
bool c2 = NumberOfPhysicalLinesInTextBox(this) > 2;
if (c1 && c2)
{
ScrollBars = ScrollBars.Both;
}
else if (c1)
{
ScrollBars = ScrollBars.Horizontal;
}
else if (c2)
{
ScrollBars = ScrollBars.Vertical;
}
else
{
ScrollBars = ScrollBars.None;
}
ScrollToCaret();
busy = false;
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
UpdateVisibleScrollBars();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
UpdateVisibleScrollBars();
}
public event EventHandler ActualTextChanged, BeforeActualTextChanged;
}
如果我将bool c2 = NumberOfPhysicalLinesInTextBox(this) > 2;
替换为bool c2 = false;
,则不会出现无休止的while循环,尽管我发现在调试中经常在c2
行上放置一个断点来调用OnResize处理程序,然后重复单击继续。然后,如果我按几次快速继续键,该程序将启动并可用。
更新3:注释掉UpdateVisibleScrollBars
处理程序中的OnResize
调用,一切正常。调整文本框大小后,如何更改滚动条的可见性?
当前代码:
class EnhancedTextBox : TextBox
{
internal string _PlaceholderText = "Enter text here...";
internal string PlaceholderText
{
get
{
return _PlaceholderText;
}
set
{
_PlaceholderText = value;
Invalidate();
}
}
internal Color _PlaceholderForeColor = SystemColors.GrayText;
public Color PlaceholderForeColor
{
get
{
return _PlaceholderForeColor;
}
set
{
_PlaceholderForeColor = value;
Invalidate();
}
}
[Obsolete]
internal string ActualText
{
get
{
return Text;
}
set
{
if (Text != value)
{
Text = value;
}
}
}
internal Color _NormalForeColor = Color.Empty;
internal Color NormalForeColor
{
get
{
return _NormalForeColor;
}
set
{
if (_NormalForeColor != value)
{
_NormalForeColor = value;
ForeColor = value;
}
}
}
public EnhancedTextBox() : base()
{
NormalForeColor = ForeColor;
WordWrap = true;
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0xf)
{
if (!this.Focused && string.IsNullOrEmpty(this.Text)
&& !string.IsNullOrEmpty(this.PlaceholderText))
{
using (var g = this.CreateGraphics())
{
TextRenderer.DrawText(g, this.PlaceholderText, this.Font,
this.ClientRectangle, this.PlaceholderForeColor, this.BackColor,
TextFormatFlags.Top | TextFormatFlags.Left);
}
}
}
}
internal static int NumberOfPhysicalLinesInTextBox(TextBox tb)
{
int lc = 0;
while (tb.GetFirstCharIndexFromLine(lc) != -1)
{
++lc;
}
return lc;
}
internal bool ProgrammaticTextChange = false;
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
ActualTextChanged?.Invoke(this, e);
UpdateVisibleScrollBars();
}
private bool busy = false;
private Size textSize = Size.Empty;
private void UpdateVisibleScrollBars()
{
if (busy) return;
busy = true;
bool c1 = false; // chars == Text.Length; // TODO: this not working for WordWrap = false
bool c2 = NumberOfPhysicalLinesInTextBox(this) > 2;
if (c1 && c2)
{
ScrollBars = ScrollBars.Both;
}
else if (c1)
{
ScrollBars = ScrollBars.Horizontal;
}
else if (c2)
{
ScrollBars = ScrollBars.Vertical;
}
else
{
ScrollBars = ScrollBars.None;
}
ScrollToCaret();
busy = false;
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
UpdateVisibleScrollBars();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
//UpdateVisibleScrollBars();
}
[Obsolete]
public event EventHandler ActualTextChanged;
}