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

时间:2008-09-25 21:56:51

标签: wpf xaml textbox textblock

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

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

有什么想法吗?

17 个答案:

答案 0 :(得分:202)

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

答案 1 :(得分:42)

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

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

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

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

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

  3. 还要求您的控件的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);
        }
    }
    

    我还创建了一个来自SelectableTextBlock的{​​{1}},它采取了上述步骤:

    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); } } 创建附加属性,以便按需启用文本选择。在这种情况下,要再次禁用选择,需要使用与此代码等效的反射来分离TextBlock

    TextEditor

答案 2 :(得分:27)

我一直无法找到真正回答这个问题的任何例子。所有答案都使用了Textbox或RichTextbox。我需要一个允许我使用TextBlock的解决方案,这就是我创建的解决方案。

我认为正确的方法是扩展TextBlock类。这是我用来扩展TextBlock类的代码,允许我选择文本并将其复制到剪贴板。 &#34; SDO&#34;是我在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);
    }

答案 3 :(得分:20)

将此样式应用于TextBox,就是这样(灵感来自this article):

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

答案 4 :(得分:19)

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

答案 5 :(得分:9)

根据Windows Dev Center

  

TextBlock.IsTextSelectionEnabled属性

     

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

     

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

答案 6 :(得分:9)

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

答案 7 :(得分:4)

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>

答案 8 :(得分:3)

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

答案 9 :(得分:2)

有一个替代解决方案可能适用于此blog post中的RichTextBox - 当用户将鼠标悬停在控件上时,它使用触发器来交换控件模板 - 应该有助于提高性能

答案 10 :(得分:1)


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

答案 11 :(得分:1)

&#39;在我的开源控件库中实现SelectableTextBlock。您可以像这样使用它:

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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

如果您已经TextTrimming="CharacterEllipsis"启用了鼠标悬停在省略号上时应用程序崩溃,则添加到@torvin的答案中,并且如注释中提到的@Dave Huang所述。

我尝试了线程中提到的其他有关使用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;
            }
        }
    }
}

答案 14 :(得分:0)

只需在 FlowDocument 中使用 FlowDocumentScrollViewer,将您的内联传递给元素。 您可以控制元素的样式,在我的例子中我添加了一个小边框。

<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

答案 15 :(得分:0)

我创建了一个类 TextBlockEx,它派生自 TextBox 并设置为只读,并在构造函数中进行文本换行,使其看起来像一个带有可选文本的 TextBlock。

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

答案 16 :(得分:-1)

set debug=myapp:* & start npm

我带了一些小修改

Really nice and easy solution, exactly what I wanted !