WPF - 部分不可编辑的文本框

3
有没有一种方法可以在WPF的TextBox内添加一个固定文本块(TextBlock或Label)?使得用户可以在控件中编写文本,但无法删除或编辑它?
我正在寻找此问题的相反解决方案:
<TextBox>
    "Chunk #1: This part of text is editable"

    "Chunk #2: This piece is not editable"

    "Chunk #3: This text is editable"
</TextBox>

(注:这里是为了详细说明而虚构的块,全部内容都是一段文本的延续;该文本可能包含多行和换行符。)

当用户编辑 Chunk #1#3 时,Chunk #2 应相应移动。


2
你最好使用一个由多个TextBlockTextBox组成的ItemsControl - Federico Berasategui
控件被设计用于处理大多数常见情况,而不是程序员可能需要的每个(无限)情况。但是您仍然可以使用内置的控件编写自己的控件。 - L.B
1
@HighCore,你的意思是 <ItemsControl><TextBox/><TextBlock/><TextBox/></ItemsControl> 并且装饰 ItemsControl 使其看起来像一个大的 TexBox,并从 TextBoxTextBlock 中删除样式吗?当用户输入时,TextBox 会自动扩展吗? - Annie
@Annie 是的,基本上是这样,但是使用DataBinding并创建一个适当的ViewModel而不是硬编码XAML,以便拥有动态数量的TextBlocks/Boxes。此外,如果您使用适当的(Wrap)Panel和Width="Auto"或类似的东西,它们将随着用户输入而扩展。 - Federico Berasategui
嗨,不要使用项控件,它主要用于绑定到集合。最好的方法是将您的控件转换为用户控件,以便您可以重复使用它。因此,请创建一个新的用户控件,该控件可以由堆栈面板组成,如@helb在下面提到的那样,然后仅公开要设置的属性,例如来自用户控件的文本框的文本作为依赖属性。 - MoZahid
显示剩余2条评论
3个回答

4
您可以在一个 StackPanel 中使用三个 TextBox 控件:
<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>

结果看起来像这样:

结果视图

编辑:您应该能够使用Tab键从第一个文本框跳到最后一个。

谢谢。这是一个好的解决方案。如果我们可以绑定箭头键,那就太棒了,这样当在第一块的最后一个字符上按下前进键时,它会进入第二块区域。 - Annie
@Annie 为中间的文本框设置 IsTabStop="False",这样就可以使用 Tab 键了。我稍后会更新我的答案... - helb
谢谢。但我刚意识到文本没有在Grid.Column="3"后自动换行。 - Annie
@Annie Grid和Row/Column设置仅用于演示。不确定您所说的“不要超出第一行”的含义。此外,只有列0、1和2。您可以将StackPanel放置在UI的任何位置。 - helb
感谢您的评论。我的意思是,在正常的文本框中,用户可以输入多行文本。当文本溢出时,文本将被换行到下一行。在这种情况下,它没有自动换行。理想情况下,所有三个控件应该具有与其父控件(StackPanel)同步换行的行为。即使保留序列,也要确保“静态后缀Non-editable text之前的长文本和文本”以相同的顺序呈现。 - Annie

1
如果您希望UI的行为类似于单个文本框,则可能需要自定义文本框控件。
经过尝试各种事件后,我想出了以下解决方案:
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
    }
}

使用方法如下:
<local:MyTextBox ReadOnlyTextChunk="Chunk2" TextWrapping="Wrap" Width="200" Height="50"/>

结果看起来像这样:

enter image description here


1

我认为一个带有掩码的文本框可能是一个创新的解决方案。请参考这篇文章,它介绍了如何在wpftoolkit中使用掩码文本框。


你能解释一下如何在这种情况下使它工作吗?我是WPF世界的新手。文本框应该将文本换行到多行(OOTB正常行为),并且它应该在可编辑区域之间有一个不可编辑的文本标识。就像@helb展示的那样,但必须支持多行和文本换行。谢谢。 - Annie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接