ControlTemplate Trigger don't work in designer (runtime ok) - wpf

I have a custom Control derived from Window:
class LVSDialog : Window
with DependencyProperty ShowCloseButton
and a Style with ControlTemplate and Trigger:
<Style TargetType="{x:Type loc:LVSDialog}" x:Key="LVSDialogStyle">
...
<Setter Property="Template">
...
<Button x:Name="closeButton" />
...
<ControlTemplate.Triggers>
<Trigger Property="loc:LVSDialog.ShowCloseButton" Value="False">
<Setter TargetName="closeButton" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</Setter>
Everything works fine in the runtime, but in Designer it doesn't take sence if I change this Property - Button is visible all the time:
<loc:LVSDialog ...
ShowCloseButton="False" Style="{StaticResource LVSDialogStyle}">
I have searched for a solution in google and here - all questions are about runtime functionality, designer problems are either unanswered or not working suggestions.
Is it possible at all use full functions in design time?
P.S. My VisualStudio is 2012. Framework 4.0

If you change the base class to Control instead of Window it will work:
public class LVSDialog : Control
{
public bool ShowCloseButton
{
get { return (bool)GetValue(ShowCloseButtonProperty); }
set { SetValue(ShowCloseButtonProperty, value); }
}
// Using a DependencyProperty as the backing store for ShowCloseButton. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ShowCloseButtonProperty =
DependencyProperty.Register("ShowCloseButton", typeof(bool), typeof(LVSDialog), new PropertyMetadata(true));
}
From Topicstarter:
I have changed to Control, added internal window, set it content to my control and added Show() and ShowDialog() methods:
private Window parentWindow;
...
public void Show()
{
if (parentWindow == null)
{
parentWindow = new Window {Content = this, WindowStyle = ...};
}
parentWindow.Show();
}
Everything works fine, designer shows all properties "live".

Related

How to set a DepedencyProperty by a style setter?

My user control have the following DP:
public static readonly DependencyProperty ButtonAnimationColorProperty =
DependencyProperty.Register("ButtonAnimationColor", typeof(Color), typeof(MyControl),
new FrameworkPropertyMetadata(Colors.RoyalBlue, FrameworkPropertyMetadataOptions.AffectsRender, ThemeUpdate));
public Color ButtonAnimationColor
{
get { return (Color)GetValue(ButtonAnimationColorProperty ); }
set { SetValue(ButtonAnimationColorProperty , value); }
}
This control is compiled into a dll, that I use in others solutions. It works perfect well when I set directly:
<ns:MyControl ButtonAnimationColor="Green" />
The problem occurs when I try to set this DP by using a Style Setter, like that:
<ns:MyControl>
<ns:MyControl.Style>
<Style>
<Setter Property="ButtonAnimationColor" Value="Green" />
</Style>
</ns:MyControl.Style>
</ns:MyControl>
It give me the following error:
The member "ButtoAnimationColor" is not recognized or is not acessible.
What changes I need to make in my code to be able to set the property like that?
Try setting the target type for the style:
<ns:MyControl.Style>
<Style TargetType="{x:Type ns:MyControl}">
<Setter Property="ButtonAnimationColor" Value="Green" />
</Style>
</ns:MyControl.Style>

How to change ContentControl's Content property using DataTemplate's DataTriggers based on Window's ViewModel property which is an enum value

I ask for your help in solving something that is simply said and most likely simply done but not for me at the moment.
What I'm developing is an application that looks like Office 2013 using FluidUI control set.
I want to achieve view switcher from Word/Access where on ribbon there is Views tab and there are buttons which switches views.
I thought that storing whole View object inside my ViewModel's property CurrentView is wrong way and I try to make this application as MVVM pure as possible. This application is more like "How to write app using MVVM" because I'm still learing WPF.
Ok. So I have my Window (MainWindowModern to be correct) which has Fluid Ribbon. There are 3 buttons to switch views (I call them Editors).
What they do is to change MainWindowModern's ViewModel's CurrentView property and set new enum value to it. This part of setting new enum value is done and works.
Now. The main body of the window is just ContentControl. Now I want to change this ContentControl's Content property based on DataContext.CurrentView property value. Like I said it before. I don't want to do any code-behind inside view's c# (i'm writing this app in C#) file.
The only thing that doesn't work is just changing ContentControl Content property. I'm trying to do this using DataTemplates and DataTemplate.Triggers
Now here's what I've got so far (without unrelated code).
The Window's XAML
<Fluent:MetroWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Fluent="clr-namespace:Fluent;assembly=Fluent"
xmlns:localVM="clr-namespace:MVVMTest.ViewModels"
xmlns:local="clr-namespace:MVVMTest"
x:Class="MVVMTest.Views.MainWindowModern"
x:Name="ThisWindow"
Title="Dialogue Editor (Modern UI Version)"
Width="1280" Height="480"
RibbonThemeColor="Red" WindowState="Maximized"
Icon="..\Assets\App\AppIcon_32x32.png">
<Window.Resources>
<DataTemplate x:Key="CharactersEditorTemplate">
<TextBlock Text="Characters Editor Template Body" />
</DataTemplate>
<DataTemplate x:Key="ChaptersEditorTemplate">
<TextBlock Text="Chapters Editor Template Body" />
</DataTemplate>
<DataTemplate x:Key="ConversationsEditorTemplate">
<TextBlock Text="Conversations Editor Template Body" />
</DataTemplate>
<DataTemplate x:Key="aaa" DataType="{x:Type ContentControl}" >
<TextBlock Text="{Binding ElementName=ThisWindow, Path=DataContext.CurrentView}" />
<DataTemplate.Triggers>
<!--<DataTrigger Binding="{Binding CurrentView}">
<DataTrigger.Value>
<localVM:EditorView>CharactersEditor</localVM:EditorView>
</DataTrigger.Value>
<Setter Property="Content" Value="{StaticResource CharactersEditorTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentView}">
<DataTrigger.Value>
<localVM:EditorView>ChaptersEditor</localVM:EditorView>
</DataTrigger.Value>
<Setter Property="Content" Value="{StaticResource ChaptersEditorTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentView}">
<DataTrigger.Value>
<localVM:EditorView>ConversationsEditor</localVM:EditorView>
</DataTrigger.Value>
<Setter Property="Content" Value="{StaticResource ConversationsEditorTemplate}" />
</DataTrigger>-->
<DataTrigger Binding="{Binding ElementName=ThisWindow, Path=DataContext.CurrentView}">
<DataTrigger.Value>
<localVM:EditorView>ChaptersEditor</localVM:EditorView>
</DataTrigger.Value>
<DataTrigger.Setters>
<Setter Property="Content" Value="{StaticResource ChaptersEditorTemplate}" />
</DataTrigger.Setters>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<localVM:MainWindowVM />
</Window.DataContext>
<DockPanel x:Name="LayoutRoot" LastChildFill="True">
<Fluent:Ribbon DockPanel.Dock="Top">
<Fluent:RibbonTabItem Header="VIEW" Fluent:KeyTip.Keys="V" ReduceOrder="ViewsRibbonGroupBox, ViewsRibbonGroupBox, ViewsRibbonGroupBox">
<Fluent:RibbonGroupBox Name="ViewsRibbonGroupBox" Header="Views">
<Fluent:Button Name="CharactersViewButton"
Header="Characters"
LargeIcon="..\Assets\Ribbon\View\CharacterEditorIcon_32x32.png"
Icon="..\Assets\Ribbon\View\CharacterEditorIcon_32x32.png"
Command="{Binding SwitchToCharactersEditorCommand}" >
<Fluent:Button.ToolTip>
<Fluent:ScreenTip
Image="..\Assets\Ribbon\View\CharacterEditorIcon_32x32.png"
Title="Characters Editor"
Text="Changes current view to Characters Editor view.
In this view user can:
• List existing characters
• Create new characters
• Edit existing characters
• Delete existing characters
It is also possible to manage character's emotions in this view." />
</Fluent:Button.ToolTip>
</Fluent:Button>
<Fluent:Button Name="ChaptersViewButton"
Header="Chapters"
LargeIcon="..\Assets\Ribbon\View\ChapterEditorIcon_32x32.png"
Icon="..\Assets\Ribbon\View\ChapterEditorIcon_32x32.png"
Command="{Binding SwitchToChaptersEditorCommand}" >
<Fluent:Button.ToolTip>
<Fluent:ScreenTip
Image="..\Assets\Ribbon\View\ChapterEditorIcon_32x32.png"
Title="Chapters Editor"
Text="Changes current view to Chapters Editor view.
In this view user can:
• List existing chapters
• Create new chapters
• Edit existing chapters
• Delete existing chapters
It is also possible to manage chapters's missions in this view." />
</Fluent:Button.ToolTip>
</Fluent:Button>
<Fluent:Button Name="ConversationsViewButton"
Header="Conversations"
LargeIcon="..\Assets\Ribbon\View\ConversationEditorIcon_32x32.png"
Icon="..\Assets\Ribbon\View\ConversationEditorIcon_32x32.png"
Command="{Binding SwitchToConversationsEditorCommand}" >
<Fluent:Button.ToolTip>
<Fluent:ScreenTip
Image="..\Assets\Ribbon\View\ConversationEditorIcon_32x32.png"
Title="Conversations Editor"
Text="Changes current view to Conversations Editor view.
In this view user can:
• List existing conversations
• Create new conversations
• Edit existing conversations
• Delete existing conversations
It is also possible to manage conversations's statements and statement's stages in this view."
DisableReason="Please define at least one chapter with at least one mission in it to enable Conversations Editor.
Also it would be helpful to define at least one character with at least one emotion.
It is optional action but highly recommended." />
</Fluent:Button.ToolTip>
</Fluent:Button>
</Fluent:RibbonGroupBox>
</Fluent:RibbonTabItem>
</Fluent:Ribbon>
<ContentControl Name="MainContent" ContentTemplate="{StaticResource aaa}" />
</DockPanel>
</Fluent:MetroWindow>
Window's ViewModel and the enum
using MVVMTest.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMTest.ViewModels {
public enum EditorView {
CharactersEditor,
ChaptersEditor,
ConversationsEditor
}
public class MainWindowVM : ViewModelBase {
public MainWindowVM() {
this.init();
}
protected void init() {
this.Characters = new ObservableCollection<CharacterVM>();
this.initCommands();
this.initSampleData();
}
protected void initSampleData() {
Character ch1 = new Character() { Name = "Character 1" };
Emotion e1 = new Emotion() { Name = "Emotion 1" };
ch1.Emotions.Add(e1);
CharacterVM ch1vm = new CharacterVM(ch1);
this.Characters.Add(ch1vm);
this.CurrentView = EditorView.ConversationsEditor;
}
protected void initCommands() {
this.SwitchToCharactersEditorCommand = new RelayCommand(param => this.SwitchToCharactersEditor(), param => this.CanSwitchToCharactersEditor());
this.SwitchToChaptersEditorCommand = new RelayCommand(param => this.SwitchToChaptersEditor(), param => this.CanSwitchToChaptersEditor());
this.SwitchToConversationsEditorCommand = new RelayCommand(param => this.SwitchToConversationsEditor(), param => this.CanSwitchToConversationsEditor());
}
public ObservableCollection<CharacterVM> Characters { get; set; }
protected EditorView _currentView;
public EditorView CurrentView {
get { return this._currentView; }
set {
if (this._currentView == value) {
return;
}
this._currentView = value;
this.OnPropertyChanged("CurrentView");
}
}
#region Commands
#region View Tab
#region Switch To Characters Editor
public RelayCommand SwitchToCharactersEditorCommand { get; private set; }
protected void SwitchToCharactersEditor() {
this.CurrentView = EditorView.CharactersEditor;
}
protected bool CanSwitchToCharactersEditor() {
if (this.CurrentView != EditorView.CharactersEditor) {
return true;
}
return false;
}
#endregion Switch To Characters Editor
#region Switch To Chapters Editor
public RelayCommand SwitchToChaptersEditorCommand { get; private set; }
protected void SwitchToChaptersEditor() {
this.CurrentView = EditorView.ChaptersEditor;
}
protected bool CanSwitchToChaptersEditor() {
if (this.CurrentView != EditorView.ChaptersEditor) {
return true;
}
return false;
}
#endregion Switch To Chapters Editor
#region Switch To Conversations Editor
public RelayCommand SwitchToConversationsEditorCommand { get; private set; }
protected void SwitchToConversationsEditor() {
this.CurrentView = EditorView.ConversationsEditor;
}
protected bool CanSwitchToConversationsEditor() {
if (this.CurrentView != EditorView.ConversationsEditor) {
return true;
}
return false;
}
#endregion Switch To Conversations Editor
#endregion View Tab
#endregion Commands
}
}
When this is all done the next step is to add animation to view switch just like on ModernUI Apps (or android smartphones) so the old content goes over the border of the window and new content comes from the other side. If it is not possible then I'll stop with just working switcher.
The good news is that this is actually pretty easy to do, including the animation switching. What you need is an ItemsControl to host your subviews. The ItemsControl allows you to use a DataTemplateSelector. So based on your enum value you can produce some output that the selector can use to determine which datatemplate to use. Try some research on the selector. If your are still confused feel free to reach out to me. Good luck.
For animations, I suggest a container control that would host your subviews. Check out this link for a pretty solid implementation to get you started.

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

Disable Save button in WPF if validation fails

I've adopted what appears to be the standard way of validating textboxes in WPF using the IDataErrorInfo interface and styles as shown below. However, how can I disable the Save button when the page becomes invalid? Is this done somehow through triggers?
Default Public ReadOnly Property Item(ByVal propertyName As String) As String Implements IDataErrorInfo.Item
Get
Dim valid As Boolean = True
If propertyName = "IncidentCategory" Then
valid = True
If Len(IncidentCategory) = 0 Then
valid = False
End If
If Not valid Then
Return "Incident category is required"
End If
End If
Return Nothing
End Get
End Property
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="3" />
<Setter Property="Height" Value="23" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
A couple of things:
First, I would recommend using the RoutedCommand ApplicationCommands.Save for implementing the handling of the save button.
If you haven't checked out the WPF Command model, you can get the scoop here.
<Button Content="Save" Command="Save">
Now, to implement the functionality, you can add a command binding to the Window/UserControl or to the Button itself:
<Button.CommandBindings>
<CommandBinding Command="Save"
Executed="Save_Executed" CanExecute="Save_CanExecute"/>
</Button.CommandBindings>
</Button>
Implement these in code behind:
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
private void Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
}
In Save_CanExecute, set e.CanExecute based on the validity of the binding on the text box.
If you want to implement using the MVVM (Model-View-ViewModel) design pattern, check out Josh Smith's post on CommandSinkBinding.
One final note: If you want the enable/disable to be updated as soon as the value in the TextBox is changed, set UpdateSourceTrigger="PropertyChanged" on the binding for the TextBox.
EDIT: If you want to validate/invalidate based on all of the bindings in the control, here are a few suggestions.
1) You are already implementing IDataErrorInfo. Try implementing the IDataErrorInfo.Error property such that it returns the string that is invalid for all of the properties that you are binding to. This will only work if your whole control is binding to a single data object. Set e.CanExecute = string.IsNullOrEmpty(data.Error);
2) Use reflection to get all of the public static DependencyProperties on the relevant controls. Then call BindingOperations.GetBindingExpression(relevantControl, DependencyProperty) in a loop on each property so you can test the validation.
3) In the constructor, manually create a collection of all bound properties on nested controls. In CanExecute, iterate through this collection and validate each DependencyObject/DepencyProperty combination by using BindingOperation.GetBindingExpression() to get expressions and then examining BindingExpression.HasError.
I've created attached property just for this:
public static class DataErrorInfoHelper
{
public static object GetDataErrorInfo(ButtonBase obj)
{
return (object)obj.GetValue(DataErrorInfoProperty);
}
public static void SetDataErrorInfo(ButtonBase obj, object value)
{
obj.SetValue(DataErrorInfoProperty, value);
}
// Using a DependencyProperty as the backing store for DataErrorInfo. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataErrorInfoProperty =
DependencyProperty.RegisterAttached("DataErrorInfo", typeof(object), typeof(DataErrorInfoHelper), new PropertyMetadata(null, OnDataErrorInfoChanged));
private static void OnDataErrorInfoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var button = d as ButtonBase;
if (button.Tag == null)
button.Tag = new DataErrorInfoContext { Button = button };
var context = button.Tag as DataErrorInfoContext;
if(e.OldValue != null)
{
PropertyChangedEventManager.RemoveHandler(((INotifyPropertyChanged)e.OldValue), context.Handler, string.Empty);
}
var inotify = e.NewValue as INotifyPropertyChanged;
if (inotify != null)
{
PropertyChangedEventManager.AddHandler(inotify, context.Handler, string.Empty);
context.Handler(inotify, new PropertyChangedEventArgs(string.Empty));
}
}
private class DataErrorInfoContext
{
public ButtonBase Button { get; set; }
public void Handler(object sender, PropertyChangedEventArgs e)
{
var dei = sender as IDataErrorInfo;
foreach (var property in dei.GetType().GetProperties())
{
if (!string.IsNullOrEmpty(dei[property.Name]))
{
Button.IsEnabled = false;
return;
}
}
Button.IsEnabled = string.IsNullOrEmpty(dei.Error);
}
}
}
I'm using it like this on my forms:
<TextBlock Margin="2">e-mail:</TextBlock>
<TextBox Margin="2" Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
<!-- other databindings--->
<Button Margin="2" local:DataErrorInfoHelper.DataErrorInfo="{Binding}" Commands="{Binding SaveCommand}">Create account</Button>

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