I have been given this requirement to limit the text entered in a textbox/XamTextEditor to two lines. Unfortunately, I am not allowed to use a monospace/fixed widht font like 'Courier New' - I would have simply used the MaxLength property of the control. Instead I have to use a variable font, so the maxlength is unknown. MFC used to allow for this feature out of the box by simply not specifying a maxlength and not allowing for scrolling, but I don't see how to do this in WPF with an Infragistic XamTextEditor.
In this thread, Curtis Taylor showed how we can get the linecount as the user types text. I wrote a small prototype using this idea and when the linecount=3, I truncated the last character and set the textbox.text to the be the new truncated value.
This way I always have two lines and this kind of worked - unfortunately, when I set the textbox.text to the new string, the focus caret is placed at the beggining of the textbox. This obviously doesn't work since the user now continues typing from the beggining of the first line instead of not being able to type more characters at the end of the second line.
Curtis (or anyone else), do you have any idea on how I could handle this crazy requirement? I can post the XAML and code-behind if needed.
Rather than truncating the text, you should cancel the event so that the textbox never even knows about the keypress. You can use the PreviewKeyUp or PreviewKeyDown and set e.Handled=true if you've exceeded your max linecount.
<ig:XamTextEditor>
<ig:XamTextEditor.Resources>
<Style TargetType="{x:Type TextBox}">
<EventSetter Event="PreviewKeyUp" Handler="OnKeyUpOrDown"/>
<EventSetter Event="PreviewKeyDown" Handler="OnKeyUpOrDown"/>
</Style>
</ig:XamTextEditor.Resources>
</ig:XamTextEditor>
code:
private void OnKeyUpOrDown(object sender, KeyEventArgs e)
{
//if linecount exceeded
e.Handled = true;
}
Tony, thanks for the idea - it almost worked. I implemented what you suggested: when I type the next character that makes it linecount=3, I see that character, but I can no longer enter more text. At that point, I can't delete or type any additional characters since e.handled is being set to true, regardless.
Private Sub OnKeyUpOrDown(ByVal sender As Object, ByVal e As System.Windows.Input.KeyEventArgs) Dim iLineCount As Integer = 0 Dim oTextBox As TextBoxIf TypeOf sender Is TextBox Then oTextBox = CType(sender, TextBox) iLineCount = oTextBox.LineCount If iLineCount > 2 Then e.Handled = TrueEnd If
End Sub
This was close - any other ideas?
You need to filter out the keys that you actually want. You can check the keycode and find out if it's the delete key. You'll probably also want to allow tab key press as well. When you identify those keys, just set e.handled=false;
-Tony
Tony, I'll try it out - I'll also add suport for the arrow keys and page up/page down, home and end. That said, I still have the issue that the first character of the third line appears - this is because of this check:
If iLineCount > 2 Then e.Handled = True
Tony - this means we allow the LineCount to get to 3 - so the first character appears. Any ideas on how we could change the algorithm (different event?) so we allow the last character of line 2, but not the first character of line 3?
D'oh! Great point - and something I don't have any immediate ideas on working around. I'll ask around and see if there are any other ideas..
I spoke to some of the guys around here, and unfortunately, there weren't any other ideas on how to implement this behavior. There are quite a few complexities, including paste support. Even if you could handle this on a character by character basis, you'll have no way to react if the user pastes in 200 lines of text. Perhaps someone else in these forums has another suggestion for you, but it sounds like you'll need to basically build your own custom textbox to handle this set of requirements.
Thanks for following up Tony. I just had an idea this morning:
1) I'll create a second XamTextEditor and make it invisible. 2) As the user types text in the first editor, I'll get the value, add one character and set the value of the second XamTextEditor to this new string.3) I'll then check the linecount on the invisible XamTextEditor - if it's >2 then I'll set e.handled = true, not allowing the user to type what would have been the first character of the 3rd line.
This is just a theory for now, but it might work. I'll let you know the results.(Note: I'll think about the paste issue later)
My idea to create a second XamTextEditor did not work - I realized that I didn't have access to the LineCount property unless I was getting an event on the underlying textbox. Suggestion: can you in future features of NetAdvantage for WPF provide the LineCount property in the XamTextEditor? Alternatively, is there any way I could get the line count directly from a XamTextEditor today?
Instead, I tried out another idea of adding a character to the already typed string and then checking the LineCount. This way, I can stop the user from typing the next character that would cause the LineCount to be greater than 2.
This worked and I copy the code below - I also took care of not allowing paste (CTRL-V, this should be ok in our case) and some other stuff:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Window1" x:Name="Window" Title="Window1" Width="Auto" Height="Auto" xmlns:igEditors="http://infragistics.com/Editors">
<Window.Resources> <ControlTemplate x:Key="XamTextEditorControlTemplate3" TargetType="{x:Type igEditors:XamTextEditor}"> <Border x:Name="MainBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <TextBox Background="#00FFFFFF" BorderBrush="#00FFFFFF" BorderThickness="0,0,0,0" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" ContextMenu="{TemplateBinding ContextMenu}" x:Name="PART_FocusSite" MaxLength="{Binding Path=ValueConstraint.MaxLength, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" Text="{Binding Path=Text, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}" TextAlignment="{TemplateBinding TextAlignmentResolved}" TextWrapping="{TemplateBinding TextWrapping}" HorizontalScrollBarVisibility="{TemplateBinding HorizontalScrollBarVisibility}" IsReadOnly="{TemplateBinding ReadOnly}" VerticalScrollBarVisibility="{TemplateBinding VerticalScrollBarVisibility}" AcceptsReturn="True" MaxLines="2"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsInEditMode" Value="True"> <Setter Property="IsTabStop" Value="False"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Window.Resources>
<StackPanel x:Name="LayoutRoot"> <igEditors:XamTextEditor HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" Height="36" TextWrapping="Wrap" IsInEditMode="True" IsAlwaysInEditMode="True" x:Name="txtXamTextEditorTemp" Width="167" EditTemplate="{DynamicResource XamTextEditorControlTemplate3}"> <igEditors:XamTextEditor.Resources> <Style TargetType="{x:Type TextBox}"> <EventSetter Event="UIElement.PreviewKeyUp" Handler="OnKeyUpOrDown"/> <EventSetter Event="UIElement.PreviewKeyDown" Handler="OnKeyUpOrDown"/> </Style> </igEditors:XamTextEditor.Resources> <igEditors:XamTextEditor.ValueConstraint> <igEditors:ValueConstraint MaxLength="0"/> </igEditors:XamTextEditor.ValueConstraint> </igEditors:XamTextEditor> </StackPanel></Window>
Private Sub OnKeyUpOrDown(ByVal sender As Object, ByVal e As System.Windows.Input.KeyEventArgs) Dim iLineCount As Integer = 0 Dim oTextBox As TextBox Dim sText1 As String = "" Dim sText2 As String = "" Dim iCaretPosition As Integer = 0 If TypeOf sender Is TextBox Then oTextBox = CType(sender, TextBox) iCaretPosition = oTextBox.SelectionStart If e.Key = Input.Key.Enter And oTextBox.LineCount > 1 Then ' Do not allow a third line e.Handled = True ElseIf e.Key = Key.V And Keyboard.Modifiers = ModifierKeys.Control Then ' Do not allow Paste e.Handled = True ElseIf e.Key = Key.Tab Or e.Key = Key.Back Or e.Key = Key.CapsLock Or e.Key = Key.Delete _ Or e.Key = Key.Left Or e.Key = Key.Right Or e.Key = Key.Down Or e.Key = Key.End Or e.Key = Key.Up Or e.Key = Key.RightShift _ Or e.Key = Key.LeftShift Or e.Key = Key.PageDown Or e.Key = Key.PageUp Or e.Key = Key.Home Then e.Handled = False Else sText1 = oTextBox.Text sText2 = sText1 + "A" ' I add one more character to check if we are going to go over two lines when the next key is pressed oTextBox.Text = sText2 iLineCount = oTextBox.LineCount If iLineCount > 2 Then e.Handled = True oTextBox.Text = sText1 ' Restore the original value Try oTextBox.SelectionStart = iCaretPosition ' Restore the original caret position (when we set the value back it was reset to the first character) Catch ex As Exception ' just in case there are no characters End Try End If End If End Sub