Любой способ сделать текстовый блок WPF выбираемым? - PullRequest
204 голосов
/ 26 сентября 2008

Я хочу, чтобы текст отображался в Witty , клиенте Twitter с открытым исходным кодом, по выбору. В настоящее время он отображается с использованием пользовательского текстового блока. Мне нужно использовать TextBlock, потому что я работаю со встроенными текстовыми блоками для отображения и форматирования @username и ссылок как гиперссылок. Частым запросом является возможность копирования и вставки текста. Для этого мне нужно сделать TextBlock доступным для выбора.

Я попытался заставить его работать, отображая текст, используя текстовый блок только для чтения, стилизованный под текстовый блок, но в моем случае это не будет работать, потому что текстовый блок не имеет встроенных строк. Другими словами, я не могу стилизовать или отформатировать текст в TextBox по отдельности, как в TextBlock.

Есть идеи?

Ответы [ 15 ]

202 голосов
/ 24 марта 2010
<TextBox Background="Transparent"
         BorderThickness="0"
         Text="{Binding Text, Mode=OneWay}"
         IsReadOnly="True"
         TextWrapping="Wrap" />
42 голосов
/ 11 августа 2017

Все ответы здесь - это просто использование TextBox или попытка осуществить выбор текста вручную, что приводит к низкой производительности или нестандартному поведению (мигание каретки в TextBox, отсутствие поддержки клавиатуры в реализациях вручную и т. Д.)

После нескольких часов копания и чтения исходного кода WPF я вместо этого обнаружил способ включения собственного выбора текста WPF для TextBlock элементов управления (или действительно любых других элементов управления). Большая часть функций выделения текста реализована в системном классе 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);
    }
}

Я также создал 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;
27 голосов
/ 30 сентября 2015

Я не смог найти ни одного примера, чтобы действительно ответить на вопрос. Все ответы использовали 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>

Код для расширенного класса:

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);
    }
20 голосов
/ 10 февраля 2012

Примените этот стиль к вашему 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>
19 голосов
/ 26 сентября 2008

Создайте ControlTemplate для TextBlock и поместите TextBox внутрь с установленным свойством только для чтения. Или просто используйте TextBox и сделайте его доступным только для чтения, затем вы можете изменить TextBox.Style, чтобы он выглядел как TextBlock.

9 голосов
/ 29 января 2016

По данным Центр разработки Windows :

Свойство TextBlock.IsTextSelectionEnabled

[Обновлено для приложений UWP в Windows 10. Статьи о Windows 8.x см. архив ]

Получает или задает значение, которое указывает, включен ли выбор текста в TextBlock , либо через действие пользователя, либо с помощью вызова API, связанный с выбором.

9 голосов
/ 26 сентября 2008

Я не уверен, что вы можете сделать выбор TextBlock, но другой вариант будет использовать RichTextBox - он похож на TextBox, как вы предложили, но поддерживает желаемое форматирование.

4 голосов
/ 13 сентября 2011

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>
3 голосов
/ 13 мая 2017

В то время как вопрос говорит «Выбираемый», я полагаю, что преднамеренные результаты - получить текст в буфер обмена. Это может быть легко и элегантно достигнуто путем добавления контекстного меню и пункта меню под названием copy, который помещает значение свойства Textblock Text в буфер обмена. В любом случае, просто идея.

2 голосов
/ 28 июня 2010

Существует альтернативное решение, которое может быть адаптировано к RichTextBox, описанному в этом сообщении в блоге - оно использовало триггер для замены шаблона элемента управления, когда использование зависает над элементом управления - должно способствовать повышению производительности 1003 *

...