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
相关产品推荐
相关产品推荐

