限制在 WPF 文本框中输入的行数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33147971/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Limit number of lines entered in WPF textbox
提问by Jay
I am trying to limit the number of lines a user can enter in a textbox.
我试图限制用户可以在文本框中输入的行数。
I have been researching - the closest I can find is this: Limit the max number of chars per line in a textbox.
我一直在研究 - 我能找到的最接近的是: Limit the max number of chars per line in a textbox。
And Limit the max number of chars per line in a textboxwhich turns out to be for winforms.
并限制文本框中每行的最大字符数,结果是用于 winform。
This isn't quite what I'm after... also worth mentioning that there is a misleading maxlinesproperty which I have discovered only limits what is shown in the text box.
这不是我所追求的......还值得一提的是,有一个误导性的maxlines属性,我发现它只限制了文本框中显示的内容。
My requirements:
我的要求:
- Do not require the use of a mono-spaced font
- Limit the textbox to having a maximum of 5 lines
- Accepts carriage return
- Do not allow extra carriage returns
- Stops text input when max length has been reached
- Wraps text (don't particularly care if it does this in-between words or breaks up whole words)
- Handles text being pasted into the control and will only paste in what will fit.
- No scroll bars
- Also - and this would nice - having the option of limiting the number of characters per line
- 不需要使用等宽字体
- 将文本框限制为最多 5 行
- 接受回车
- 不允许额外的回车
- 达到最大长度时停止文本输入
- 包装文本(不要特别关心它是否在单词之间这样做或分解整个单词)
- 处理粘贴到控件中的文本,并且只会粘贴适合的文本。
- 没有滚动条
- 另外 - 这会很好 - 可以选择限制每行的字符数
These requirements are for creating a WYSIWYG textbox which will be used for capturing data that will eventually be printed, and the fonts need to be changeable - if the text gets cut off or is too big for a fixed size line - then it will come out that way in print (even if it does not look right).
这些要求用于创建 WYSIWYG 文本框,该文本框将用于捕获最终将打印的数据,并且字体需要可变 - 如果文本被截断或对于固定大小的行来说太大 - 那么它就会出来以这种方式打印(即使它看起来不正确)。
I've had a stab at doing this myself by handling events - but am having a great deal of trouble getting this right. Here is my code so far.
我已经尝试通过处理事件来自己做这件事 - 但是我很难做到这一点。到目前为止,这是我的代码。
XAML
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
PreviewTextInput="UIElement_OnPreviewTextInput"
TextChanged="TextBoxBase_OnTextChanged" />
Code Behind
背后的代码
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;
}
}
}
This doesn't quite work right - I am getting strange behaviour on the last line and the selected position within the textbox keeps jumping about.
这不太正常 - 我在最后一行出现奇怪的行为,并且文本框中的选定位置不断跳动。
As an aside, maybe I am going down the wrong track... I was also wondering if this could be achieved by using a regular expression using something like this: https://stackoverflow.com/a/1103822/685341
顺便说一句,也许我走错了路......我还想知道这是否可以通过使用这样的正则表达式来实现:https: //stackoverflow.com/a/1103822/685341
I am open to any ideas as I have been struggling with this for a while. The requirements listed above are immutable - I am unable to change them.
我对任何想法持开放态度,因为我已经为此苦苦挣扎了一段时间。上面列出的要求是不可变的 - 我无法更改它们。
回答by Jay
Here is my final solution - I'd still like to hear if anyone can come up with a better way of doing this...
这是我的最终解决方案 - 我仍然想听听是否有人能想出更好的方法来做到这一点......
This just handles max number of lines - I haven't done anything with max characters yet - but it's logically a simple extension to what I have already done.
这只是处理最大行数——我还没有对最大字符数做任何事情——但它在逻辑上是对我已经做过的事情的简单扩展。
As I'm handling the textChanged event of the textbox - this also covers pasing into the control too - I haven't found a clean way to truncate text in this event (unless I handle the key_preview separately) - so I'm just not allowing invalid input by undoing.
因为我正在处理文本框的 textChanged 事件 - 这也包括传递到控件中 - 我还没有找到在此事件中截断文本的干净方法(除非我单独处理 key_preview) - 所以我只是没有通过撤消允许无效输入。
XAML
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
<i:Interaction.Behaviors>
<lineLimitingTextBoxWpfTest:LineLimitingBehavior TextBoxMaxAllowedLines="5" />
</i:Interaction.Behaviors>
</TextBox>
Code (for behaviour)
代码(行为)
/// <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 - http://stackoverflow.com/a/25453051/685341
if (textLineCount > TextBoxMaxAllowedLines.Value)
Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action) (() => textBox.Undo()));
}
}
This requires System.Windows.InterActivityto be added to the project and referenced in XAML thusly:
这需要将System.Windows.InterActivity添加到项目中并因此在 XAML 中引用:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
回答by Thomas Phaneuf
I have been looking for answers to problems similar to this for awhile and every answer that I have found involves attaching event handlers and writing a lot of code. This didn't seem right to me, and seems to link the GUI much too tightly to the Codebehind for my tastes. Also, it doesn't seem to leverage the power of WPF.
一段时间以来,我一直在寻找类似问题的答案,我找到的每个答案都涉及附加事件处理程序和编写大量代码。这对我来说似乎不正确,并且似乎将 GUI 与代码隐藏过于紧密地联系在一起,以符合我的口味。此外,它似乎没有利用 WPF 的强大功能。
Limiting the number of lines is actually part of the more generic question of: How do you limit anything in a textbox as it is being edited?
限制行数实际上是更通用问题的一部分:如何在编辑文本框时限制文本框中的任何内容?
The answer is surprisingly simple: bind your textbox to a custom DependencyProperty, then use the CoerceCallback to limit/alter/change the content of the textbox.
答案非常简单:将您的文本框绑定到自定义DependencyProperty,然后使用 CoerceCallback 来限制/更改/更改文本框的内容。
Make sure to set your data context properly - the simplest (but not the best) way is to add the line: DataContext="{Binding RelativeSource={RelativeSource self}}" to the top of your Window or UserControl XAML code.
确保正确设置数据上下文 - 最简单(但不是最好)的方法是将以下行添加:DataContext="{Binding RelativeSource={RelativeSource self}}" 到 Window 或 UserControl XAML 代码的顶部。
XAML
XAML
<TextBox TextWrapping="Wrap"
Text="{Binding NotesText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
AcceptsReturn="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Disabled">
Codebehind (C#)
代码隐藏 (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;
}
(The line-limiting code is crude - but you get the idea).
(行限制代码很粗糙 - 但你明白了)。
The cool thing is, is this provides an easy framework to do other things as well, like limiting to numbers or alpha or special stuff - for instance, here is a simple (non-Regx) Phone number coerce method:
很酷的是,这是否也提供了一个简单的框架来做其他事情,比如限制到数字或字母或特殊的东西 - 例如,这是一个简单的(非 Regx)电话号码强制方法:
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();
}
This seems like a much cleaner and maintainable solution to me that can easily be refactored - while keeping the data and the presentation as separate as possible. The Coerce methods don't need to know anything about where the data came from or is going - it is just data.
对我来说,这似乎是一个更清晰和可维护的解决方案,可以轻松重构 - 同时尽可能保持数据和演示文稿分开。Coerce 方法不需要知道数据从哪里来或去哪里——它只是数据。
回答by Ahmed Nour
That is my simple soultion to set the MaxLines for TextBox and it is working fine, I hope it matches your requirements.
这是我为 TextBox 设置 MaxLines 的简单方法,它运行良好,我希望它符合您的要求。
My_Defined_MaxTextLength is a property to set the MaxLenght
My_Defined_MaxTextLength 是设置 MaxLenght 的属性
My_MaxLines is a property to set Maximum lines
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;
}
};
Best Regards
此致
Ahmed Nour
艾哈迈德·努尔
回答by Idan
Thanks to Jay's answer I was able to find the best solution for me. It will undo paste and block typing.
感谢 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;
}
}
回答by Hyman Berkhout
This should limit the lines and will show the last added lines, as opposed to showing the first lines:
这应该限制行并将显示最后添加的行,而不是显示第一行:
XAML
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
PreviewTextInput="UIElement_OnPreviewTextInput"
TextChanged="TextBoxBase_OnTextChanged" />
Code
代码
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();
}
}
回答by Danil
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;
}
}

