xaml - أي طريقة لجعل textBlock WPF للتحديد؟




textbox (12)

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

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

أيه أفكار؟


Answers

هناك حل بديل قد يكون قابلاً للتكيف مع RichTextBox oultined في هذا التدوينة - حيث استخدم مشغلًا لاستبدال قالب التحكم عند استخدام الاستيلاء على عنصر التحكم - يجب أن يساعد في الأداء


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

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

وفقًا لـ Windows Dev Center :

TextBlock.IsTextSelectionEnabled خاصية

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

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


لم أتمكن من العثور على أي مثال للإجابة على السؤال. استخدمت كل الإجابات 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 Background="Transparent"
         BorderThickness="0"
         Text="{Binding Text, Mode=OneWay}"
         IsReadOnly="True"
         TextWrapping="Wrap" />


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 أو تحاول تنفيذ تحديد النص يدويًا ، مما يؤدي إلى ضعف الأداء أو السلوك غير المحلي (وضع علامة الإقحام في TextBox ، بدون دعم لوحة المفاتيح في التطبيقات اليدوية إلخ)

بعد ساعات من الحفر وقراءة شفرة المصدر WPF ، اكتشفت بدلاً من ذلك طريقة لتمكين اختيار نص WPF الأصلي لعناصر TextBlock (أو أي عناصر تحكم أخرى). يتم تطبيق معظم الوظائف حول تحديد النص في فئة النظام System.Windows.Documents.TextEditor .

لتمكين تحديد النص لعنصر التحكم ، يلزمك تنفيذ أمرين:

  1. استدعاء TextEditor.RegisterCommandHandlers() مرة واحدة لتسجيل معالجات الحدث فئة

  2. قم بإنشاء مثيل لـ TextEditor لكل مثيل 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;

لا يحتوي 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 في الحافظة. مجرد فكرة على أي حال.


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

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

قد لا يكون لديك منضدة حيث تحاول الربط ، يمكنك محاولة استبدال بناء ElementName Source={x:Reference DisplayMarkers} .

للحصول على حل بديل لأخطاء التبعية الدورية المحتملة ، راجع: https://.com/a/6858917/546730





wpf xaml textbox textblock