有没有什么方法可以选择WPF文本块?

Ala*_* Le 214 wpf xaml textbox textblock

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

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

有任何想法吗?

小智 211

<TextBox Background="Transparent"
         BorderThickness="0"
         Text="{Binding Text, Mode=OneWay}"
         IsReadOnly="True"
         TextWrapping="Wrap" />
Run Code Online (Sandbox Code Playgroud)

  • -1该问题专门询问如何使文本块可选.因为他不想丢失"Inlines"属性(textBoxes没有).这个"答案"只是建议让文本框看起来像文本块. (103认同)
  • @AlanLe为什么你接受这个答案时,你明确表示你不想要的?为什么147个无能为力的人投票呢? (13认同)
  • 我有一个包含许多TextBlocks/Labels的项目,我无法真正将它们变成TextBoxes.我想要做的是,为应用级资源添加一个魔术应用于所有样式,这样它应该影响所有的Label/TextBlock,并使其内部文本呈现器作为一个只读的TextBox,你知道吗?去做吧? (6认同)
  • 您可能需要根据您的具体情况添加IsTabStop ="False" (4认同)
  • +1 非常好的解决方案!我添加了一个 Padding="0",因为在我的项目中文本的底部被剪掉了......也许是因为其他地方的风格。 (2认同)
  • 我在下面添加了一个解决方案,允许文本块按照原始解决方案中的要求工作,我同意,为什么对一个完全符合他所说的他不想做的事情的解决方案有如此多的赞成票? (2认同)
  • 同意@00jt - 问题是关于 TextBlock 而不是 TextBox。在我看来,不应该被选为答案。 (2认同)
  • 将这种类型的 TextBox 放在 TextBlock 旁边,我注意到 TextBox 有一点“边距”,即使 Margin 属性为 0。您知道为什么或如何消除它吗? (2认同)
  • @KyleDelaney 这可能是“填充”。有点晚了,但也许对某人有帮助 (2认同)

tor*_*vin 56

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

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

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

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

  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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我还创建了一个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);
    }
}
Run Code Online (Sandbox Code Playgroud)

另一种选择是创建附加属性以便TextBlock按需启用文本选择.在这种情况下,要再次禁用选择,需要TextEditor使用与此代码等效的反射来分离a :

_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;
Run Code Online (Sandbox Code Playgroud)

  • 最后一个使用TextBlock的真正答案!我只需要等待近10年.这需要被投票. (7认同)
  • 与使用任何其他自定义控件的方式相同。例如,参见 /sf/answers/263772491/ (2认同)
  • 是的,它适用于多个“运行”元素,甚至在复制时保留格式 (2认同)
  • @BillyWilloughby您的解决方案只是模拟选择。它缺少许多本机选择功能:键盘支持,上下文菜单等。我的解决方案启用了本机选择功能 (2认同)
  • 只要“文本块”已嵌入“超级链接”,只要“超级链接”不是其中的最后一个内联,似乎此解决方案就可以工作。在内容中添加尾随的空白“运行”可以解决导致“ ExecutionEngineException”抛出的潜在问题。 (2认同)
  • 这很棒!除非你在`TextBlock` 上有`TextTrimming="CharacterEllipsis"` 并且可用宽度不足,如果你将鼠标指针移到...上,它会崩溃并出现 System.ArgumentException “请求的距离超出相关文档的内容.” 在 System.Windows.Documents.TextPointer.InitializeOffset(TextPointer position, Int32 distance, LogicalDirection direction) :( 不知道除了将 TextTrimming 设置为 None 之外是否还有其他解决方法。 (2认同)

Bil*_*hby 28

我一直无法找到真正回答这个问题的任何例子.所有答案都使用了Textbox或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>
Run Code Online (Sandbox Code Playgroud)

扩展类的代码:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

示例窗口代码:

    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);
    }
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案!没有反射黑客,不使用文本框......并且它可以轻松地重构为可重用的行为。非常好,谢谢! (4认同)

jua*_*ana 19

将此样式应用于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>
Run Code Online (Sandbox Code Playgroud)

  • 似乎没有人能够阅读.OP需要一个TextBlock,而不是像TextBlock一样的TextBox. (11认同)
  • 顺便说一句,截至今天,文章链接似乎已死 (2认同)
  • 另外一个补充:填充应该是-2,0,-2,0.在TextBox内部,创建一个TextBoxView控件,其默认边距为2,0,2,0.不幸的是,你无法重新定义它的风格,因为它标记为内部. (2认同)

Job*_*Joy 18

为TextBlock创建ControlTemplate并将TextBox放在readonly属性集中.或者只使用TextBox并使其只读,然后您可以更改TextBox.Style以使其看起来像TextBlock.

  • 如果TextBlock中包含内联元素,则此方法将不起作用.如果您有超链接或粗体或斜体文本,该怎么办?TextBox不支持这些. (17认同)
  • 如何为TextBlock设置ControlTemplate?我找不到房产? (11认同)
  • -1 TextBlock没有ControlTemplate,因为它是FrameworkElement的直接子类.另一方面,TextBox是Control的子类. (7认同)
  • 为什么不能读人?OP明确表示需要TextBlock,而不是TextBox,因为TextBlock支持内联格式而TextBox不支持.为什么像这样完全错误的垃圾答案得到了无数的赞成? (5认同)
  • 如果您使用内联运行,则不起作用,就像 HaxElit 所问的那样,我不确定您所说的控制模板是什么意思。 (2认同)

Bru*_*uce 9

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


Jac*_*nes 9

根据Windows开发中心:

TextBlock.IsTextSelectionEnabled属性

[针对Windows 10上的UWP应用程序进行了更新.对于Windows 8.x文章,请参阅存档 ]

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

  • Amswer似乎不正确.IsTextSelectionEnabled仅适用于UWP,而不是WPF - 原始问题确实指定了WPF. (20认同)
  • 不幸的是,与Win7不兼容(有时这是必须的) (5认同)

Sim*_*erT 6

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