任何方式来使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,您需要做两件事:
-
调用
TextEditor.RegisterCommandHandlers()
一次来注册类事件处理程序 -
为每个类的实例创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; } }