限制在 WPF 文本框中输入的行数
Limit number of lines entered in WPF textbox
我正在尝试限制用户可以在文本框中输入的行数。
我一直在研究 - 我能找到的最接近的是:
Limit the max number of chars per line in a textbox .
和 Limit the max number of chars per line in a textbox 原来是用于 winforms 的。
这不是我想要的...还值得一提的是,有一个误导性的 maxlines 属性,我发现它只限制了文本框中显示的内容。
我的要求:
- 不需要使用等宽字体
- 将文本框限制为最多 5 行
- 接受运输return
- 不允许额外的回车returns
- 达到最大长度时停止文本输入
- 换行文本(不要特别在意它是在单词之间还是将整个单词分开)
- 处理粘贴到控件中的文本,并且只会粘贴适合的文本。
- 没有滚动条
- 另外 - 这很好 - 可以选择限制每行的字符数
这些要求是为了创建一个 WYSIWYG 文本框,该文本框将用于捕获最终将被打印的数据,并且字体需要可变 - 如果文本被截断或对于固定大小的行来说太大 -然后它就会以这种方式打印出来(即使它看起来不正确)。
我曾尝试过自己通过处理事件来做到这一点 - 但我很难做到这一点。到目前为止,这是我的代码。
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
PreviewTextInput="UIElement_OnPreviewTextInput"
TextChanged="TextBoxBase_OnTextChanged" />
代码隐藏
public int TextBoxMaxAllowedLines { get; set; }
public int TextBoxMaxAllowedCharactersPerLine { get; set; }
public MainWindow()
{
InitializeComponent();
TextBoxMaxAllowedLines = 5;
TextBoxMaxAllowedCharactersPerLine = 50;
}
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = (TextBox)sender;
int textLineCount = textBox.LineCount;
if (textLineCount > TextBoxMaxAllowedLines)
{
StringBuilder text = new StringBuilder();
for (int i = 0; i < TextBoxMaxAllowedLines; i++)
text.Append(textBox.GetLineText(i));
textBox.Text = text.ToString();
}
}
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
TextBox textBox = (TextBox)sender;
int textLineCount = textBox.LineCount;
for (int i = 0; i < textLineCount; i++)
{
var line = textBox.GetLineText(i);
if (i == TextBoxMaxAllowedLines-1)
{
int selectStart = textBox.SelectionStart;
textBox.Text = textBox.Text.TrimEnd('\r', '\n');
textBox.SelectionStart = selectStart;
//Last line
if (line.Length > TextBoxMaxAllowedCharactersPerLine)
e.Handled = true;
}
else
{
if (line.Length > TextBoxMaxAllowedCharactersPerLine-1 && !line.EndsWith("\r\n"))
e.Handled = true;
}
}
}
这不太正确 - 我在最后一行出现奇怪的行为,文本框中的选定位置不断跳动。
顺便说一句,也许我走错了路……我也想知道是否可以通过使用正则表达式来实现,使用如下内容:
我对任何想法都持开放态度,因为我已经为此苦苦挣扎了一段时间。上面列出的要求是不可变的 - 我无法更改它们。
这是我的最终解决方案 - 我仍然想听听是否有人能想出更好的方法...
这只处理最大行数——我还没有对最大字符数做任何事情——但从逻辑上讲,它是对我已经完成的工作的简单扩展。
因为我正在处理文本框的 textChanged 事件 - 这也包括传递到控件中 - 我还没有找到截断此事件中文本的干净方法(除非我处理 key_preview分别)-所以我只是不允许通过撤消进行无效输入。
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
<i:Interaction.Behaviors>
<lineLimitingTextBoxWpfTest:LineLimitingBehavior TextBoxMaxAllowedLines="5" />
</i:Interaction.Behaviors>
</TextBox>
代码(针对行为)
/// <summary> limits the number of lines the textbox will accept </summary>
public class LineLimitingBehavior : Behavior<TextBox>
{
/// <summary> The maximum number of lines the textbox will allow </summary>
public int? TextBoxMaxAllowedLines { get; set; }
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>
/// Override this to hook up functionality to the AssociatedObject.
/// </remarks>
protected override void OnAttached()
{
if (TextBoxMaxAllowedLines != null && TextBoxMaxAllowedLines > 0)
AssociatedObject.TextChanged += OnTextBoxTextChanged;
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
/// </summary>
/// <remarks>
/// Override this to unhook functionality from the AssociatedObject.
/// </remarks>
protected override void OnDetaching()
{
AssociatedObject.TextChanged -= OnTextBoxTextChanged;
}
private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = (TextBox)sender;
int textLineCount = textBox.LineCount;
//Use Dispatcher to undo -
if (textLineCount > TextBoxMaxAllowedLines.Value)
Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action) (() => textBox.Undo()));
}
}
这需要将 System.Windows.InterActivity 添加到项目中并在 XAML 中引用,因此:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
一段时间以来,我一直在寻找类似问题的答案,我找到的每个答案都涉及附加事件处理程序和编写大量代码。这对我来说似乎不正确,并且似乎 link GUI 对我的口味来说与代码隐藏太紧密了。此外,它似乎没有利用 WPF 的强大功能。
限制行数实际上是更普遍的问题的一部分:如何在编辑文本框时限制文本框中的任何内容?
答案非常简单:将您的文本框绑定到自定义 DependencyProperty,然后使用 CoerceCallback limit/alter/change 文本框的内容。
确保正确设置您的数据上下文 - 最简单(但不是最好)的方法是添加行:DataContext="{Binding RelativeSource={RelativeSource self}}" 到您的 Window 或 UserControl XAML 代码。
XAML
<TextBox TextWrapping="Wrap"
Text="{Binding NotesText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
AcceptsReturn="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Disabled">
代码隐藏 (C#)
const int MaxLineCount = 10;
const int MaxLineLength = 200;
public static readonly DependencyProperty NotesTextProperty =
DependencyProperty.Register(
name: "NotesText",
propertyType: typeof( String ),
ownerType: typeof( SampleTextBoxEntryWindow ),
typeMetadata: new PropertyMetadata(
defaultValue: string.Empty,
propertyChangedCallback: OnNotesTextPropertyChanged,
coerceValueCallback: CoerceTextLineLimiter ) );
public string NotesText
{
get { return (String)GetValue( NotesTextProperty ); }
set { SetValue( NotesTextProperty, value ); }
}
private static void OnNotesTextPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
// Whatever you want to do when the text changes, like
// set flags to allow buttons to light up, etc.
}
private static object CoerceTextLineLimiter(DependencyObject d, object value)
{
string result = null;
if (value != null)
{
string text = ((string)value);
string[] lines = text.Split( '\n' );
if (lines.Length <= MaxLineCount)
result = text;
else
{
StringBuilder obj = new StringBuilder();
for (int index = 0; index < MaxLineCount; index++)
if (lines[index].Length > 0)
obj.AppendLine( lines[index] > MaxLineLength ? lines[index].Substring(0, MaxLineLength) : lines[index] );
result = obj.ToString();
}
}
return result;
}
(行限制代码很粗糙 - 但你明白了)。
很酷的是,这是否提供了一个简单的框架来做其他事情,比如限制数字或 alpha 或特殊的东西 - 例如,这是一个简单的(非 Regx)Phone数字强制方法:
private static object CoercePhoneNumber(DependencyObject d, object value)
{
StringBuilder result = new StringBuilder();
if (value != null)
{
string text = ((string)value).ToUpper();
foreach (char chr in text)
if ((chr >= '0' && chr <= '9') || (chr == ' ') || (chr == '-') || (chr == '(') || (chr == ')'))
result.Append( chr );
}
return result.ToString();
}
对我来说,这似乎是一个更简洁、更易于维护的解决方案,可以很容易地重构——同时尽可能保持数据和表示分离。 Coerce 方法不需要知道数据的来源或去向 - 它只是数据。
这是我为 TextBox 设置 MaxLines 的简单方法,它工作正常,我希望它符合您的要求。
My_Defined_MaxTextLength是一个属性来设置MaxLenght
My_MaxLines 是 属性 设置最大行数
My_TextBox.TextChanged += (sender, e) =>
{
if(My_TextBox.LineCount > My_MaxLines)
{
My_TextBox.MaxLength = My_TextBox.Text.Length;
}
else
{
My_TextBox.MaxLength = My_Defined_MaxTextLength;
}
};
此致
艾哈迈德·努尔
多亏了 Jay 的回答,我才能够找到最适合我的解决方案。
它将撤消粘贴并阻止输入。
public class LineLimitingBehavior : Behavior<TextBox>
{
public int? TextBoxMaxAllowedLines { get; set; }
protected override void OnAttached()
{
if (TextBoxMaxAllowedLines == null || !(TextBoxMaxAllowedLines > 0)) return;
AssociatedObject.PreviewTextInput += OnTextBoxPreviewTextInput;
AssociatedObject.TextChanged += OnTextBoxTextChanged;
}
private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
var textBox = (TextBox)sender;
if (textBox.LineCount > TextBoxMaxAllowedLines.Value)
Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action)(() => textBox.Undo()));
}
private void OnTextBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
{
var textBox = (TextBox)sender;
var currentText = textBox.Text;
textBox.Text += e.Text;
if (textBox.LineCount > TextBoxMaxAllowedLines.Value)
e.Handled = true;
textBox.Text = currentText;
textBox.CaretIndex = textBox.Text.Length;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewTextInput -= OnTextBoxPreviewTextInput;
AssociatedObject.TextChanged -= OnTextBoxTextChanged;
}
}
这应该限制行数并显示最后添加的行,而不是显示第一行:
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
PreviewTextInput="UIElement_OnPreviewTextInput"
TextChanged="TextBoxBase_OnTextChanged" />
代码
const int MaxLineCount = 10;
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = (TextBox)sender;
int textLineCount = textBox.LineCount;
if (textLineCount > MaxLineCount)
{
StringBuilder text = new StringBuilder();
for (int i = 0; i < MaxLineCount; i++)
{
text.Append(textBox.GetLineText((textLineCount - MaxLineCount) + i - 1));
}
textBox.Text = text.ToString();
}
}
string prev_text = string.Empty;
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
int MaxLineCount = 5;
if (textBox1.LineCount > MaxLineCount)
{
int index = textBox1.CaretIndex;
textBox1.Text = prev_text;
textBox1.CaretIndex = index;
}
else
{
prev_text = textBox1.Text;
}
}
我正在尝试限制用户可以在文本框中输入的行数。
我一直在研究 - 我能找到的最接近的是: Limit the max number of chars per line in a textbox .
和 Limit the max number of chars per line in a textbox 原来是用于 winforms 的。
这不是我想要的...还值得一提的是,有一个误导性的 maxlines 属性,我发现它只限制了文本框中显示的内容。
我的要求:
- 不需要使用等宽字体
- 将文本框限制为最多 5 行
- 接受运输return
- 不允许额外的回车returns
- 达到最大长度时停止文本输入
- 换行文本(不要特别在意它是在单词之间还是将整个单词分开)
- 处理粘贴到控件中的文本,并且只会粘贴适合的文本。
- 没有滚动条
- 另外 - 这很好 - 可以选择限制每行的字符数
这些要求是为了创建一个 WYSIWYG 文本框,该文本框将用于捕获最终将被打印的数据,并且字体需要可变 - 如果文本被截断或对于固定大小的行来说太大 -然后它就会以这种方式打印出来(即使它看起来不正确)。
我曾尝试过自己通过处理事件来做到这一点 - 但我很难做到这一点。到目前为止,这是我的代码。
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
PreviewTextInput="UIElement_OnPreviewTextInput"
TextChanged="TextBoxBase_OnTextChanged" />
代码隐藏
public int TextBoxMaxAllowedLines { get; set; }
public int TextBoxMaxAllowedCharactersPerLine { get; set; }
public MainWindow()
{
InitializeComponent();
TextBoxMaxAllowedLines = 5;
TextBoxMaxAllowedCharactersPerLine = 50;
}
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = (TextBox)sender;
int textLineCount = textBox.LineCount;
if (textLineCount > TextBoxMaxAllowedLines)
{
StringBuilder text = new StringBuilder();
for (int i = 0; i < TextBoxMaxAllowedLines; i++)
text.Append(textBox.GetLineText(i));
textBox.Text = text.ToString();
}
}
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
TextBox textBox = (TextBox)sender;
int textLineCount = textBox.LineCount;
for (int i = 0; i < textLineCount; i++)
{
var line = textBox.GetLineText(i);
if (i == TextBoxMaxAllowedLines-1)
{
int selectStart = textBox.SelectionStart;
textBox.Text = textBox.Text.TrimEnd('\r', '\n');
textBox.SelectionStart = selectStart;
//Last line
if (line.Length > TextBoxMaxAllowedCharactersPerLine)
e.Handled = true;
}
else
{
if (line.Length > TextBoxMaxAllowedCharactersPerLine-1 && !line.EndsWith("\r\n"))
e.Handled = true;
}
}
}
这不太正确 - 我在最后一行出现奇怪的行为,文本框中的选定位置不断跳动。
顺便说一句,也许我走错了路……我也想知道是否可以通过使用正则表达式来实现,使用如下内容:
我对任何想法都持开放态度,因为我已经为此苦苦挣扎了一段时间。上面列出的要求是不可变的 - 我无法更改它们。
这是我的最终解决方案 - 我仍然想听听是否有人能想出更好的方法...
这只处理最大行数——我还没有对最大字符数做任何事情——但从逻辑上讲,它是对我已经完成的工作的简单扩展。
因为我正在处理文本框的 textChanged 事件 - 这也包括传递到控件中 - 我还没有找到截断此事件中文本的干净方法(除非我处理 key_preview分别)-所以我只是不允许通过撤消进行无效输入。
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
<i:Interaction.Behaviors>
<lineLimitingTextBoxWpfTest:LineLimitingBehavior TextBoxMaxAllowedLines="5" />
</i:Interaction.Behaviors>
</TextBox>
代码(针对行为)
/// <summary> limits the number of lines the textbox will accept </summary>
public class LineLimitingBehavior : Behavior<TextBox>
{
/// <summary> The maximum number of lines the textbox will allow </summary>
public int? TextBoxMaxAllowedLines { get; set; }
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>
/// Override this to hook up functionality to the AssociatedObject.
/// </remarks>
protected override void OnAttached()
{
if (TextBoxMaxAllowedLines != null && TextBoxMaxAllowedLines > 0)
AssociatedObject.TextChanged += OnTextBoxTextChanged;
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
/// </summary>
/// <remarks>
/// Override this to unhook functionality from the AssociatedObject.
/// </remarks>
protected override void OnDetaching()
{
AssociatedObject.TextChanged -= OnTextBoxTextChanged;
}
private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = (TextBox)sender;
int textLineCount = textBox.LineCount;
//Use Dispatcher to undo -
if (textLineCount > TextBoxMaxAllowedLines.Value)
Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action) (() => textBox.Undo()));
}
}
这需要将 System.Windows.InterActivity 添加到项目中并在 XAML 中引用,因此:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
一段时间以来,我一直在寻找类似问题的答案,我找到的每个答案都涉及附加事件处理程序和编写大量代码。这对我来说似乎不正确,并且似乎 link GUI 对我的口味来说与代码隐藏太紧密了。此外,它似乎没有利用 WPF 的强大功能。
限制行数实际上是更普遍的问题的一部分:如何在编辑文本框时限制文本框中的任何内容?
答案非常简单:将您的文本框绑定到自定义 DependencyProperty,然后使用 CoerceCallback limit/alter/change 文本框的内容。
确保正确设置您的数据上下文 - 最简单(但不是最好)的方法是添加行:DataContext="{Binding RelativeSource={RelativeSource self}}" 到您的 Window 或 UserControl XAML 代码。
XAML
<TextBox TextWrapping="Wrap"
Text="{Binding NotesText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
AcceptsReturn="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Disabled">
代码隐藏 (C#)
const int MaxLineCount = 10;
const int MaxLineLength = 200;
public static readonly DependencyProperty NotesTextProperty =
DependencyProperty.Register(
name: "NotesText",
propertyType: typeof( String ),
ownerType: typeof( SampleTextBoxEntryWindow ),
typeMetadata: new PropertyMetadata(
defaultValue: string.Empty,
propertyChangedCallback: OnNotesTextPropertyChanged,
coerceValueCallback: CoerceTextLineLimiter ) );
public string NotesText
{
get { return (String)GetValue( NotesTextProperty ); }
set { SetValue( NotesTextProperty, value ); }
}
private static void OnNotesTextPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
// Whatever you want to do when the text changes, like
// set flags to allow buttons to light up, etc.
}
private static object CoerceTextLineLimiter(DependencyObject d, object value)
{
string result = null;
if (value != null)
{
string text = ((string)value);
string[] lines = text.Split( '\n' );
if (lines.Length <= MaxLineCount)
result = text;
else
{
StringBuilder obj = new StringBuilder();
for (int index = 0; index < MaxLineCount; index++)
if (lines[index].Length > 0)
obj.AppendLine( lines[index] > MaxLineLength ? lines[index].Substring(0, MaxLineLength) : lines[index] );
result = obj.ToString();
}
}
return result;
}
(行限制代码很粗糙 - 但你明白了)。
很酷的是,这是否提供了一个简单的框架来做其他事情,比如限制数字或 alpha 或特殊的东西 - 例如,这是一个简单的(非 Regx)Phone数字强制方法:
private static object CoercePhoneNumber(DependencyObject d, object value)
{
StringBuilder result = new StringBuilder();
if (value != null)
{
string text = ((string)value).ToUpper();
foreach (char chr in text)
if ((chr >= '0' && chr <= '9') || (chr == ' ') || (chr == '-') || (chr == '(') || (chr == ')'))
result.Append( chr );
}
return result.ToString();
}
对我来说,这似乎是一个更简洁、更易于维护的解决方案,可以很容易地重构——同时尽可能保持数据和表示分离。 Coerce 方法不需要知道数据的来源或去向 - 它只是数据。
这是我为 TextBox 设置 MaxLines 的简单方法,它工作正常,我希望它符合您的要求。
My_Defined_MaxTextLength是一个属性来设置MaxLenght
My_MaxLines 是 属性 设置最大行数
My_TextBox.TextChanged += (sender, e) =>
{
if(My_TextBox.LineCount > My_MaxLines)
{
My_TextBox.MaxLength = My_TextBox.Text.Length;
}
else
{
My_TextBox.MaxLength = My_Defined_MaxTextLength;
}
};
此致
艾哈迈德·努尔
多亏了 Jay 的回答,我才能够找到最适合我的解决方案。 它将撤消粘贴并阻止输入。
public class LineLimitingBehavior : Behavior<TextBox>
{
public int? TextBoxMaxAllowedLines { get; set; }
protected override void OnAttached()
{
if (TextBoxMaxAllowedLines == null || !(TextBoxMaxAllowedLines > 0)) return;
AssociatedObject.PreviewTextInput += OnTextBoxPreviewTextInput;
AssociatedObject.TextChanged += OnTextBoxTextChanged;
}
private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
var textBox = (TextBox)sender;
if (textBox.LineCount > TextBoxMaxAllowedLines.Value)
Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action)(() => textBox.Undo()));
}
private void OnTextBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
{
var textBox = (TextBox)sender;
var currentText = textBox.Text;
textBox.Text += e.Text;
if (textBox.LineCount > TextBoxMaxAllowedLines.Value)
e.Handled = true;
textBox.Text = currentText;
textBox.CaretIndex = textBox.Text.Length;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewTextInput -= OnTextBoxPreviewTextInput;
AssociatedObject.TextChanged -= OnTextBoxTextChanged;
}
}
这应该限制行数并显示最后添加的行,而不是显示第一行:
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
PreviewTextInput="UIElement_OnPreviewTextInput"
TextChanged="TextBoxBase_OnTextChanged" />
代码
const int MaxLineCount = 10;
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = (TextBox)sender;
int textLineCount = textBox.LineCount;
if (textLineCount > MaxLineCount)
{
StringBuilder text = new StringBuilder();
for (int i = 0; i < MaxLineCount; i++)
{
text.Append(textBox.GetLineText((textLineCount - MaxLineCount) + i - 1));
}
textBox.Text = text.ToString();
}
}
string prev_text = string.Empty;
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
int MaxLineCount = 5;
if (textBox1.LineCount > MaxLineCount)
{
int index = textBox1.CaretIndex;
textBox1.Text = prev_text;
textBox1.CaretIndex = index;
}
else
{
prev_text = textBox1.Text;
}
}