有什么方法可以使 WPF 文本块可选?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/136435/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Any way to make a WPF textblock selectable?
提问by Alan Le
I want to make the text displayed in the Witty, an open source Twitter client, selectable. It is currently displayed using a custom textblock. I need to use a TextBlock because I'm working with the textblock's inlines to display and format the @username and links as hyperlinks. A frequent request is to be able to copy-paste the text. In order to do that I need to make the TextBlock selectable.
我想让Witty 中显示的文本可以选择,这是一个开源 Twitter 客户端。它当前使用自定义文本块显示。我需要使用 TextBlock,因为我正在使用文本块的内联来显示和格式化 @username 和链接作为超链接。一个常见的要求是能够复制粘贴文本。为了做到这一点,我需要使 TextBlock 可选。
I tried to get it to work by displaying the text using a read-only TextBox styled to look like a textblock but this will not work in my case because a TextBox does not have inlines. In other words, I can't style or format the text within a TextBox individually like I can with a TextBlock.
我试图通过使用样式看起来像文本块的只读文本框显示文本来使其工作,但这在我的情况下不起作用,因为文本框没有内联。换句话说,我无法像使用 TextBlock 那样单独设置 TextBox 中文本的样式或格式。
Any ideas?
有任何想法吗?
采纳答案by MSB
<TextBox Background="Transparent"
BorderThickness="0"
Text="{Binding Text, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap" />
回答by torvin
All the answers here are just using a TextBox
or trying to implement text selection manually, which leads to poor performance or non-native behaviour (blinking caret in TextBox
, no keyboard support in manual implementations etc.)
这里的所有答案都只是使用 a TextBox
or 尝试手动实现文本选择,这会导致性能不佳或非本机行为(在 中闪烁插入符号TextBox
,手动实现中没有键盘支持等)
After hours of digging around and reading the WPF source code, I instead discovered a way of enabling the native WPF text selection for TextBlock
controls (or really any other controls). Most of the functionality around text selection is implemented in System.Windows.Documents.TextEditor
system class.
经过数小时的挖掘和阅读WPF 源代码,我发现了一种为TextBlock
控件(或任何其他控件)启用本机 WPF 文本选择的方法。大多数围绕文本选择的功能都是在System.Windows.Documents.TextEditor
系统类中实现的。
To enable text selection for your control you need to do two things:
要为控件启用文本选择,您需要做两件事:
Call
TextEditor.RegisterCommandHandlers()
once to register class event handlersCreate an instance of
TextEditor
for each instance of your class and pass the underlying instance of yourSystem.Windows.Documents.ITextContainer
to it
调用
TextEditor.RegisterCommandHandlers()
一次以注册类事件处理程序TextEditor
为您的类的每个实例创建一个实例,并将您的底层实例传递System.Windows.Documents.ITextContainer
给它
There's also a requirement that your control's Focusable
property is set to True
.
还要求将控件的Focusable
属性设置为True
。
This is it! Sounds easy, but unfortunately TextEditor
class is marked as internal. So I had to write a reflection wrapper around it:
就是这个!听起来很简单,但不幸的TextEditor
是 class 被标记为 internal。所以我不得不围绕它写一个反射包装器:
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);
}
}
I also created a SelectableTextBlock
derived from TextBlock
that takes the steps noted above:
我还创建了一个SelectableTextBlock
派生自TextBlock
,采取上述步骤:
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);
}
}
Another option would be to create an attached property for TextBlock
to enable text selection on demand. In this case, to disable the selection again, one needs to detach a TextEditor
by using the reflection equivalent of this code:
另一种选择是创建一个附加属性,TextBlock
以便按需启用文本选择。在这种情况下,要再次禁用选择,需要TextEditor
使用此代码的反射等效项来分离 a :
_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;
回答by Billy Willoughby
I have been unable to find any example of really answering the question. All the answers used a Textbox or RichTextbox. I needed a solution that allowed me to use a TextBlock, and this is the solution I created.
我一直无法找到任何真正回答问题的例子。所有答案都使用 Textbox 或 RichTextbox。我需要一个允许我使用 TextBlock 的解决方案,这就是我创建的解决方案。
I believe the correct way to do this is to extend the TextBlock class. This is the code I used to extend the TextBlock class to allow me to select the text and copy it to clipboard. "sdo" is the namespace reference I used in the WPF.
我相信正确的方法是扩展 TextBlock 类。这是我用来扩展 TextBlock 类以允许我选择文本并将其复制到剪贴板的代码。“sdo”是我在 WPF 中使用的命名空间引用。
WPF Using Extended Class:
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>
Code Behind for Extended Class:
扩展类的代码隐藏:
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);
}
}
}
Example Window Code:
示例窗口代码:
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);
}
回答by juanjo.arana
Apply this style to your TextBox and that's it (inspired from this article):
将此样式应用于您的 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>
回答by Jobi Joy
Create ControlTemplate for the TextBlock and put a TextBox inside with readonly property set. Or just use TextBox and make it readonly, then you can change the TextBox.Style to make it looks like TextBlock.
为 TextBlock 创建 ControlTemplate 并在其中放置一个具有只读属性设置的 TextBox。或者只是使用 TextBox 并将其设为只读,然后您可以更改 TextBox.Style 使其看起来像 TextBlock。
回答by Bruce
I'm not sure if you can make a TextBlock selectable, but another option would be to use a RichTextBox - it is like a TextBox as you suggested, but supports the formatting you want.
我不确定您是否可以选择 TextBlock,但另一种选择是使用 RichTextBox - 它就像您建议的 TextBox,但支持您想要的格式。
回答by Hyman Pines
According to Windows Dev Center:
根据Windows 开发中心:
TextBlock.IsTextSelectionEnabled property
[ Updated for UWP apps on Windows 10. For Windows 8.x articles, see the archive]
Gets or sets a value that indicates whether text selection is enabled in the TextBlock, either through user action or calling selection-related API.
TextBlock.IsTextSelectionEnabled 属性
[针对 Windows 10 上的 UWP 应用更新。有关 Windows 8.x 文章,请参阅存档]
获取或设置一个值,该值指示是否通过用户操作或调用与选择相关的 API在TextBlock 中启用文本选择。
回答by SimperT
While the question does say 'Selectable' I believe the intentional results is to get the text to the clipboard. This can easily and elegantly be achieved by adding a Context Menu and menu item called copy that puts the Textblock Text property value in clipboard. Just an idea anyway.
虽然问题确实说“可选择”,但我相信有意的结果是将文本放入剪贴板。这可以通过添加上下文菜单和名为 copy 的菜单项轻松优雅地实现,该菜单项将 Textblock Text 属性值放入剪贴板。反正只是一个想法。
回答by Saraf Talukder
TextBlock does not have a template. So inorder to achieve this, we need to use a TextBox whose style is changed to behave as a textBlock.
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>