[wpf] WPF 텍스트 블록을 선택 가능하게 만드는 방법은 무엇입니까?

오픈 소스 Twitter 클라이언트 인 Witty에 텍스트를 표시하도록 선택하고 싶습니다 . 현재 사용자 정의 텍스트 블록을 사용하여 표시됩니다. 텍스트 블록의 인라인을 사용하여 @username 및 링크를 하이퍼 링크로 표시하고 형식을 지정하기 때문에 TextBlock을 사용해야합니다. 자주 요청하면 텍스트를 복사하여 붙여 넣을 수 있습니다. 그렇게하려면 TextBlock을 선택 가능하게 만들어야합니다.

텍스트 블록처럼 보이도록 읽기 전용 TextBox를 사용하여 텍스트를 표시하여 작동 시키려고했지만 TextBox에 인라인이 없기 때문에 제 경우에는 작동하지 않습니다. 즉, TextBlock으로 할 수있는 것처럼 TextBox 내의 텍스트를 개별적으로 스타일을 지정하거나 서식을 지정할 수 없습니다.

어떤 아이디어?



답변

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


답변

여기에있는 모든 대답은 a를 사용 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);
    }
}

또한 위에서 언급 한 단계를 수행 하는 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이 코드와 동일한 리플렉션을 사용하여 a를 분리해야 합니다.

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


답변

나는 그 질문에 실제로 대답하는 예를 찾지 못했습니다. 모든 답변은 텍스트 상자 또는 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);
    }


답변

이 스타일을 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>


답변

TextBlock에 대한 ControlTemplate을 만들고 읽기 전용 속성이 설정된 TextBox를 넣습니다. 또는 TextBox를 사용하여 읽기 전용으로 만든 다음 TextBox.Style을 TextBlock처럼 보이도록 변경할 수 있습니다.


답변

TextBlock을 선택할 수 있는지 확실하지 않지만 다른 옵션은 RichTextBox를 사용하는 것입니다. 제안한 TextBox와 비슷하지만 원하는 형식을 지원합니다.


답변

에 따르면 윈도우 개발자 센터 :

TextBlock.IsTextSelectionEnabled 속성

[Windows 10의 UWP 앱용으로 업데이트되었습니다. Windows 8.x 문서의 경우 아카이브를 참조하십시오. ]

사용자 작업 또는 선택 관련 API 호출을 통해 TextBlock 에서 텍스트 선택이 활성화되어 있는지 여부를 나타내는 값을 가져 오거나 설정합니다 .