有什么方法使WPF文本块可选?

如何允许TextBlock的文本是可选择的?

我试图让它通过显示文本使用只读文本框样式看起来像一个文本块,但这不会在我的情况下工作,因为一个文本框没有内联。换句话说,如何使它具有可选性?

135116 次浏览

我不确定你是否可以使一个TextBlock可选,但另一个选择是使用RichTextBox -它就像你建议的TextBox,但支持你想要的格式。

创建控件模板的TextBlock和把一个文本框内readonly属性设置。 或者只是使用文本框,使其只读,然后你可以改变文本框。样式使它看起来像TextBlock.

使用带有这些设置的TextBox来代替,使其只读,看起来像TextBlock控件。

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

有一个替代的解决方案,可能适用于这个博客中的RichTextBox -它使用触发器来交换控件模板时,使用悬停在控件上-应该有助于性能


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
}
};

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>

应用此样式到你的文本框,这就是它(灵感来自这篇文章):

<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>

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

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

我找不到任何能真正回答这个问题的例子。所有的答案都使用了一个文本框或RichTextbox。我需要一个解决方案,让我使用一个TextBlock,这是我创建的解决方案。

我相信这样做的正确方法是扩展TextBlock类。这是我用来扩展TextBlock类的代码,以允许我选择文本并将其复制到剪贴板。“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);
}

根据Windows开发中心:

TextBlock。IsTextSelectionEnabled财产

[针对Windows 10上的UWP应用程序更新。]Windows 8。X篇文章,见 the 存档]

获取或设置一个值,该值指示是否启用文本选择 在TextBlock中,通过用户操作或调用 selection-related API。< / p >

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;
}
}

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

这里的所有答案都只是使用TextBox或试图手动实现文本选择,这导致性能较差或非本机行为(在TextBox中闪烁插入符号,手动实现中不支持键盘等)。

经过数小时的挖掘和阅读WPF源代码,我反而发现了一种为TextBlock控件(或其他任何控件)启用本机WPF文本选择的方法。大多数关于文本选择的功能是在System.Windows.Documents.TextEditor系统类中实现的。

要启用控件的文本选择,您需要做两件事:

  1. 调用TextEditor.RegisterCommandHandlers()注册类 李事件处理程序< / p > < / >

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

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

就是它了!听起来很简单,但不幸的是TextEditor类被标记为内部类。所以我必须在它周围写一个反射包装:

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);
}
}

我还创建了一个从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);
}
}

另一种选择是为TextBlock创建一个附加属性,以便根据需要选择文本。在这种情况下,要再次禁用选择,需要使用相当于下面代码的反射来分离TextEditor:

_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;
public MainPage()
{
this.InitializeComponent();
...
...
...
//Make Start result text copiable
TextBlockStatusStart.IsTextSelectionEnabled = true;
}

加上@torvin的回答,正如@Dave Huang在评论中提到的,如果你启用了TextTrimming="CharacterEllipsis",当你把鼠标悬停在省略号上时,应用程序会崩溃。

我尝试了关于使用TextBox的线程中提到的其他选项,但它似乎真的不是解决方案,因为它不显示“省省号”,也如果文本太长,以适合容器选择文本框的内容“滚动”内部这不是一个TextBlock行为。

我认为最好的解决方案是@torvin的答案,但当悬停在省略号上时,会出现令人讨厌的崩溃。

我知道这并不漂亮,但是在内部订阅/取消订阅未处理的异常和处理异常是我发现解决这个问题的唯一方法,如果有人有更好的解决方案,请分享:)

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);


this.Loaded += (sender, args) => {
this.Dispatcher.UnhandledException -= Dispatcher_UnhandledException;
this.Dispatcher.UnhandledException += Dispatcher_UnhandledException;
};
this.Unloaded += (sender, args) => {
this.Dispatcher.UnhandledException -= Dispatcher_UnhandledException;
};
}


private void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
if (!string.IsNullOrEmpty(e?.Exception?.StackTrace))
{
if (e.Exception.StackTrace.Contains("System.Windows.Controls.TextBlock.GetTextPositionFromDistance"))
{
e.Handled = true;
}
}
}
}
只要在FlowDocumentScrollViewer中使用FlowDocument,将你的内联传递给元素。 你可以控制元素的风格,在我的例子中,我添加了一个小边框
<FlowDocumentScrollViewer Grid.Row="2" Margin="5,3" BorderThickness="1"
BorderBrush="{DynamicResource Element.Border}"
VerticalScrollBarVisibility="Auto">
<FlowDocument>
<Paragraph>
<Bold>Some bold text in the paragraph.</Bold>
Some text that is not bold.
</Paragraph>


<List>
<ListItem>
<Paragraph>ListItem 1</Paragraph>
</ListItem>
<ListItem>
<Paragraph>ListItem 2</Paragraph>
</ListItem>
<ListItem>
<Paragraph>ListItem 3</Paragraph>
</ListItem>
</List>
</FlowDocument>
</FlowDocumentScrollViewer>

enter image description here

以下是对我有效的方法。我创建了一个类TextBlockEx,从TextBox派生,并设置为只读,文本包装在构造函数。

public class TextBlockEx : TextBox
{
public TextBlockEx()
{
base.BorderThickness = new Thickness(0);
IsReadOnly = true;
TextWrapping = TextWrapping.Wrap;
//Background = Brushes.Transparent; // Uncomment to get parent's background color
}
}
我同意这里的大多数答案不创建一个可选的Text。@Billy Willoughby的工作得很好,但是它没有一个明显的选择线索。我想扩展他的扩展,可以突出显示文本,因为它被选中。它还包含双击和三次点击选择。您可以添加一个上下文菜单与quot;Copy"如果需要的话。 它使用Background属性来“highlight”;所以它是有限的,因为它将覆盖Run.Background

https://github.com/mwagnerEE/WagnerControls

增加选择&SelectionChanged事件为torvin的代码

public class SelectableTextBlock : TextBlock
{


static readonly Type TextEditorType
= Type.GetType("System.Windows.Documents.TextEditor, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");


static readonly PropertyInfo IsReadOnlyProp
= TextEditorType.GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);


static readonly PropertyInfo TextViewProp
= TextEditorType.GetProperty("TextView", BindingFlags.Instance | BindingFlags.NonPublic);


static readonly MethodInfo RegisterMethod
= TextEditorType.GetMethod("RegisterCommandHandlers",
BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(Type), typeof(bool), typeof(bool), typeof(bool) }, null);


static readonly Type TextContainerType
= Type.GetType("System.Windows.Documents.ITextContainer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
static readonly PropertyInfo TextContainerTextViewProp
= TextContainerType.GetProperty("TextView");


static readonly PropertyInfo TextContainerTextSelectionProp
= TextContainerType.GetProperty("TextSelection");


static readonly PropertyInfo TextContainerProp = typeof(TextBlock).GetProperty("TextContainer", BindingFlags.Instance | BindingFlags.NonPublic);


static void RegisterCommandHandlers(Type controlType, bool acceptsRichContent, bool readOnly, bool registerEventListeners)
{
RegisterMethod.Invoke(null, new object[] { controlType, acceptsRichContent, readOnly, registerEventListeners });
}


static SelectableTextBlock()
{
FocusableProperty.OverrideMetadata(typeof(SelectableTextBlock), new FrameworkPropertyMetadata(true));
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;
object? textContainer;
object? editor;
public TextSelection TextSelection { get; private set; }


public SelectableTextBlock()
{
textContainer = TextContainerProp.GetValue(this);


editor = Activator.CreateInstance(TextEditorType, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance,
null, new[] { textContainer, this, false }, null);




IsReadOnlyProp.SetValue(editor, true);
TextViewProp.SetValue(editor, TextContainerTextViewProp.GetValue(textContainer));


TextSelection = (TextSelection)TextContainerTextSelectionProp.GetValue(textContainer);
TextSelection.Changed += (s, e) => OnSelectionChanged?.Invoke(this, e);
}


public event EventHandler OnSelectionChanged;
}