Reputation: 3190
Is there a way to add a fixed piece of text (TextBlock or Label) inside WPF's TextBox? Such that; user can write text around it in the control, but can't delete or edit it?
I am looking for the reciprocal of this question, that is:
<TextBox>
"Chunk #1: This part of text is editable"
"Chunk #2: This piece is not editable"
"Chunk #3: This text is editable"
</TextBox>
(Note: these are the imaginary chucks there for elaboration, its all continuation of one block of text; which may be multi-line with line-breaks).
where Chunk #2
should move accordingly, as user edits Chunk #1
and #3
.
Upvotes: 3
Views: 4081
Reputation: 7773
If you want the UI behave like a single TextBox, you probably need a customized TextBox control.
After experimenting with various events I came up with the following solution:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public class MyTextBox : TextBox
{
public MyTextBox()
{
TextChanged += new TextChangedEventHandler(MyTextBox_TextChanged);
PreviewKeyDown += new KeyEventHandler(MyTextBox_PreviewKeyDown);
PreviewTextInput += new TextCompositionEventHandler(MyTextBox_PreviewTextInput);
DataObject.AddPastingHandler(this, new DataObjectPastingEventHandler(OnPaste));
}
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
if (!IsValidPositionForEdit())
{
e.CancelCommand(); // do not allow pasting
}
}
void MyTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (!IsValidPositionForEdit())
{
e.Handled = true;
}
}
void MyTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!IsValidPositionForEdit())
{
e.Handled = true;
}
}
private bool IsValidPositionForEdit()
{
return SelectionStart <= this.before || SelectionStart >= this.before + ReadOnlyTextChunk.Length;
}
public static readonly DependencyProperty ReadOnlyTextChunkProperty = DependencyProperty.Register(
"ReadOnlyTextChunk", typeof(string), typeof(MyTextBox), new PropertyMetadata(""));
public string ReadOnlyTextChunk
{
get { return (string)GetValue(ReadOnlyTextChunkProperty); }
set { SetValue(ReadOnlyTextChunkProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Text = ReadOnlyTextChunk;
this.before = 0;
this.after = ReadOnlyTextChunk.Length;
}
void MyTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
foreach (TextChange ch in e.Changes)
{
if (ch.Offset <= this.before) // before text was modified
{
this.before += ch.AddedLength - ch.RemovedLength;
}
else if (ch.Offset >= this.before + ReadOnlyTextChunk.Length) // after text was modified
{
this.after += ch.AddedLength - ch.RemovedLength;
}
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Tab) // jump to after part
{
if (SelectionStart <= this.before)
{
SelectionStart = this.before + ReadOnlyTextChunk.Length;
e.Handled = true;
}
else
{
base.OnKeyDown(e);
}
}
}
private int before; // length of before part
private int after; // length of after part
}
}
Use it as follows:
<local:MyTextBox ReadOnlyTextChunk="Chunk2" TextWrapping="Wrap" Width="200" Height="50"/>
Result looks like this:
Upvotes: 1
Reputation: 7773
You could use three TextBox controls within a StackPanel:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1">
<TextBox BorderThickness="1,1,0,1"/>
<TextBox BorderThickness="0,1,0,1"
Text="Chunk 2" IsReadOnly="True"
IsTabStop="False" />
<TextBox BorderThickness="0,1,1,1"/>
</StackPanel>
</Grid>
The result looks like this:
EDIT: You should be able to jump from the first TextBox to the last using the Tab key.
Upvotes: 4
Reputation: 1955
I might think a masked text box is a possible outside the box solution to this. Check out this article on on codeplex in the wpftoolkit on how to use one.
Upvotes: 1