Любой способ сделать текстовый блок WPF выбираемым?
Я хочу, чтобы текст отображался в остроумные, клиент Twitter с открытым исходным кодом, выбирается. В настоящее время выполняется с помощью пользовательского кода. Мне нужно использовать TextBlock, потому что я работаю с строками textblock для отображения и форматирования @username и ссылок в виде гиперссылок. Частым запросом является возможность копировать-вставлять текст. Для этого мне нужно сделать текстовый блок выбираемым.
Я попытался заставить его работать, отображая текст с помощью текстовое поле только для чтения, стилизованное под текстовый блок, но это не будет работать в моем случае, потому что текстовое поле не имеет строк. Другими словами, Я не могу стилизовать или отформатировать текст в текстовом поле индивидуально, как я могу с текстовым блоком.
какие идеи?
13 ответов:
<TextBox Background="Transparent" BorderThickness="0" Text="{Binding Text, Mode=OneWay}" IsReadOnly="True" TextWrapping="Wrap" />
все ответы здесь просто с помощью
TextBox
или попытка реализовать выделение текста вручную, что приводит к низкой производительности или неродному поведению (мигание курсора вTextBox
, отсутствие поддержки клавиатуры в ручных реализациях etc.)после нескольких часов копания и чтения исходный код WPF, вместо этого я обнаружил способ включения собственного выбора текста WPF для
TextBlock
элементы управления (или действительно любые другие элементы управления). Большая часть функциональности вокруг выделение текста реализовано вSystem.Windows.Documents.TextEditor
система класса.включить выделение текста для вашего контроля, вам нужно сделать две вещи:
вызов
TextEditor.RegisterCommandHandlers()
один раз для регистрации класса обработчики событийсоздать экземпляр
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
включить выделение текста по требованию. В этом случае, чтобы снова отключить выделение, нужно отсоединить aTextEditor
by используя эквивалент отражения этого кода:_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); }
создать ControlTemplate для TextBlock и поместить текстовое поле внутри с набором свойств readonly. Или просто использовать текстовое поле и сделать его только для чтения, то вы можете изменить текстовое поле.Стиль, чтобы он выглядел как TextBlock.
применить этот стиль к текстовому полю, и это все (вдохновленный в этой статье):
<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>
Я не уверен, что вы можете сделать выбор текстового блока, но другим вариантом было бы использовать RichTextBox - это похоже на текстовое поле, как вы предложили, но поддерживает форматирование, которое вы хотите.
по данным Центр Разработки Windows:
TextBlock.IsTextSelectionEnabled свойство
[обновлено для приложений UWP в Windows 10. Для Windows 8.х статьях, см. элемент архиве]
возвращает или задает значение, указывающее, включен ли выбор текста в TextBlock, либо через действие пользователя или вызов API, связанный с выбором.
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>
существует альтернативное решение, которое может быть адаптировано к RichTextBox oultined в этом блоге - он использовал триггер для замены шаблона управления, когда использование зависает над элементом управления-должно помочь с производительностью
хотя вопрос говорит "выбирается", я считаю, что намеренные результаты-это получить текст в буфер обмена. Это может быть легко и элегантно достигнуто путем добавления контекстного меню и пункта меню под названием copy, который помещает значение свойства Textblock Text в буфер обмена. В любом случае, это просто идея.
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" />
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; } }