WPF does not seem to find a custom attached property - wpf

I am trying to bind a property (Button.Background) to a property on my custom attached property.
In a C# file I have
public static class Square
{
public static readonly DependencyProperty PlayerProperty =
DependencyProperty.RegisterAttached
(
name : "Player",
propertyType : typeof(Player),
ownerType : typeof(UIElement),
defaultMetadata: new FrameworkPropertyMetadata(null)
);
public static Player GetPlayer(UIElement element)
{
return (Player)element.GetValue(PlayerProperty);
}
public static void SetPlayer(UIElement element, Player player)
{
element.SetValue(PlayerProperty, player);
}
// Other attached properties
}
A snippet of my XAML is
<Grid Name="board" Grid.Row="1" Grid.Column="1">
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Height" Value="20" />
<Setter Property="Width" Value="20" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="Background"
Value="{Binding Path=(l:Square.Player).Brush, Mode=OneWay}" />
</Style>
</Grid.Resources>
</Grid>
This is the error I get:
Cannot convert string '(l:Square.Player).Brush' in attribute 'Path' to object of type 'System.Windows.PropertyPath'.
Property path is not valid. 'Square' does not have a public property named 'Player'.
Error at object 'System.Windows.Data.Binding' in markup file 'Gobang.Gui;component/mainwindow.xaml' Line 148 Position 59.
But since Player is an attached property which is on Square, the above code should work, right?

I believe your attached property should designate Square as the owner rather than UIElement.
public static readonly DependencyProperty PlayerProperty =
DependencyProperty.RegisterAttached("Player", typeof(Player),
typeof(Square), new FrameworkPropertyMetadata(null));

I got it to work.
Note: its a read-only property, the Helper class HAS TO inherit from DependencyObject
public class Helper : DependencyObject
{
public static readonly DependencyPropertyKey IsExpandedKey = DependencyProperty.RegisterAttachedReadOnly(
"IsExpanded", typeof(bool), typeof(Helper), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits));
public static readonly DependencyProperty IsExpandedProperty = IsExpandedKey.DependencyProperty;
public static bool GetIsExpanded(DependencyObject d)
{
return (bool)d.GetValue(IsExpandedKey.DependencyProperty);
}
internal static void SetIsExpanded(DependencyObject d, bool value)
{
d.SetValue(IsExpandedKey, value);
}
}

You can't set up a binding in the way that you're doing it - you'll need an instance of either Square or Player to bind to that.

Related

How to set DP value when the custom control use in the xaml? (when declaring)

My English skill is poor because I'm not a native English speaker.
I hope you to understand.
I created a custom window that overrides the title bar shape.
The part of the xaml code is as shown below.
<Style x:Key="MainWindow" TargetType="{x:Type Window}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<Border x:Name="PART_TITLEBAR"
Margin="2,0,2,2"
Height="30"
DockPanel.Dock="Top"
CornerRadius="2"
Background="Transparent">
This control works well except for one problem.
The problem is that can't set the value of the DP.
The part of the cs code of the control is as shown below.
[TemplatePart(Name = "PART_TITLEBAR", Type = typeof(UIElement))]
public partial class CustomWindow : Window
{
private UIElement TitleBar { get; set; }
#region Dependency Properties for appearance.
public int TitleBarHeight
{
get { return (int)GetValue(TitleBarHeightProperty); }
set { SetValue(TitleBarHeightProperty, value); }
}
// Using a DependencyProperty as the backing store for TitleBarHeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleBarHeightProperty =
DependencyProperty.Register("TitleBarHeight", typeof(int), typeof(CustomWindow), new PropertyMetadata(TitleBarHeightChanged));
public static void TitleBarHeightChanged(DependencyObject dp, DependencyPropertyChangedEventArgs args)
{
CustomWindow window = dp as CustomWindow;
Border titleBar = window.TitleBar as Border;
if (titleBar == null) return;
titleBar.Height = (int)args.NewValue;
}
public SolidColorBrush TitleTextBrush
{
get { return (SolidColorBrush)GetValue(TitleTextBrushProperty); }
set { SetValue(TitleTextBrushProperty, value); }
}
// Using a DependencyProperty as the backing store for TitleTextBrush. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleTextBrushProperty =
DependencyProperty.Register("TitleTextBrush", typeof(SolidColorBrush), typeof(CustomWindow), new PropertyMetadata(TitleTextBrushChanged));
public static void TitleTextBrushChanged(DependencyObject dp, DependencyPropertyChangedEventArgs args)
{
CustomWindow window = dp as CustomWindow;
Border titleBar = window.TitleBar as Border;
if (titleBar == null) return;
// find the textblock control of the children of the titlebar and change the value of the foreground of the control.
}
#endregion
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
AttachToVisualTree();
}
private void AttachToVisualTree()
{
AttachCloseButton();
AttachMinimizeButton();
AttachMaximizeRestoreButton();
AttachTitleBar();
AttachBorders();
}
private void AttachTitleBar()
{
if (TitleBar != null)
{
TitleBar.RemoveHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnTitlebarClick));
}
UIElement titleBar = GetChildControl<UIElement>("PART_TITLEBAR");
if (titleBar != null)
{
TitleBar = titleBar;
titleBar.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnTitlebarClick));
}
}
I tried to track the problem and I have found the cause.
First, I loaded the custom control in the main project and set the value of the DP on the custom control as below.
<custom:CustomWindow TitleBarHeight="20">
<.../>
</custom:CustomWindow>
And then later, I executed the project and the sequence processed as below.
The CustomWindow is created. (constructor called)
The TitleBarHeight value of the CustomWindow is set
OnApplyTemplate() of the CustomWindow is called.
According to my confirmation, sequence 2 is the starting point of the problem.
In sequence 2, WPF trying to set the TitleBarHeight value of the CustomWindow. therefore the below code is called.
public static void TitleBarHeightChanged(DependencyObject dp, DependencyPropertyChangedEventArgs args)
{
CustomWindow window = dp as CustomWindow;
Border titleBar = window.TitleBar as Border;
if (titleBar == null) return;
titleBar.Height = (int)args.NewValue;
}
But at this point, the TitleBar has not be instantiated so TitleBarHeight value is not set.
As a result, it would be moved to the routine of the below.
if (titleBar == null) return;
After then later, OnApplyTemplate() is called and TitleBar is instantiated.
Summary :
when execute < custom:CustomWindow TitleBarHeight="20"> logic, at this point the TitleBar of the CustomWindow is not instantiated so TitleBarHeight value is not set.
What I should do to solve this problem?
I hope to get your help.
Thank you for reading.
Thanks for the advice, I solved this problem.
I modified xaml code as below.
<Border x:Name="PART_TITLEBAR"
Margin="2,0,2,2"
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:CustomWindow}, Path=TitleBarHeight}"
DockPanel.Dock="Top"
CornerRadius="2"
Background="Transparent">
If you have a better way of doing this, please let me know.
Thank you for advice.

Binding the Key in <object property="{StaticResource key}" .../> to a value

In WPF, is it possible to bind the key in "{StaticResource key}"to a variable.
For example. I have a variable ExecutionState with the states Active and Completed.
In my ResourceDictionary I have
<Style TargetType="{x:Type TextBlock}" x:Key="Active">
<Setter Property="Foreground" Value="Yellow"/>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="Completed">
<Setter Property="Foreground" Value="Green"/>
</Style>
Instead of having
<TextBlock Style="{StaticResource Active}"/>
I Would like to have something like
<TextBlock Style="{StaticResource {Binding ExecutionState}}"/>
Thus if the state changes the text color changes.
Is something like this even possible?
I can achieve the wanted functionality using triggers, but I have to reuse it at several places and I don't want to clutter my code.
I am using MVVM also.
thanx
No, it's not possible. Binding can only be set on a DependencyProperty. StaticResource is not a DependencyObject, so there is no DependencyProperty. You should use Trigger(s) or develop your own attached behavior.
There is no direct way to achieve .
Create one attached property and assign the property name to bind.
In the property change callback function update control style.
<TextBlock dep:CustomStyle.StyleName="{Binding ExecutionState}" Text="Thiru" />
public static class CustomStyle
{
static FrameworkPropertyMetadata _styleMetadata = new FrameworkPropertyMetadata(
string.Empty, FrameworkPropertyMetadataOptions.AffectsRender, StyleNamePropertyChangeCallBack);
public static readonly DependencyProperty StyleNameProperty =
DependencyProperty.RegisterAttached("StyleName", typeof (String), typeof (CustomStyle), _styleMetadata);
public static void SetStyleName(UIElement element, string value)
{
element.SetValue(StyleNameProperty, value);
}
public static Boolean GetStyleName(UIElement element)
{
return (Boolean)element.GetValue(StyleNameProperty);
}
public static void StyleNamePropertyChangeCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement ctrl = d as FrameworkElement;
if (ctrl.IsLoaded)
{
string styleName = Convert.ToString(e.NewValue);
if (!string.IsNullOrEmpty(styleName))
{
ctrl.Style = ctrl.TryFindResource(styleName) as Style;
}
}
}
}

Usercontrol properties in Styles

I have a usercontrol that expose a public property like this :
public Double ButtonImageHeight
{
get { return imgButtonImage.Height; }
set { imgButtonImage.Height = value; }
}
when I use that control, I want to be able to set that property throught a Style like that :
<Style x:Key="MyButtonStyle" TargetType="my:CustomButtonUserControl" >
<Setter Property="ButtonImageHeight" Value="100" />
</Style>
What am I doing wrong?
Thanks
thanks Matt, I just found it myself but you were absolutely right... here's the exact code I used in case it can help someone else (all the examples I found were on WPF, silverlight is just slightly different) :
public static readonly DependencyProperty ButtonImageHeightProperty = DependencyProperty.Register("ButtonImageHeight", typeof(Double), typeof(CustomButtonUserControl),new PropertyMetadata(ButtonImageHeight_PropertyChanged ));
public Double ButtonImageHeight
{
get { return (Double)GetValue(ButtonImageHeightProperty); }
set { SetValue(ButtonImageHeightProperty, value); }
}
private static void ButtonImageHeight_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((CustomButtonUserControl)source).imgButtonImage.Height = (Double)e.NewValue;
}
The property needs to be a dependency property in order to support styles.
You can make it even more generic and nice by passing through a Style for your imgButtonImage, that way you can set multiple properties. So within your user control add the dependency property, but make it a Style:
public static readonly DependencyProperty UseStyleProperty =
DependencyProperty.Register("UseStyle", typeof(Style), typeof(CustomButtonUserControl), new PropertyMetadata(UseStyle_PropertyChanged));
public Style UseStyle
{
get { return (Style)GetValue(UseStyleProperty); }
set { SetValue(UseStyleProperty, value); }
}
private static void UseStyle_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((CustomButtonUserControl)source).imgButtonImage.Style = (Style)e.NewValue;
}
Notice how within the PropertyChanged function I set the style of the control to the new style.
Then when I host the UserControl I can pass through the style:
<Style x:Name="MyFancyStyle" TargetType="Button" >
<Setter Property="FontSize" Value="24" />
</Style>
<controls:MyUserControl UseStyle="{StaticResource MyFancyStyle}" />
works in VS design mode too! (It's a miracle)

XamlParseException - Invalid attribute value (...) for property Property

I'm having some trouble styling a custom control that I built. Here's the control source:
namespace SilverlightStyleTest
{
public class AnotherControl: TextBox
{
public string MyProperty { get; set; }
}
}
In the same namespace & project I try to create a style with a setter for MyProperty like so:
<UserControl x:Class="SilverlightStyleTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Local="clr-namespace:SilverlightStyleTest">
<UserControl.Resources>
<Style x:Name="AnotherStyle" TargetType="Local:AnotherControl">
<Setter Property="Width" Value="200"/>
<Setter Property="MyProperty" Value="Hello."/>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Local:AnotherControl Style="{StaticResource AnotherStyle}"/>
</Grid>
</UserControl>
I end up with the runtime error:
Invalid attribute value MyProperty for property Property. [Line: 9 Position: 30]
I can't figure out what's wrong with the style to cause this error. I also tried "fully qualifying" the property name as "Local:AnotherControl.MyProperty" but that didn't work either.
Non-dependency properties cannot be set in styles.
You need to define it as a DependencyProperty:
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string), typeof(AnotherTextBox),
new FrameworkPropertyMetadata((string)null));
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}

Any way to make a WPF textblock selectable?

How to allow TextBlock's text to be selectable?
I tried to get it to work by displaying the text using a read-only TextBox styled to look like a textblock but this will not work in my case because a TextBox does not have inlines. In other words, how to make it selectable?
Use a TextBox with these settings instead to make it read only and to look like a TextBlock control.
<TextBox Background="Transparent"
BorderThickness="0"
Text="{Binding Text, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap" />
All the answers here are just using a TextBox or trying to implement text selection manually, which leads to poor performance or non-native behaviour (blinking caret in TextBox, no keyboard support in manual implementations etc.)
After hours of digging around and reading the WPF source code, I instead discovered a way of enabling the native WPF text selection for TextBlock controls (or really any other controls). Most of the functionality around text selection is implemented in System.Windows.Documents.TextEditor system class.
To enable text selection for your control you need to do two things:
Call TextEditor.RegisterCommandHandlers() once to register class
event handlers
Create an instance of TextEditor for each instance of your class and pass the underlying instance of your System.Windows.Documents.ITextContainer to it
There's also a requirement that your control's Focusable property is set to True.
This is it! Sounds easy, but unfortunately TextEditor class is marked as internal. So I had to write a reflection wrapper around it:
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);
}
}
I also created a SelectableTextBlock derived from TextBlock that takes the steps noted above:
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);
}
}
Another option would be to create an attached property for TextBlock to enable text selection on demand. In this case, to disable the selection again, one needs to detach a TextEditor by using the reflection equivalent of this code:
_editor.TextContainer.TextView = null;
_editor.OnDetach();
_editor = null;
I have been unable to find any example of really answering the question. All the answers used a Textbox or RichTextbox. I needed a solution that allowed me to use a TextBlock, and this is the solution I created.
I believe the correct way to do this is to extend the TextBlock class. This is the code I used to extend the TextBlock class to allow me to select the text and copy it to clipboard. "sdo" is the namespace reference I used in the WPF.
WPF Using Extended Class:
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>
Code Behind for Extended 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);
}
}
}
Example Window Code:
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);
}
Create ControlTemplate for the TextBlock and put a TextBox inside with readonly property set.
Or just use TextBox and make it readonly, then you can change the TextBox.Style to make it looks like TextBlock.
Apply this style to your TextBox and that's it (inspired from this article):
<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>
I'm not sure if you can make a TextBlock selectable, but another option would be to use a RichTextBox - it is like a TextBox as you suggested, but supports the formatting you want.
According to Windows Dev Center:
TextBlock.IsTextSelectionEnabled property
[ Updated for UWP apps on Windows 10. For Windows 8.x articles, see
the archive ]
Gets or sets a value that indicates whether text selection is enabled
in the TextBlock, either through user action or calling
selection-related API.
While the question does say 'Selectable' I believe the intentional results is to get the text to the clipboard. This can easily and elegantly be achieved by adding a Context Menu and menu item called copy that puts the Textblock Text property value in clipboard. Just an idea anyway.
TextBlock does not have a template. So inorder to achieve this, we need to use a TextBox whose style is changed to behave as a 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>
There is an alternative solution that might be adaptable to the RichTextBox oultined in this blog post - it used a trigger to swap out the control template when the use hovers over the control - should help with performance
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
}
};
Here is what worked for me. I created a class TextBlockEx that is derived from TextBox and is set read-only, and text wrap in the constructor.
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 !
I bring some small modifications
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;
}
Adding to #torvin's answer and as #Dave Huang mentioned in the comments if you have TextTrimming="CharacterEllipsis" enabled the application crashes when you hover over the ellipsis.
I tried other options mentioned in the thread about using a TextBox but it really doesn't seem to be the solution either as it doesn't show the 'ellipsis' and also if the text is too long to fit the container selecting the content of the textbox 'scrolls' internally which isn't a TextBlock behaviour.
I think the best solution is #torvin's answer but has the nasty crash when hovering over the ellipsis.
I know it isn't pretty, but subscribing/unsubscribing internally to unhandled exceptions and handling the exception was the only way I found of solving this problem, please share if somebody has a better solution :)
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;
}
}
}
}
Just use a FlowDocument inside a FlowDocumentScrollViewer, passing your inlines to the element.
You can control the style of the element, in my case I added a small border.
<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>
I agree most answers here do not create a selectable TextBlock. #Billy Willoughby's worked well however it didn't have a visible cue for selection. I'd like to extend his extension which can highlight text as it is selected. It also incorporates double and triple click selection. You can add a context menu with a "Copy" if needed.
It uses the Background property to "highlight" the selection so it is limited in that it will overwrite Run.Background
https://github.com/mwagnerEE/WagnerControls
Added Selection & SelectionChanged Event to torvin's code
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;
}
I've implemented SelectableTextBlock in my opensource controls library. You can use it like this:
<jc:SelectableTextBlock Text="Some text" />

Resources