source

WPF 텍스트 블록을 선택할 수 있는 방법이 있습니까?

lovecheck 2023. 5. 7. 11:34
반응형

WPF 텍스트 블록을 선택할 수 있는 방법이 있습니까?

을 허용하는 TextBlock의 텍스트를 선택할 수 있습니까?

텍스트 블록처럼 스타일이 지정된 읽기 전용 TextBox를 사용하여 텍스트를 표시하여 작동하려고 했지만 TextBox에는 인라인이 없기 때문에 제 경우에는 작동하지 않습니다.다시 말해, 어떻게 하면 선택할 수 있게 만들 수 있을까요?

을 합니다.TextBox대신 읽기 전용으로 만들고 보기 위해 이러한 설정을 사용합니다.TextBlock통제.

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

여기에 있는 모든 대답은 단지 사용하는 것입니다.TextBox수동으로 이 저하되거나 동작 in ▁in캐이▁or▁selectionblinking▁caret▁behaviour▁(▁manually▁text필▁trying발)이 발생합니다.TextBox수동 구현 등에서 키보드 지원 없음)

시간 동안 WPF 소스 코드를 탐색하고 읽은 후에 대신에 네이티브 WPF 텍스트 선택을 활성화하는 방법을 발견했습니다.TextBlock컨트롤(또는 실제로는 다른 컨트롤).텍스트 선택과 관련된 대부분의 기능은 다음과 같이 구현됩니다.System.Windows.Documents.TextEditor시스템 클래스.

컨트롤에 대해 텍스트 선택을 활성화하려면 다음 두 가지 작업을 수행해야 합니다.

  1. TextEditor.RegisterCommandHandlers() event handler는 클래스 이벤트 핸들러입니다.

  2. 의 .TextEditor각인 " 클래스각에스대해기본통다인를과니스합스턴인의스턴▁of▁the▁for다니▁your통과▁pass합▁of를lying▁under▁instance▁each"를 통과합니다.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요청 시 텍스트 선택을 활성화합니다.▁a합▁in다를 해야 합니다.TextEditor하여: " 이드코가 의 등 사 용 하 여 를 반 사 하 : 여

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

저는 그 질문에 정말로 대답할 수 있는 어떤 예도 찾을 수 없었습니다.모든 답변은 텍스트 상자 또는 리치 텍스트 상자를 사용했습니다.저는 텍스트 블록을 사용할 수 있는 솔루션이 필요했고, 이것이 제가 만든 솔루션입니다.

이를 위한 올바른 방법은 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);
    }

TextBlock에 대한 ControlTemplate를 만들고 읽기 전용 속성이 설정된 TextBox를 내부에 배치합니다.또는 TextBox를 사용하여 읽기 전용으로 만든 다음 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을 선택할 수 있는지는 모르겠지만 다른 옵션으로 RichTextBox를 사용할 수도 있습니다. RichTextBox는 제안한 대로 TextBox와 비슷하지만 원하는 형식을 지원합니다.

Windows Dev Center 기준:

텍스트 차단.IsTextSelectionEnabled 속성

[ Windows 10에서 UWP 앱용으로 업데이트됨.Windows 8.x 문서는 아카이브 참조]

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

질문에 '선택 가능'이라고 표시되지만 의도적인 결과는 텍스트를 클립보드로 가져오는 것이라고 생각합니다.텍스트 블록 텍스트 속성 값을 클립보드에 저장하는 copy라는 컨텍스트 메뉴 및 메뉴 항목을 추가하여 쉽고 우아하게 이 작업을 수행할 수 있습니다.어쨌든 그냥 생각일 뿐입니다.

TextBlock에 템플릿이 없습니다.따라서 이를 달성하려면 스타일이 textBlock으로 변경된 TextBox를 사용해야 합니다.

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

블로그 게시물에 있는 RichTextBox에 적용할 수 있는 대체 솔루션이 있습니다. 이 솔루션은 사용자가 컨트롤을 마우스로 가리키면 컨트롤 템플릿을 교체하는 트리거를 사용했습니다. 이 솔루션은 성능에 도움이 될 것입니다.


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

여기 제게 효과가 있었던 것이 있습니다.TextBox에서 파생되어 읽기 전용으로 설정된 TextBlockEx 클래스를 생성자에서 만들었습니다.

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
    }
}
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;
    }
}
public MainPage()
{
    this.InitializeComponent();
    ...
    ...
    ...
    //Make Start result text copiable
    TextBlockStatusStart.IsTextSelectionEnabled = true;
}

@처럼 @torvin @Dave Huang @Dave Huang이 있으면 합니다.TextTrimming="CharacterEllipsis"줄임표 위에 마우스를 올릴 때 응용프로그램 충돌을 활성화했습니다.

스레드에서 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;
            }
        }
    }
}

사용하기만 하면 됩니다.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>

여기에 이미지 설명 입력

여기에 있는 대부분의 답변은 선택 가능한 텍스트 블록을 만들지 않는다는 것에 동의합니다.@빌리 윌러비는 잘 작동했지만 선택에 대한 가시적인 단서가 없었습니다.텍스트를 선택할 때 강조 표시할 수 있는 확장자를 확장하고 싶습니다.또한 더블클릭과 트리플클릭 선택이 통합되어 있습니다.필요한 경우 "복사"가 있는 상황별 메뉴를 추가할 수 있습니다.를 사용합니다.Background선택 항목을 "수정"하여 덮어쓸 수 있도록 제한하는 속성Run.Background

https://github.com/mwagnerEE/WagnerControls

Torvin 코드에 Selection & Selection Changed Event 추가

public class SelectableTextBlock : TextBlock
{

    static readonly Type TextEditorType
    = Type.GetType("System.Windows.Documents.TextEditor, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");

    static readonly PropertyInfo IsReadOnlyProp
        = TextEditorType.GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);

    static readonly PropertyInfo TextViewProp
        = TextEditorType.GetProperty("TextView", BindingFlags.Instance | BindingFlags.NonPublic);

    static readonly MethodInfo RegisterMethod
        = TextEditorType.GetMethod("RegisterCommandHandlers",
        BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(Type), typeof(bool), typeof(bool), typeof(bool) }, null);

    static readonly Type TextContainerType
        = Type.GetType("System.Windows.Documents.ITextContainer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
    static readonly PropertyInfo TextContainerTextViewProp
        = TextContainerType.GetProperty("TextView");

    static readonly PropertyInfo TextContainerTextSelectionProp
        = TextContainerType.GetProperty("TextSelection");

    static readonly PropertyInfo TextContainerProp = typeof(TextBlock).GetProperty("TextContainer", BindingFlags.Instance | BindingFlags.NonPublic);

    static void RegisterCommandHandlers(Type controlType, bool acceptsRichContent, bool readOnly, bool registerEventListeners)
    {
        RegisterMethod.Invoke(null, new object[] { controlType, acceptsRichContent, readOnly, registerEventListeners });
    }

    static SelectableTextBlock()
    {
        FocusableProperty.OverrideMetadata(typeof(SelectableTextBlock), new FrameworkPropertyMetadata(true));
        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;
    object? textContainer;
    object? editor;
    public TextSelection TextSelection { get; private set; }

    public SelectableTextBlock()
    {
        textContainer = TextContainerProp.GetValue(this);

        editor = Activator.CreateInstance(TextEditorType, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance,
            null, new[] { textContainer, this, false }, null);


        IsReadOnlyProp.SetValue(editor, true);
        TextViewProp.SetValue(editor, TextContainerTextViewProp.GetValue(textContainer));

        TextSelection = (TextSelection)TextContainerTextSelectionProp.GetValue(textContainer);
        TextSelection.Changed += (s, e) => OnSelectionChanged?.Invoke(this, e);
    }

    public event EventHandler OnSelectionChanged;
}

Selectable을 구현했습니다.내 오픈 소스 제어 라이브러리의 TextBlock.다음과 같이 사용할 수 있습니다.

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

언급URL : https://stackoverflow.com/questions/136435/any-way-to-make-a-wpf-textblock-selectable

반응형