Можно ли выбрать текстовый блок WPF?
Я хочу, чтобы текст отображался в Witty , клиенте с открытым исходным кодом Twitter, который можно выбрать. В настоящее время он отображается с использованием настраиваемого текстового блока. Мне нужно использовать TextBlock, потому что я работаю с встроенными строками textblock для отображения и форматирования имени @us и ссылок как гиперссылок. Частым запросом является возможность вставить текст в текст. Для этого мне нужно сделать TextBlock доступным.
Я попытался заставить его работать, отображая текст, используя текстовый блок только для чтения, который выглядит как текстовый блок, но это не будет работать в моем случае, потому что TextBox не имеет встроенных строк. Другими словами, я не могу стилизовать или форматировать текст внутри TextBox отдельно, как я могу, с помощью TextBlock.
Есть идеи?
- Автоматическое управление текстовым полем по вертикали
- Как настроить текстовое поле в Silverlight?
- Отчеты Jasper - выравнивание динамических текстовых полей и их меток горизонтально
- Как автоматически перейти к нижней части многострочного текстового поля?
- Чтение текстового файла назад в C
Все ответы здесь просто используют TextBox
или пытаются вручную выполнить выбор текста, что приводит к плохой производительности или нелогичному поведению (мигающий кареток в TextBox
, отсутствие поддержки клавиатуры в ручных реализациях и т. Д.),
После нескольких часов работы и чтения исходного кода WPF я вместо этого обнаружил способ включения собственного текста текста WPF для элементов управления TextBlock
(или действительно любых других элементов управления). Большая часть функциональности вокруг выбора текста реализована в системном classе System.Windows.Documents.TextEditor
.
Чтобы включить выбор текста для вашего контроля, вам нужно сделать две вещи:
-
Вызовите
TextEditor.RegisterCommandHandlers()
один раз, чтобы зарегистрировать обработчики событий classа -
Создайте экземпляр
TextEditor
для каждого экземпляра вашего classа и передайте ему базовый экземпляр вашегоSystem.Windows.Documents.ITextContainer
Также необходимо, чтобы свойство Focusable
вашего Focusable
управления было установлено значение True
.
Это оно! Звучит просто, но, к сожалению, class 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;
Я не смог найти ни одного примера ответа на этот вопрос. Все ответы использовали текстовое поле или RichTextbox. Мне нужно решение, которое позволило мне использовать TextBlock, и это решение, которое я создал.
Я считаю, что правильный способ сделать это – расширить class TextBlock. Это код, который я использовал для расширения classа TextBlock, чтобы я мог выбрать текст и скопировать его в буфер обмена. «sdo» – это ссылка пространства имен, которую я использовал в WPF.
WPF Использование расширенного classа:
xmlns:sdo="clr-namespace:iFaceCaseMain"
Код для расширенного classа:
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); } } }
Пример кода windows:
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 и поместите TextBox внутри с набором свойств readonly. Или просто используйте TextBox и сделайте его только для чтения, затем вы можете изменить TextBox.Style, чтобы он выглядел как TextBlock.
Примените этот стиль к своему TextBox, и это все (вдохновлено этой статьей ):
Я не уверен, что вы можете сделать выбор TextBlock, но другой вариант – использовать RichTextBox – это как TextBox, как вы предлагали, но поддерживает нужное форматирование.
Согласно Windows Dev Center :
Свойство TextBlock.IsTextSelectionEnabled
[Обновлено для приложений UWP в Windows 10. Для статей Windows 8.x см. Архив ]
Возвращает или задает значение, указывающее, включен ли выбор текста в TextBlock , либо через действие пользователя, либо вызывающий API, связанный с выбором.
TextBlock не имеет шаблона. Поэтому для достижения этого нам нужно использовать TextBox, стиль которого изменился, чтобы вести себя как textBlock.
Существует альтернативное решение, которое может быть адаптировано к RichTextBox, выпущенному в этом сообщении в блоге, – оно использовало триггер для замены шаблона управления, когда использование зависает над элементом управления, – должно помочь в производительности
Хотя вопрос действительно говорит «Выбираемый», я считаю, что преднамеренные результаты – это получить текст в буфер обмена. Это можно легко и элегантно достичь, добавив контекстное меню и пункт меню, который называется копией, которая помещает значение свойства 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 } };
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 в моей библиотеке элементов управления opensource. Вы можете использовать его следующим образом:
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; } }