首页 文章

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

提问于
浏览
191

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

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

有任何想法吗?

14 回答

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

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

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

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

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

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

    's also a requirement that your control' s 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 派生自 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

    _editor.TextContainer.TextView = null;
    _editor.OnDetach();
    _editor = null;
    
  • 0

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

    我相信正确的方法是扩展TextBlock类 . 这是我用来扩展TextBlock类的代码,允许我选择文本并将其复制到剪贴板 . “sdo”是我在WPF中使用的命名空间引用 .

    WPF Using Extended Class:

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

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

  • 30

    将此样式应用于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>
    
  • 1

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

  • 2

    根据Windows Dev Center

    TextBlock.IsTextSelectionEnabled属性[已针对Windows 10上的UWP应用程序进行了更新 . 对于Windows 8.x文章,请参阅存档]获取或设置一个值,该值指示是否在TextBlock中启用文本选择,通过用户操作或调用选择相关API .

  • 9

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

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

  • -1

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

  • 9
    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
             }
    };
    
  • 20

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

    <jc:SelectableTextBlock Text="Some text" />
    
  • 4
    public MainPage()
    {
        this.InitializeComponent();
        ...
        ...
        ...
        //Make Start result text copiable
        TextBlockStatusStart.IsTextSelectionEnabled = true;
    }
    
  • 26
    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;
        }
    }
    

相关问题