هل هناك أي طريقة لجعل كتلة النص WPF قابلة للتحديد؟

StackOverflow https://stackoverflow.com/questions/136435

  •  02-07-2019
  •  | 
  •  

سؤال

أريد أن أجعل النص معروضًا في بارع, ، عميل تويتر مفتوح المصدر، قابل للتحديد.يتم عرضه حاليًا باستخدام كتلة نصية مخصصة.أحتاج إلى استخدام TextBlock لأنني أعمل مع سطور كتلة النص لعرض وتنسيق اسم المستخدم والروابط كارتباطات تشعبية.الطلب المتكرر هو أن تكون قادرًا على نسخ النص ولصقه.وللقيام بذلك، أحتاج إلى جعل TextBlock قابلاً للتحديد.

لقد حاولت تشغيله من خلال عرض النص باستخدام TextBox للقراءة فقط المصمم ليبدو وكأنه كتلة نصية ولكن هذا لن ينجح في حالتي لأن TextBox لا يحتوي على سطور مضمنة.بمعنى آخر، لا يمكنني تصميم النص أو تنسيقه داخل TextBox بشكل فردي كما أفعل مع TextBlock.

أيه أفكار؟

هل كانت مفيدة؟

المحلول

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

نصائح أخرى

جميع الإجابات هنا تستخدم فقط 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;

لم أتمكن من العثور على أي مثال للإجابة الحقيقية على السؤال.استخدمت جميع الإجابات 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);
    }

قم بتطبيق هذا النمط على 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>

قم بإنشاء ControlTemplate لـ TextBlock ووضع TextBox بداخله مع مجموعة خصائص للقراءة فقط.أو فقط استخدم TextBox واجعله للقراءة فقط، ثم يمكنك تغيير TextBox.Style لجعله يبدو مثل TextBlock.

لست متأكدًا مما إذا كان بإمكانك جعل TextBlock قابلاً للتحديد، ولكن هناك خيار آخر يتمثل في استخدام RichTextBox - فهو يشبه TextBox كما اقترحت، ولكنه يدعم التنسيق الذي تريده.

وفق مركز تطوير ويندوز:

خاصية TextBlock.IsTextSelectionEnabled

[تم التحديث لتطبيقات UWP على نظام التشغيل Windows 10.لمقالات Windows 8.x ، انظر أرشيف ]

يحصل أو تعيين قيمة تشير إلى ما إذا كان يتم تمكين اختيار النص في كتلة النص, ، إما من خلال إجراء المستخدم أو استدعاء واجهة برمجة التطبيقات المتعلقة بالتحديد.

لا يحتوي 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>

بينما يقول السؤال "قابل للتحديد"، أعتقد أن النتائج المقصودة هي نقل النص إلى الحافظة.يمكن تحقيق ذلك بسهولة وأناقة عن طريق إضافة قائمة سياق وعنصر قائمة يسمى نسخة يضع قيمة خاصية Textblock Text في الحافظة.مجرد فكرة على أي حال.

يوجد حل بديل قد يكون قابلاً للتكيف مع 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
         }
};

لقد نفذت SelectableTextBlock في مكتبة الضوابط مفتوحة المصدر الخاصة بي.يمكنك استخدامه مثل هذا:

<jc:SelectableTextBlock Text="Some text" />
public MainPage()
{
    this.InitializeComponent();
    ...
    ...
    ...
    //Make Start result text copiable
    TextBlockStatusStart.IsTextSelectionEnabled = true;
}

إضافة إلى إجابة @torvin وكما ذكر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;
            }
        }
    }
}
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;
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top