如何从代码后台引用TabControl内容模板中的RichTextBox加载文件?
这问题我做WPF项目时也碰到过——因为ContentTemplate里的控件是数据模板动态生成的,你没法直接用x:Name在后台代码里直接引用到它。下面给你两种可行的解决方案,推荐优先用第一种MVVM绑定的方式,更符合WPF的设计思路。
方法一:通过数据绑定加载内容(推荐)
既然你的TabControl已经绑定到ObservableCollection,那最好的方式是给集合里的每个文件对象添加一个存储RichTextBox内容的属性,然后把RichTextBox的Document和这个属性绑定起来。不过要注意,RichTextBox的Document属性不是依赖属性,没法直接绑定,所以需要写一个转换器。
步骤1:定义文件数据类
先给你的OpenedFiles集合里的元素创建一个类,包含文件名和文档内容:
public class FileItem : INotifyPropertyChanged { private string _name; public string Name { get => _name; set { _name = value; OnPropertyChanged(nameof(Name)); } } private string _filePath; public string FilePath { get => _filePath; set { _filePath = value; OnPropertyChanged(nameof(FilePath)); } } private FlowDocument _documentContent; public FlowDocument DocumentContent { get => _documentContent; set { _documentContent = value; OnPropertyChanged(nameof(DocumentContent)); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:实现Document绑定转换器
创建一个实现IValueConverter的转换器,用来在FlowDocument和RichTextBox之间转换:
public class FlowDocumentConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var doc = value as FlowDocument; if (doc == null) return new FlowDocument(); // 返回新副本避免多个控件共享同一个实例 var newDoc = new FlowDocument(); var range = new TextRange(newDoc.ContentStart, newDoc.ContentEnd); var originalRange = new TextRange(doc.ContentStart, doc.ContentEnd); using (var ms = new MemoryStream()) { originalRange.Save(ms, DataFormats.Rtf); ms.Position = 0; range.Load(ms, DataFormats.Rtf); } return newDoc; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value as FlowDocument; } }
步骤3:修改XAML绑定
在XAML里添加转换器资源,然后绑定RichTextBox的Document:
<TabControl Grid.Column="0" x:Name="openFiles" ItemsSource="{Binding OpenedFiles}"> <TabControl.Resources> <local:FlowDocumentConverter x:Key="FlowDocumentConverter"/> </TabControl.Resources> <TabControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </TabControl.ItemTemplate> <TabControl.ContentTemplate> <DataTemplate> <RichTextBox Document="{Binding DocumentContent, Converter={StaticResource FlowDocumentConverter}}"/> </DataTemplate> </TabControl.ContentTemplate> </TabControl>
步骤4:加载文件到数据对象
现在打开文件时,直接把内容加载到FileItem的DocumentContent里,再添加到集合:
if (openFile1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { if (System.IO.File.Exists(openFile1.FileName)) { var fileItem = new FileItem { Name = System.IO.Path.GetFileName(openFile1.FileName), FilePath = openFile1.FileName }; var doc = new FlowDocument(); var range = new TextRange(doc.ContentStart, doc.ContentEnd); using (var fStream = new System.IO.FileStream(openFile1.FileName, System.IO.FileMode.Open)) { range.Load(fStream, System.Windows.DataFormats.Rtf); } fileItem.DocumentContent = doc; // 假设OpenedFiles是你的ObservableCollection<FileItem>实例 OpenedFiles.Add(fileItem); } }
方法二:通过VisualTreeHelper查找控件(应急方案)
如果你不想改MVVM结构,也可以用VisualTreeHelper遍历可视化树,找到当前TabItem里的RichTextBox。不过这种方法耦合性高,不推荐长期使用。
通用控件查找方法
先写一个通用的查找子控件的方法:
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject { if (parent == null) return null; T foundChild = null; int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is T t && (string.IsNullOrEmpty(childName) || ((FrameworkElement)child).Name == childName)) { foundChild = t; break; } else { foundChild = FindChild<T>(child, childName); if (foundChild != null) break; } } return foundChild; }
查找并加载内容
比如在TabControl的SelectionChanged事件里,找到当前选中Tab里的RichTextBox:
private void openFiles_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (openFiles.SelectedItem != null) { var tabItem = openFiles.ItemContainerGenerator.ContainerFromItem(openFiles.SelectedItem) as TabItem; if (tabItem != null) { var richTextBox = FindChild<RichTextBox>(tabItem, "fileTextBox"); if (richTextBox != null) { var fileItem = openFiles.SelectedItem as FileItem; if (fileItem != null && System.IO.File.Exists(fileItem.FilePath)) { var range = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd); using (var fStream = new System.IO.FileStream(fileItem.FilePath, System.IO.FileMode.Open)) { range.Load(fStream, System.Windows.DataFormats.Rtf); } } } } } }
记得在XAML里给TabControl加上SelectionChanged事件绑定:
<TabControl Grid.Column="0" x:Name="openFiles" ItemsSource="{Binding OpenedFiles}" SelectionChanged="openFiles_SelectionChanged"> <!-- 其他代码不变 --> </TabControl>
内容的提问来源于stack exchange,提问作者Alicja Barankiewicz

