Поведение TextBox в Windows Forms по умолчанию состоит в том, чтобы выделить весь текст, если он фокусируется в первый раз, путем вкладывания в него, но не при нажатии на него. Мы можем увидеть это в Reflector, посмотрев на TextBox
s OnGotFocus()
override:
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
if (!this.selectionSet)
{
this.selectionSet = true;
if ((this.SelectionLength == 0) && (Control.MouseButtons == MouseButtons.None))
{
base.SelectAll();
}
}
}
Это утверждение if вызывает поведение, которое нам не нравится. Кроме того, чтобы добавить оскорбление ране, установщик свойства Text
вслепую сбрасывает эту переменную selectionSet
каждый раз, когда текст переназначается:
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
this.selectionSet = false;
}
}
Так что если у вас есть TextBox и вкладка в нем, будет выделен весь текст. Если вы щелкнете по нему, выделение будет удалено, и если вы вернётесь в него, ваше положение каретки (и длина выделения ноль) будет сохранено. Но если мы программно установим новый Text
и снова перейдем в TextBox, тогда весь текст будет выделен снова.
Если вы похожи на меня и считаете это поведение раздражающим и непоследовательным, тогда есть два пути решения этой проблемы.
Первое, и, вероятно, самое простое, это просто запустить настройку selectionSet
, вызвав DeselectAll()
в форме Load()
и всякий раз, когда Text
изменяется:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.textBox2.SelectionStart = this.textBox2.Text.Length;
this.textBox2.DeselectAll();
}
(DeselectAll()
просто устанавливает SelectionLength
в ноль. На самом деле SelectionStart
переворачивает selectionSet
переменную TextBox
. В приведенном выше случае вызов DeselectAll()
не является необходимым, поскольку мы установка начала и конца текста, но если мы установим его в любую другую позицию, например, в начало текста, то назвать его будет хорошей идеей.)
Более постоянный способ - создать собственный TextBox с желаемым поведением посредством наследования:
public class NonSelectingTextBox : TextBox
{
// Base class has a selectionSet property, but its private.
// We need to shadow with our own variable. If true, this means
// "don't mess with the selection, the user did it."
private bool selectionSet;
protected override void OnGotFocus(EventArgs e)
{
bool needToDeselect = false;
// We don't want to avoid calling the base implementation
// completely. We mirror the logic that we are trying to avoid;
// if the base implementation will select all of the text, we
// set a boolean.
if (!this.selectionSet)
{
this.selectionSet = true;
if ((this.SelectionLength == 0) &&
(Control.MouseButtons == MouseButtons.None))
{
needToDeselect = true;
}
}
// Call the base implementation
base.OnGotFocus(e);
// Did we notice that the text was selected automatically? Let's
// de-select it and put the caret at the end.
if (needToDeselect)
{
this.SelectionStart = this.Text.Length;
this.DeselectAll();
}
}
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
// Update our copy of the variable since the
// base implementation will have flipped its back.
this.selectionSet = false;
}
}
}
Возможно, вы просто не захотите вызывать base.OnGotFocus()
, но тогда мы потеряем полезную функциональность в базовом Control
классе. И у вас может возникнуть соблазн вообще не связываться с ерундой selectionSet
и просто каждый раз отменять выделение текста в OnGotFocus (), но тогда мы потеряем выделение пользователя, если он выходит из поля и обратно.
Гадкий? Еще бы. Но это то, что есть.