You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF中如何使RichTextBox内的TextBlock不可删除?

问题描述

在WPF的RichTextBox中动态添加包含TextBlock的InlineUIContainer作为表单标签(如"First Name:"、"Company:"等),但用户可像删除普通文本一样移除这些标签,需要让标签固定不可删除。曾尝试使用只读Run的方案,但无法将光标移动到对应Run后方,导致用户无法在标签后输入内容。

用户原代码如下:

private void AddVCard()
{
    FlowDocument flowDocument = rich.Document;
    Paragraph dp = new Paragraph(); 
    Run fnRun = new Run() {Foreground = System.Windows.Media.Brushes.Black };
    Run companyRun = new Run() {Foreground = System.Windows.Media.Brushes.Black };
    Run telRun = new Run() {Foreground = System.Windows.Media.Brushes.Black };
    Run emailRun = new Run() { Foreground = System.Windows.Media.Brushes.Black };
    Run adrRun = new Run() {Foreground = System.Windows.Media.Brushes.Black };
    
    InlineUIContainer fnTb = new InlineUIContainer(new TextBlock() {  Tag = "fn", Text = "First Name:", Foreground = System.Windows.Media.Brushes.Green });
    InlineUIContainer ctbh = new InlineUIContainer(new TextBlock() {  Tag = "company", Text = "Company:", Foreground = System.Windows.Media.Brushes.Green });
    InlineUIContainer teltb = new InlineUIContainer(new TextBlock() { Tag = "tel", Text = "Phone/Tel No:", Foreground = System.Windows.Media.Brushes.Green });
    InlineUIContainer emailtb = new InlineUIContainer(new TextBlock() {  Tag = "email", Text = "Email:", Foreground = System.Windows.Media.Brushes.Green });
    InlineUIContainer adrtb = new InlineUIContainer(new TextBlock() {  Tag = "adr", Text = "Address:", Foreground = System.Windows.Media.Brushes.Green });
    fnTb.Unloaded += InlineUIContainer_Unloaded_1;
    ctbh.Unloaded += InlineUIContainer_Unloaded_2;
    teltb.Unloaded += InlineUIContainer_Unloaded_3;
    emailtb.Unloaded += InlineUIContainer_Unloaded_4;
    adrtb.Unloaded += InlineUIContainer_Unloaded_5;
    dp.Inlines.Add(fnTb);

    dp.Inlines.Add(fnRun);
    dp.Inlines.Add(new LineBreak());

    dp.Inlines.Add(ctbh);

    dp.Inlines.Add(companyRun);
    dp.Inlines.Add(new LineBreak());

    dp.Inlines.Add(teltb);

    dp.Inlines.Add(telRun);
    dp.Inlines.Add(new LineBreak());

    dp.Inlines.Add(emailtb);

    dp.Inlines.Add(emailRun);
    dp.Inlines.Add(new LineBreak());

    dp.Inlines.Add(adrtb);

    dp.Inlines.Add(adrRun);

    vcardRuns["fn"] = fnRun;
    vcardRuns["company"] = companyRun;
    vcardRuns["tel"] = telRun;
    vcardRuns["email"] = emailRun;
    vcardRuns["adr"] = adrRun;

    flowDocument.Blocks.Add(dp);
}

private void InlineUIContainer_Unloaded_1(object sender, RoutedEventArgs e)
{
    (sender as InlineUIContainer).Unloaded -= new RoutedEventHandler(InlineUIContainer_Unloaded_1);

    TextBlock tb = new TextBlock();
    tb.Text = "Full Name:";
    tb.Foreground = System.Windows.Media.Brushes.Green; 

    TextPointer tp = rich.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
    InlineUIContainer iuic = new InlineUIContainer(tb, tp);
    iuic.Unloaded += new RoutedEventHandler(InlineUIContainer_Unloaded_1);
}

private void InlineUIContainer_Unloaded_2(object sender, RoutedEventArgs e)
{
    (sender as InlineUIContainer).Unloaded -= new RoutedEventHandler(InlineUIContainer_Unloaded_2);

    TextBlock tb = new TextBlock();
    tb.Text = "Company:";
    tb.Foreground = System.Windows.Media.Brushes.Green;

    TextPointer tp = rich.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
    InlineUIContainer iuic = new InlineUIContainer(tb, tp);
    iuic.Unloaded += new RoutedEventHandler(InlineUIContainer_Unloaded_2);
}

private void InlineUIContainer_Unloaded_3(object sender, RoutedEventArgs e)
{
    (sender as InlineUIContainer).Unloaded -= new RoutedEventHandler(InlineUIContainer_Unloaded_3);

    TextBlock tb = new TextBlock();
    tb.Text = "Phone/Tel:";
    tb.Foreground = System.Windows.Media.Brushes.Green;

    TextPointer tp = rich.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
    InlineUIContainer iuic = new InlineUIContainer(tb, tp);
    iuic.Unloaded += new RoutedEventHandler(InlineUIContainer_Unloaded_3);
}

private void InlineUIContainer_Unloaded_4(object sender, RoutedEventArgs e)
{
    (sender as InlineUIContainer).Unloaded -= new RoutedEventHandler(InlineUIContainer_Unloaded_4);

    TextBlock tb = new TextBlock();
    tb.Text = "Email:";
    tb.Foreground = System.Windows.Media.Brushes.Green;

    TextPointer tp = rich.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
    InlineUIContainer iuic = new InlineUIContainer(tb, tp);
    iuic.Unloaded += new RoutedEventHandler(InlineUIContainer_Unloaded_4);
}

private void InlineUIContainer_Unloaded_5(object sender, RoutedEventArgs e)
{
    (sender as InlineUIContainer).Unloaded -= new RoutedEventHandler(InlineUIContainer_Unloaded_5);

    TextBlock tb = new TextBlock();
    tb.Text = "Address:";
    tb.Foreground = System.Windows.Media.Brushes.Green;

    TextPointer tp = rich.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
    InlineUIContainer iuic = new InlineUIContainer(tb, tp);
    iuic.Unloaded += new RoutedEventHandler(InlineUIContainer_Unloaded_5);
}

解决方案1:保护InlineUIContainer不被删除

通过拦截RichTextBox的删除、剪切操作,阻止保护标签被移除,同时自动将光标定位到可编辑区域。

步骤1:绑定事件

在XAML中为RichTextBox添加事件绑定:

<RichTextBox x:Name="rich" PreviewKeyDown="RichTextBox_PreviewKeyDown" PreviewExecuted="RichTextBox_PreviewExecuted"/>

步骤2:实现事件处理逻辑

private void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
    // 拦截退格和删除键
    if (e.Key == Key.Back || e.Key == Key.Delete)
    {
        var protectedContainer = GetSelectedProtectedContainer();
        if (protectedContainer != null)
        {
            e.Handled = true;
            MoveCaretToEditableArea(protectedContainer);
        }
    }
}

private void RichTextBox_PreviewExecuted(object sender, ExecutedRoutedEventArgs e)
{
    // 拦截剪切命令
    if (e.Command == ApplicationCommands.Cut)
    {
        var protectedContainer = GetSelectedProtectedContainer();
        if (protectedContainer != null)
        {
            e.Handled = true;
        }
    }
}

// 检测选中区域内的受保护InlineUIContainer
private InlineUIContainer GetSelectedProtectedContainer()
{
    var selection = rich.Selection;
    if (!selection.IsEmpty)
    {
        var startInline = selection.Start.Parent as InlineUIContainer;
        var endInline = selection.End.Parent as InlineUIContainer;
        
        if (IsProtectedContainer(startInline)) return startInline;
        if (IsProtectedContainer(endInline)) return endInline;
    }

    // 检测光标是否在保护标签起始位置(退格会删除标签的情况)
    var caretParent = rich.CaretPosition.Parent as InlineUIContainer;
    if (IsProtectedContainer(caretParent) && rich.CaretPosition.CompareTo(caretParent.ContentStart) == 0)
    {
        return caretParent;
    }
    return null;
}

// 判断是否为需要保护的标签容器
private bool IsProtectedContainer(InlineUIContainer container)
{
    if (container?.Tag == null) return false;
    var tag = container.Tag.ToString();
    return tag == "fn" || tag == "company" || tag == "tel" || tag == "email" || tag == "adr";
}

// 将光标移动到标签后方的可编辑Run
private void MoveCaretToEditableArea(InlineUIContainer protectedContainer)
{
    var nextInline = protectedContainer.NextInline;
    if (nextInline is Run editableRun)
    {
        rich.CaretPosition = editableRun.ContentStart;
    }
}

解决方案2:替换为只读Run并修复光标问题

如果倾向于使用Run作为标签,可替换InlineUIContainer,同时通过事件保护标签并处理光标定位。

步骤1:修改AddVCard方法

private void AddVCard()
{
    FlowDocument flowDocument = rich.Document;
    Paragraph dp = new Paragraph(); 
    Run fnRun = new Run() {Foreground = System.Windows.Media.Brushes.Black };
    Run companyRun = new Run() {Foreground = System.Windows.Media.Brushes.Black };
    Run telRun = new Run() {Foreground = System.Windows.Media.Brushes.Black };
    Run emailRun = new Run() { Foreground = System.Windows.Media.Brushes.Black };
    Run adrRun = new Run() {Foreground = System.Windows.Media.Brushes.Black };
    
    // 用带Tag的Run替代InlineUIContainer
    Run fnLabel = new Run("First Name: ") { Foreground = System.Windows.Media.Brushes.Green, Tag = "fn" };
    Run companyLabel = new Run("Company: ") { Foreground = System.Windows.Media.Brushes.Green, Tag = "company" };
    Run telLabel = new Run("Phone/Tel No: ") { Foreground = System.Windows.Media.Brushes.Green, Tag = "tel" };
    Run emailLabel = new Run("Email: ") { Foreground = System.Windows.Media.Brushes.Green, Tag = "email" };
    Run adrLabel = new Run("Address: ") { Foreground = System.Windows.Media.Brushes.Green, Tag = "adr" };

    dp.Inlines.Add(fnLabel);
    dp.Inlines.Add(fnRun);
    dp.Inlines.Add(new LineBreak());

    dp.Inlines.Add(companyLabel);
    dp.Inlines.Add(companyRun);
    dp.Inlines.Add(new LineBreak());

    dp.Inlines.Add(telLabel);
    dp.Inlines.Add(telRun);
    dp.Inlines.Add(new LineBreak());

    dp.Inlines.Add(emailLabel);
    dp.Inlines.Add(emailRun);
    dp.Inlines.Add(new LineBreak());

    dp.Inlines.Add(adrLabel);
    dp.Inlines.Add(adrRun);

    vcardRuns["fn"] = fnRun;
    vcardRuns["company"] = companyRun;
    vcardRuns["tel"] = telRun;
    vcardRuns["email"] = emailRun;
    vcardRuns["adr"] = adrRun;

    flowDocument.Blocks.Add(dp);
}

步骤2:添加保护和光标处理逻辑

复用解决方案1的事件绑定,修改检测和光标移动逻辑:

private void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Back || e.Key == Key.Delete)
    {
        var protectedRun = GetSelectedProtectedRun();
        if (protectedRun != null)
        {
            e.Handled = true;
            MoveCaretToEditableRun(protectedRun);
        }
    }
}

private void RichTextBox_PreviewExecuted(object sender, ExecutedRoutedEventArgs e)
{
    if (e.Command == ApplicationCommands.Cut)
    {
        var protectedRun = GetSelectedProtectedRun();
        if (protectedRun != null)
        {
            e.Handled = true;
        }
    }
}

private Run GetSelectedProtectedRun()
{
    var selection = rich.Selection;
    if (!selection.IsEmpty)
    {
        var startRun = selection.Start.Parent as Run;
        var endRun = selection.End.Parent as Run;
        
        if (IsProtectedRun(startRun)) return startRun;
        if (IsProtectedRun(endRun)) return endRun;
    }

    var caretParent = rich.CaretPosition.Parent as Run;
    if (IsProtectedRun(caretParent))
    {
        // 光标在Run的起始或末尾时,阻止删除并移动光标
        if (rich.CaretPosition.CompareTo(caretParent.ContentStart) == 0 || 
            rich.CaretPosition.CompareTo(caretParent.ContentEnd) == 0)
        {
            return caretParent;
        }
    }
    return null;
}

private bool IsProtectedRun(Run run)
{
    if (run?.Tag == null) return false;
    var tag = run.Tag.ToString();
    return tag == "fn" || tag == "company" || tag == "tel" || tag == "email" || tag == "adr";
}

private void MoveCaretToEditableRun(Run protectedRun)
{
    var nextInline = protectedRun.NextInline;
    if (nextInline is Run editableRun)
    {
        rich.CaretPosition = editableRun.ContentStart;
    }
}

内容的提问来源于stack exchange,提问作者Rushaan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 21:32:01