任何方式来使WPF文本块可选?

我想让Witty (一个开源的Twitter客户端)中显示的文本可选。 它当前使用自定义文本块显示。 我需要使用TextBlock,因为我正在使用文本块的内联来显示和格式化@username和链接作为超链接。 频繁的请求是能够复制粘贴文本。 为了做到这一点,我需要使TextBlock可选。

我试图通过显示文本使用只读文本框样式来看起来像一个文本块,但这不会工作在我的情况,因为一个文本框没有内联。 换句话说,我无法在TextBox中单独设置样式或格式,就像我可以使用TextBlock一样。

有任何想法吗?

<TextBox Background="Transparent" BorderThickness="0" Text="{Binding Text, Mode=OneWay}" IsReadOnly="True" TextWrapping="Wrap" /> 

为TextBlock创buildControlTemplate,并将TextBox设置为readonly属性。 或者只是使用TextBox并使其只读,然后您可以更改TextBox.Style,使其看起来像TextBlock。

我一直都找不到真正回答这个问题的例子。 所有的答案都使用了一个Textbox或RichTextbox。 我需要一个允许我使用TextBlock的解决scheme,这是我创build的解决scheme。

我相信正确的方法是扩展TextBlock类。 这是我用来扩展TextBlock类的代码,允许我select文本并将其复制到剪贴板。 “sdo”是我在WPF中使用的命名空间参考。

WPF使用扩展类:

 xmlns:sdo="clr-namespace:iFaceCaseMain" <sdo:TextBlockMoo x:Name="txtResults" Background="Black" Margin="5,5,5,5" Foreground="GreenYellow" FontSize="14" FontFamily="Courier New"></TextBlockMoo> 

扩展类后面的代码:

 public partial class TextBlockMoo : TextBlock { TextPointer StartSelectPosition; TextPointer EndSelectPosition; public String SelectedText = ""; public delegate void TextSelectedHandler(string SelectedText); public event TextSelectedHandler TextSelected; protected override void OnMouseDown(MouseButtonEventArgs e) { base.OnMouseDown(e); Point mouseDownPoint = e.GetPosition(this); StartSelectPosition = this.GetPositionFromPoint(mouseDownPoint, true); } protected override void OnMouseUp(MouseButtonEventArgs e) { base.OnMouseUp(e); Point mouseUpPoint = e.GetPosition(this); EndSelectPosition = this.GetPositionFromPoint(mouseUpPoint, true); TextRange otr = new TextRange(this.ContentStart, this.ContentEnd); otr.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.GreenYellow)); TextRange ntr = new TextRange(StartSelectPosition, EndSelectPosition); ntr.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.White)); SelectedText = ntr.Text; if (!(TextSelected == null)) { TextSelected(SelectedText); } } } 

示例窗口代码:

  public ucExample(IInstanceHost host, ref String WindowTitle, String ApplicationID, String Parameters) { InitializeComponent(); /*Used to add selected text to clipboard*/ this.txtResults.TextSelected += txtResults_TextSelected; } void txtResults_TextSelected(string SelectedText) { Clipboard.SetText(SelectedText); } 

将这种风格应用于您的TextBox,就是这样(从这篇文章启发):

 <Style x:Key="SelectableTextBlockLikeStyle" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}"> <Setter Property="IsReadOnly" Value="True"/> <Setter Property="IsTabStop" Value="False"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="Padding" Value="-2,0,0,0"/> <!-- The Padding -2,0,0,0 is required because the TextBox seems to have an inherent "Padding" of about 2 pixels. Without the Padding property, the text seems to be 2 pixels to the left compared to a TextBlock --> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsMouseOver" Value="False" /> <Condition Property="IsFocused" Value="False" /> </MultiTrigger.Conditions> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <TextBlock Text="{TemplateBinding Text}" FontSize="{TemplateBinding FontSize}" FontStyle="{TemplateBinding FontStyle}" FontFamily="{TemplateBinding FontFamily}" FontWeight="{TemplateBinding FontWeight}" TextWrapping="{TemplateBinding TextWrapping}" Foreground="{DynamicResource NormalText}" Padding="0,0,0,0" /> </ControlTemplate> </Setter.Value> </Setter> </MultiTrigger> </Style.Triggers> </Style> 

我不确定您是否可以selectTextBlock,但是另一个select是使用RichTextBox – 就像您build议的那样,它就像一个TextBox,但是支持您想要的格式。

根据Windows开发中心 :

TextBlock.IsTextSelectionEnabled属性

[已针对Windows 10上的UWP应用更新。有关Windows 8.x文章,请参阅存档 ]

获取或设置一个值,该值指示是否在TextBlock中启用文本select,通过用户操作或调用与select相关的API。

TextBlock没有模板。 所以为了达到这个目的,我们需要使用一个TextBox,其样式被改变为textBlock。

 <Style x:Key="TextBlockUsingTextBoxStyle" BasedOn="{x:Null}" TargetType="{x:Type TextBox}"> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Padding" Value="1"/> <Setter Property="AllowDrop" Value="true"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <TextBox BorderThickness="{TemplateBinding BorderThickness}" IsReadOnly="True" Text="{TemplateBinding Text}" Background="{x:Null}" BorderBrush="{x:Null}" /> </ControlTemplate> </Setter.Value> </Setter> </Style> 

所有的答案只是使用一个TextBox或试图手动实现文本select,这导致了糟糕的性能或非本地行为(在TextBox闪烁插入符号,手动实现中没有键盘支持等)

经过几个小时的挖掘并阅读WPF源代码 ,我反而find了一种为TextBlock控件(或其他任何控件)启用本机WPF文本select的方法。 文本select周围的大多数function都是在System.Windows.Documents.TextEditor系统类中实现的。

要为控件启用文本select,您需要做两件事:

  1. 调用TextEditor.RegisterCommandHandlers()一次来注册类事件处理程序

  2. 为每个类的实例创build一个TextEditor的实例,并将System.Windows.Documents.ITextContainer的底层实例传递给它

还有一个要求,您的控件的Focusable属性设置为True

就是这个! 听起来很容易,但不幸的是, TextEditor类被标记为内部。 所以我不得不围绕它写一个reflection包装:

 class TextEditorWrapper { private static readonly Type TextEditorType = Type.GetType("System.Windows.Documents.TextEditor, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); private static readonly PropertyInfo IsReadOnlyProp = TextEditorType.GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly PropertyInfo TextViewProp = TextEditorType.GetProperty("TextView", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly MethodInfo RegisterMethod = TextEditorType.GetMethod("RegisterCommandHandlers", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(Type), typeof(bool), typeof(bool), typeof(bool) }, null); private static readonly Type TextContainerType = Type.GetType("System.Windows.Documents.ITextContainer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); private static readonly PropertyInfo TextContainerTextViewProp = TextContainerType.GetProperty("TextView"); private static readonly PropertyInfo TextContainerProp = typeof(TextBlock).GetProperty("TextContainer", BindingFlags.Instance | BindingFlags.NonPublic); public static void RegisterCommandHandlers(Type controlType, bool acceptsRichContent, bool readOnly, bool registerEventListeners) { RegisterMethod.Invoke(null, new object[] { controlType, acceptsRichContent, readOnly, registerEventListeners }); } public static TextEditorWrapper CreateFor(TextBlock tb) { var textContainer = TextContainerProp.GetValue(tb); var editor = new TextEditorWrapper(textContainer, tb, false); IsReadOnlyProp.SetValue(editor._editor, true); TextViewProp.SetValue(editor._editor, TextContainerTextViewProp.GetValue(textContainer)); return editor; } private readonly object _editor; public TextEditorWrapper(object textContainer, FrameworkElement uiScope, bool isUndoEnabled) { _editor = Activator.CreateInstance(TextEditorType, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance, null, new[] { textContainer, uiScope, isUndoEnabled }, null); } } 

我还创build了一个从TextBlock派生的SelectableTextBlock ,它执行上述步骤:

 public class SelectableTextBlock : TextBlock { static SelectableTextBlock() { FocusableProperty.OverrideMetadata(typeof(SelectableTextBlock), new FrameworkPropertyMetadata(true)); TextEditorWrapper.RegisterCommandHandlers(typeof(SelectableTextBlock), true, true, true); // remove the focus rectangle around the control FocusVisualStyleProperty.OverrideMetadata(typeof(SelectableTextBlock), new FrameworkPropertyMetadata((object)null)); } private readonly TextEditorWrapper _editor; public SelectableTextBlock() { _editor = TextEditorWrapper.CreateFor(this); } } 

另一种select是为TextBlock创build一个附加属性,以便按需启用文本select。 在这种情况下,要再次禁用select,需要使用与此代码等效的reflection来分离TextEditor

 _editor.TextContainer.TextView = null; _editor.OnDetach(); _editor = null; 

还有一个可以适应RichTextBox的替代解决scheme,在这篇博客文章中 ,它使用了一个触发器,当控件使用hover在控件上的时候,用来replace控件模板,这样可以提高性能

 new TextBox { Text = text, TextAlignment = TextAlignment.Center, TextWrapping = TextWrapping.Wrap, IsReadOnly = true, Background = Brushes.Transparent, BorderThickness = new Thickness() { Top = 0, Bottom = 0, Left = 0, Right = 0 } };
new TextBox { Text = text, TextAlignment = TextAlignment.Center, TextWrapping = TextWrapping.Wrap, IsReadOnly = true, Background = Brushes.Transparent, BorderThickness = new Thickness() { Top = 0, Bottom = 0, Left = 0, Right = 0 } }; 

我在我的开源控件库中实现了SelectableTextBlock 。 你可以像这样使用它:

 <jc:SelectableTextBlock Text="Some text" /> 

虽然问题确实说“可选”,我相信有意的结果是将文本到剪贴板。 这可以通过添加一个上下文菜单和名为copy的菜单项,将Textblock文本属性值放入剪贴板来轻松而优雅地实现。 无论如何只是一个想法。

 Really nice and easy solution, exactly what I wanted ! 

我带来一些小的修改

 public class TextBlockMoo : TextBlock { public String SelectedText = ""; public delegate void TextSelectedHandler(string SelectedText); public event TextSelectedHandler OnTextSelected; protected void RaiseEvent() { if (OnTextSelected != null){OnTextSelected(SelectedText);} } TextPointer StartSelectPosition; TextPointer EndSelectPosition; Brush _saveForeGroundBrush; Brush _saveBackGroundBrush; TextRange _ntr = null; protected override void OnMouseDown(MouseButtonEventArgs e) { base.OnMouseDown(e); if (_ntr!=null) { _ntr.ApplyPropertyValue(TextElement.ForegroundProperty, _saveForeGroundBrush); _ntr.ApplyPropertyValue(TextElement.BackgroundProperty, _saveBackGroundBrush); } Point mouseDownPoint = e.GetPosition(this); StartSelectPosition = this.GetPositionFromPoint(mouseDownPoint, true); } protected override void OnMouseUp(MouseButtonEventArgs e) { base.OnMouseUp(e); Point mouseUpPoint = e.GetPosition(this); EndSelectPosition = this.GetPositionFromPoint(mouseUpPoint, true); _ntr = new TextRange(StartSelectPosition, EndSelectPosition); // keep saved _saveForeGroundBrush = (Brush)_ntr.GetPropertyValue(TextElement.ForegroundProperty); _saveBackGroundBrush = (Brush)_ntr.GetPropertyValue(TextElement.BackgroundProperty); // change style _ntr.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.Yellow)); _ntr.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.DarkBlue)); SelectedText = _ntr.Text; } }