Annie
Annie

Reputation: 3190

WPF - partially uneditable textbox

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

Answers (3)

helb
helb

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:

enter image description here

Upvotes: 1

helb
helb

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:

Resulting view

EDIT: You should be able to jump from the first TextBox to the last using the Tab key.

Upvotes: 4

DROP TABLE users
DROP TABLE users

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

Related Questions