In our WPF software, we used a ControlTemplate which defines a ToggleButton that causes the window to shrink/extend. The definition of ToggleButton is given below:
<ToggleButton ToolTip="Standard/Extended" Grid.Column="0"
x:Name="PART_MaximizeToggle" VerticalAlignment="Top"
HorizontalAlignment="Right" Margin="0,5,5,0"
Width="14" Height="14" Cursor="Hand">
We're creating a custom DockPanel which contains this button at the upper right corner. Our application can contain up to three of this DockPanels at the same time:
The small rectangle on the right of each DockPanel is shown in the image above.
Notice from the definition that all three of the rectangles have same name: "PART_MaximizeToggle". This causes trouble when writing CodedUI programs to automate testing. CodedUI captures all of their FriendlyNames as "PART_MaximizeToggle" with Name field empty. The locations and sequence of the DockPanels can change based or what the user want.
How can we make CodedUI capture exact the button where a click is expected? I was thinking of making the Name of each toggle button dynamic but fixed for a specific DockPanel.
How can I do that? Is there a better approach?
You could assign (and register) the names automatically via an AttachedProperty that increments a counter for each prefix.
(This is just a proof of concept, you should also check that the names are valid)
public static class TestingProperties
{
private static readonly Dictionary<string, int> _counter = new Dictionary<string, int>();
public static readonly DependencyProperty AutoNameProperty = DependencyProperty.RegisterAttached(
"AutoName", typeof(string), typeof(TestingProperties), new PropertyMetadata(default(string), OnAutoNamePropertyChanged));
private static void OnAutoNamePropertyChanged(DependencyObject element, DependencyPropertyChangedEventArgs eventArgs)
{
string value = (string) eventArgs.NewValue;
if (String.IsNullOrWhiteSpace(value)) return;
if (DesignerProperties.GetIsInDesignMode(element)) return;
if (!(element is FrameworkElement)) return;
int index = 0;
if (!_counter.ContainsKey(value))
_counter.Add(value, index);
else
index = ++_counter[value];
string name = String.Format("{0}_{1}", value, index);
((FrameworkElement)element).Name = name;
((FrameworkElement)element).RegisterName(name, element);
}
public static void SetAutoName(DependencyObject element, string value)
{
element.SetValue(AutoNameProperty, value);
}
public static string GetAutoName(DependencyObject element)
{
return (string)element.GetValue(AutoNameProperty);
}
}
Usage in XAML:
<!-- will be Button_0 -->
<Button namespace:TestingProperties.AutoName="Button"/>
<!-- will be Button_1 -->
<Button namespace:TestingProperties.AutoName="Button"/>
<!-- will be Button_2 -->
<Button namespace:TestingProperties.AutoName="Button"/>
Resulting Visual-Tree:
Manfred Radlwimmer's solution is useful but makes the controls code behind harder.
Any dynamic code in the Controls' OnApplyTemplate that searches for that template part will become a pain.
An alternative would be to use same trick (generation of a unique id) for the automation id instead and use the automation id in the tests.
See:
http://www.jonathanantoine.com/2011/11/03/coded-ui-tests-automationid-or-how-to-find-the-chose-one-control/
Related
I spent several hours searching and testing and can't get it to work. I want to have a UserControl that exposes a template to fill a section of the UserControl. I got it to work by creating a DependencyProperty of type ContentTemplate (or DataTemplate?). Then I display it like this
<ContentControl x:Name="PlayerContent" ContentTemplate="{Binding PlayerTemplate, ElementName=W}" />
Now the problem is that when I use the UserControl I cannot set element names within the template.
<local:MediaPlayerWpf x:Name="PlayerUI" Height="auto" Width="auto">
<local:MediaPlayerWpf.PlayerTemplate>
<ControlTemplate>
<WindowsFormsHost x:Name="Host" Focusable="False" />
</ControlTemplate>
</local:MediaPlayerWpf.PlayerTemplate>
</local:MediaPlayerWpf>
This throws
Cannot set Name attribute value 'Host' on element
'WindowsFormsHost'. 'WindowsFormsHost' is under the
scope of element 'MediaPlayerWpf', which already had a name registered
when it was defined in another scope.
As a result, I have no way of accessing the control defined within the template. I also found no way of accessing the root of the children displayed in the ContentControl.
How can I access the "Host" control defined in the template?
It seems we can't use templates in such a way within UserControl. I don't know whether there's a work-around.
I ended up taking a different approach. I exposed a property to set the content.
public static DependencyProperty HostProperty = DependencyProperty.Register("Host", typeof(PlayerBase), typeof(MediaPlayerWpf), new PropertyMetadata(null, OnHostChanged));
public PlayerBase Host { get => (PlayerBase)base.GetValue(HostProperty); set => base.SetValue(HostProperty, value); }
private static void OnHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (e.OldValue != null)
P.HostGrid.Children.Remove(e.OldValue as PlayerBase);
if (e.NewValue != null) {
P.HostGrid.Children.Add(e.NewValue as PlayerBase);
P.UI.PlayerHost = e.NewValue as PlayerBase;
}
}
Then in the code-behind where the class is being used, I set it like this
Player.Host = new MpvMediaPlayerHost();
At least it's working.
EDIT: A better solution is to switch from User Control to Custom Control. I still applied the solution above, and switching to Custom Control allowed me to set the Host in a derived class' constructor.
I'm using MVVM pattern to bind text to TextBlock.
The text is defined in database with <Subscript> tag to define when the text is Subscript. "Some<Subscript>subscript</Subscript>text."
I tried using Unicode subscripts and superscripts, but the characters appears too small, and hard to read.
I couldn't find a direct way to do this. any suggestions?
When you know that there is a subscription Tag you may use several Runs within your TextBlock.
<TextBlock>
<Run />
<Run />
</TextBlock>
I think you do not know the exact position of the subscripted text, right? So, why not just analyze your input and creating a new Run programatically? The Runs with the normal text have another size than Runs with subscripted text.
If you need help with adding Runs programatically just have a look at this StackOverflow post:
How to assign a Run to a text property, programmatically?
I know this is not the best way in MVVM to define XAML controls in your ViewModel, but thats the fastest way to reach better legibility.
Using Attached properties fixed my problem in the most MVVM friendly way.
You get the text and add to the TextBlock Inlines as you want.
Attached property c#:
public static class TextBlockAp {
public static readonly DependencyProperty SubscriptTextProperty = DependencyProperty.RegisterAttached(
"SubscriptText", typeof(string), typeof(TextboxAttachedProperty), new PropertyMetadata(OnSubscriptTextPropertyChanged));
public static string GetSubscriptText(DependencyObject obj) {
return (string)obj.GetValue(SubscriptTextProperty);
}
public static void SetSubscriptText(DependencyObject obj, string value) {
obj.SetValue(SubscriptTextProperty, value);
}
private static void OnSubscriptTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
try {
var value = e.NewValue as string;
if (String.IsNullOrEmpty(value)) return;
var textBlock = (TextBlock)d;
var startTag = "<Subscript>";
var endTag = "</Subscript>";
var subscript = String.Empty;
if (value.Contains(startTag) && value.Contains(endTag)) {
int index = value.IndexOf(startTag) + startTag.Length;
subscript = value.Substring(index, value.IndexOf(endTag) - index);
}
var text = value.Split(new[] { startTag }, StringSplitOptions.None);
textBlock.Inlines.Add(text[0]);
Run run = new Run($" {subscript}") { BaselineAlignment = BaselineAlignment.Subscript, FontSize = 9 };
textBlock.Inlines.Add(run);
} catch (Exception ex) {
if (ExceptionUtilities.UiPolicyException(ex)) throw;
}
}
}
xaml
<TextBlock ap:TextBlockAp.SubscriptText="{Binding MyProperty}" />
It needs more refactoring to work correctly, but it's a start.
When a user is running my program for the first time, I want them to go through a series of tips. Each time they hit a certain "checkpoint", the program will pause what its doing, the background will go a little fuzzy (except for the area of the window the tip is referencing), and a tip will appear on top, explaining how to use it / what to do etc.
I dont quite know what to call this, in my head its called "tutorial tips", but googling anything related to this shows a load of generic tutorials with WPF / C#.
What would be the best way to do this? Am I really just looking at using popups and controling when they are visible? Is there a better / more elegant solution, or any resources out there to aid with this?
Ok, I think I may have dedicated too much time to this, but it sounded like a cool challenge :P
I've created a Decorator class named TipFocusDecorator that handles all this.
public class TipFocusDecorator : Decorator
{
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
// Using a DependencyProperty as the backing store for Open. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(TipFocusDecorator),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IsOpenPropertyChanged));
public string TipText
{
get { return (string)GetValue(TipTextProperty); }
set { SetValue(TipTextProperty, value); }
}
// Using a DependencyProperty as the backing store for TipText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TipTextProperty =
DependencyProperty.Register("TipText", typeof(string), typeof(TipFocusDecorator), new UIPropertyMetadata(string.Empty));
public bool HasBeenShown
{
get { return (bool)GetValue(HasBeenShownProperty); }
set { SetValue(HasBeenShownProperty, value); }
}
// Using a DependencyProperty as the backing store for HasBeenShown. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HasBeenShownProperty =
DependencyProperty.Register("HasBeenShown", typeof(bool), typeof(TipFocusDecorator), new UIPropertyMetadata(false));
private static void IsOpenPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var decorator = sender as TipFocusDecorator;
if ((bool)e.NewValue)
{
if (!decorator.HasBeenShown)
decorator.HasBeenShown = true;
decorator.Open();
}
if (!(bool)e.NewValue)
{
decorator.Close();
}
}
TipFocusAdorner adorner;
protected void Open()
{
adorner = new TipFocusAdorner(this.Child);
var adornerLayer = AdornerLayer.GetAdornerLayer(this.Child);
adornerLayer.Add(adorner);
MessageBox.Show(TipText); // Change for your custom tip Window
IsOpen = false;
}
protected void Close()
{
var adornerLayer = AdornerLayer.GetAdornerLayer(this.Child);
adornerLayer.Remove(adorner);
adorner = null;
}
}
This Decorator must be used in XAML around the control you want to focus. It has three properties: IsOpen, TipText and HasBeenShown. IsOpen must be set to true to make the focus and tip window appear (and is set to false automatically when the tip window is closed). TipText allows you to define the text that must be shown in the tip window. And HasBeenShown keeps track of whether the tip window has been shown, so it only shows once. You can use Bindings for all these properties or set them from code-behind.
To create the focus effect, this class uses another custom Adorner, the TipFocusAdorner:
public class TipFocusAdorner : Adorner
{
public TipFocusAdorner(UIElement adornedElement)
: base(adornedElement)
{
}
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var root = Window.GetWindow(this);
var adornerLayer = AdornerLayer.GetAdornerLayer(AdornedElement);
var presentationSource = PresentationSource.FromVisual(adornerLayer);
Matrix transformToDevice = presentationSource.CompositionTarget.TransformToDevice;
var sizeInPixels = transformToDevice.Transform((Vector)adornerLayer.RenderSize);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)(sizeInPixels.X), (int)(sizeInPixels.Y), 96, 96, PixelFormats.Default);
var oldEffect = root.Effect;
var oldVisibility = AdornedElement.Visibility;
root.Effect = new BlurEffect();
AdornedElement.SetCurrentValue(FrameworkElement.VisibilityProperty, Visibility.Hidden);
rtb.Render(root);
AdornedElement.SetCurrentValue(FrameworkElement.VisibilityProperty, oldVisibility);
root.Effect = oldEffect;
drawingContext.DrawImage(rtb, adornerLayer.TransformToVisual(AdornedElement).TransformBounds(new Rect(adornerLayer.RenderSize)));
drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(22, 0, 0, 0)), null, adornerLayer.TransformToVisual(AdornedElement).TransformBounds(new Rect(adornerLayer.RenderSize)));
drawingContext.DrawRectangle(new VisualBrush(AdornedElement) { AlignmentX = AlignmentX.Left, TileMode = TileMode.None, Stretch = Stretch.None },
null,
AdornedElement.RenderTransform.TransformBounds(new Rect(AdornedElement.RenderSize)));
}
}
This dims and blurs (and freezes, since it actually uses a screen capture) all the window, while keeping the desired controls focused and clear (and moving - i.e. in TextBoxes, the text input caret will still be visible and blinking).
To use this Decorator, you only must set it like this in XAML:
<StackPanel>
<local:TipFocusDecorator x:Name="LoginDecorator"
TipText="Enter your username and password and click 'Login'"
IsOpen="{Binding ShowLoginTip}">
<local:LoginForm />
</local:TipFocusDecorator>
</StackPanel>
And the final result, when ShowLoginTip is set to true:
KNOWN ISSUES
Right now this uses a simple MessageBox to show the tip, but you can create your own Window class for the tips, style it as you want, and call it with ShowDialog() instead of the MessageBox.Show() (and you could also control where the Window appears, if you want it to appear right next to the focused Control or something like that).
Also, this won't work inside UserControls right away, because AdornerLayer.GetAdornerLayer(AdornedElement) will return null inside UserControls. This could be easily fixed by looking for the AdornerLayer of the PARENT of the UserControl (or the parent of the parent, recursively). There are functions around to do so.
This won't work for Pages either, only for Windows. Simply because I use Window.GetWindow(this) to get the parent Window of the Decorator... You could use other functions to get the parent, that could work either with Windows, Pages or whatever. As with the AdornerLayer problem, there are plenty of solutions for this around here.
Also, I guess this could be animated somehow (making the blur and dim effect appear gradually, for instance), but haven't really looked into it...
You can create your tip as a window and show it using ShowDialog(). This gives you a Modal dialog, as others have suggested. Be sure to set it's owner. Just before you show it, you can use
<UIElement.Effect>
<BlurEffect/>
</UIelement.Effect>
to set your window or outer container's(grid maybe) Blur Effect. The radius property sets the "level" of blur, so I imagine you can set it to 0 initially and modify it programatically when you show your dialog
Is it possible to make the whole text area of the RadComboBox clickable while having IsEditable=true and ReadOnly=True?
I would just set IsEditable = false but unfortunately I need it to be editable in order to display custom text when something is selected (I have it set so multiple things can be selected and present a list of the selected items). If I disable IsEditable then I lose the .Text attribute and can't set a custom text.
My two best bets would be:
1) somehow apply a style that makes the whole textbar clickable and not just the arrow
2) somehow apply custom text display when IsEditable is set to false.
Unfortunately I don't know how to do either so any help would be nice. Thanks
Edit: This would be ideal, except that we're using Silverlight and not ASP.net
http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/checkboxes/defaultcs.aspx
This is probably more realistic, just to somehow make the text area clickable so it opens the dropdown menu. Just like the ComboBox on the right, minus being able to type. http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/comboboxvsdropdownlist/defaultcs.aspx
I can think of several solutions, of varying elegance. Here is one that might be suitable to close your remaining gap between the Arrow-ToggleButton and the Text-Input-Area. And now that I think about it... maybe you can get rid of that rather smelly and fragile side-effect-piggybacking with the OpenDropDownOnFocus property (which will break as soon as a click does not change the focus owner).
Register a MouseLeftButtonDown click handler with the RadComboBox, you can choose to get all events, not only unhandled events. Then we can toggle the DropDown from there. But we don't want to interfere with the Arrow-ToggleButton, therefore we check from where the mouse click originated.
public class MyView : UserControl
{
public MyView()
{
InitializeComponent();
MouseButtonEventHandler handler = OnComboBoxClicked;
radComboBox.AddHandler( UIElement.MouseLeftButtonDownEvent, handler,
handledEventsToo: true );
}
private void OnComboBoxClicked( object sender, MouseButtonEventArgs args )
{
if (!args.Handled ||
!args.IsRoutedEventFromToggleButton(
togglebuttonAncestorToStopTheSearch: (UIElement) sender))
{
ToggleDropDown();
}
}
}
and extension methods for easier use:
public static class ControlExtensions
{
public static bool IsRoutedEventFromToggleButton(
this RoutedEventArgs args,
UIElement togglebuttonAncestorToStopTheSearch )
{
ToggleButton toggleButton = ((UIElement) args.OriginalSource)
.GetAncestor<ToggleButton>( togglebuttonAncestorToStopTheSearch );
return toggleButton != null;
}
public static TAncestor GetAncestor<TAncestor>(
this DependencyObject subElement,
UIElement potentialAncestorToStopTheSearch )
where TAncestor : DependencyObject
{
DependencyObject parent;
for (DependencyObject subControl = subElement; subControl != null;
subControl = parent)
{
if (subControl is TAncestor) return (TAncestor) subControl;
if (object.ReferenceEquals( subControl,
potentialAncestorToStopTheSearch )) return null;
parent = VisualTreeHelper.GetParent( subControl );
if (parent == null)
{
FrameworkElement element = subControl as FrameworkElement;
if (element != null)
{
parent = element.Parent;
}
}
}
return null;
}
}
I ended up finding a multiselectcombobox that someone else implemented here:
http://www.telerik.com/support/code-library/a-multiselect-combobox
I didn't need the whole combobox itself since we already had one implemented so I just looked at how the person was displaying a custom message while the combo box IsEditable was set to false.
After looking at that code for a while and seeing how I can make it work for me, I put
<ucControls:RadComboBox.SelectionBoxTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text,ElementName=RadCombo}" />
</DataTemplate>
</ucControls:RadComboBox.SelectionBoxTemplate>
inside the XAML of our own custom MultiSelectComboBox. (RadCombo being the name of the particular control that I wanted the Text to be linked to)
<ucControls:RadComboBox
x:Name="RadCombo"
Text=""
........
<ucControls:RadComboBox.SelectionBoxTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text,ElementName=RadCombo}" />
</DataTemplate>
</ucControls:RadComboBox.SelectionBoxTemplate>
.......
</ucControls:RadComboBox>
Using the built in SelectionBoxTemplate, this basically just added a TextBlock overlay, and the content was bound to the RadComboBox's own Text, so when we would set the Text of the RadComboBox, the TextBlock would update itself.
This was the most effective way for us to do it because it required minimal code changes, and no structure changes since we already had all the code in place for checking boxes and setting a custom text.
Hope this helps someone, best of luck!
I would like to format specific words in TextBlock dynamically, because it is binded to my object. I was thinking about using Converter but using following solution add only tags directly to the text (instead of showing it formatted).
public class TextBlockFormatter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
string regexp = #"\p{L}+#\d{4}";
if (value != null) {
return Regex.Replace(value as string, regexp, m => string.Format("<Bold>{0}</Bold>", m.Value));
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return null;
}
}
This is not an attempt to answer this question... it is a demonstration in response to a question in the question comments.
Yes #Blam, you can format individual words, or even characters in a TextBlock... you need to use the Run (or you could replace Run with TextBlock) class. Either way, you can also data bind to the Text property on either of them:
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
<Run Text="This" FontWeight="Bold" Foreground="Red" />
<Run Text="text" FontSize="18" />
<Run Text="is" FontStyle="Italic" />
<Run Text="in" FontWeight="SemiBold" Background="LightGreen" />
<Run Text="one" FontFamily="Candara" FontSize="20" />
<Run Text="TextBlock" FontWeight="Bold" Foreground="Blue" />
</TextBlock>
UPDATE >>>
Regarding this question now, I suppose that it would be possible to use these Run elements in a DataTemplate and have them generated from the data... the data in this case would have to be classes with (obviously) a Text property, but also formatting properties that you could use to data bind to the Run style properties.
It would be awkward though because the TextBlock has no ItemsSource property that you could bind your collection of word classes to... maybe you could use a Converter for that part... just thinking aloud here... I'm going to stop now.
UPDATE >>>
#KrzysztofBzowski, unfortunately the TextBlock.Inlines property is not a DependencyProperty, so you won't be able to data bind to it. However, it got me thinking and I did another search and found the Binding text containing tags to TextBlock inlines using attached property in Silverlight for Windows Phone 7 article on Jevgeni Tsaikin's .NET laboratory.
It would involve you declaring an Attached Property and a Converter, but it looks promising... give it a go. And don't worry that it's for Silverlight... if it works in Silverlight, then it'll work in WPF.
I recently had to solve this problem which I was able to do by writing a Blend behaviour for TextBlocks.
It can be declared in XAML with a list of Highlight elements where you specify the text to highlight, the colour you want that text to be and it's font weight (can easily add more formatting properties as required).
It works by looping though the desired highlights, scanning the TextBlock for each phrase starting at the TextBlock.ContentStart TextPointer. Once the phrase is found it can build a TextRange which can have the formatting options applied to it.
It should work if the TextBlock Text property is data bound too because I attach to the bindings Target updated event.
See below for the behaviour code and an example in XAML
public class TextBlockHighlightBehaviour : Behavior<TextBlock>
{
private EventHandler<DataTransferEventArgs> targetUpdatedHandler;
public List<Highlight> Highlights { get; set; }
public TextBlockHighlightBehaviour()
{
this.Highlights = new List<Highlight>();
}
#region Behaviour Overrides
protected override void OnAttached()
{
base.OnAttached();
targetUpdatedHandler = new EventHandler<DataTransferEventArgs>(TextBlockBindingUpdated);
Binding.AddTargetUpdatedHandler(this.AssociatedObject, targetUpdatedHandler);
// Run the initial behaviour logic
HighlightTextBlock(this.AssociatedObject);
}
protected override void OnDetaching()
{
base.OnDetaching();
Binding.RemoveTargetUpdatedHandler(this.AssociatedObject, targetUpdatedHandler);
}
#endregion
#region Private Methods
private void TextBlockBindingUpdated(object sender, DataTransferEventArgs e)
{
var textBlock = e.TargetObject as TextBlock;
if (textBlock == null)
return;
if(e.Property.Name == "Text")
HighlightTextBlock(textBlock);
}
private void HighlightTextBlock(TextBlock textBlock)
{
foreach (var highlight in this.Highlights)
{
foreach (var range in FindAllPhrases(textBlock, highlight.Text))
{
if (highlight.Foreground != null)
range.ApplyPropertyValue(TextElement.ForegroundProperty, highlight.Foreground);
if(highlight.FontWeight != null)
range.ApplyPropertyValue(TextElement.FontWeightProperty, highlight.FontWeight);
}
}
}
private List<TextRange> FindAllPhrases(TextBlock textBlock, string phrase)
{
var result = new List<TextRange>();
var position = textBlock.ContentStart;
while (position != null)
{
var range = FindPhrase(position, phrase);
if (range != null)
{
result.Add(range);
position = range.End;
}
else
position = null;
}
return result;
}
// This method will search for a specified phrase (string) starting at a specified position.
private TextRange FindPhrase(TextPointer position, string phrase)
{
while (position != null)
{
if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
string textRun = position.GetTextInRun(LogicalDirection.Forward);
// Find the starting index of any substring that matches "phrase".
int indexInRun = textRun.IndexOf(phrase);
if (indexInRun >= 0)
{
TextPointer start = position.GetPositionAtOffset(indexInRun);
TextPointer end = start.GetPositionAtOffset(phrase.Length);
return new TextRange(start, end);
}
}
position = position.GetNextContextPosition(LogicalDirection.Forward);
}
// position will be null if "phrase" is not found.
return null;
}
#endregion
}
public class Highlight
{
public string Text { get; set; }
public Brush Foreground { get; set; }
public FontWeight FontWeight { get; set; }
}
Example usage in XAML:
<TextBlock Text="Here is some text">
<i:Interaction.Behaviors>
<behaviours:TextBlockHighlightBehaviour>
<behaviours:TextBlockHighlightBehaviour.Highlights>
<behaviours:Highlight Text="some" Foreground="{StaticResource GreenBrush}" FontWeight="Bold" />
</behaviours:TextBlockHighlightBehaviour.Highlights>
</behaviours:TextBlockHighlightBehaviour>
</i:Interaction.Behaviors>
</TextBlock>
You'll need to import the Blend interactivity namespace and your behaviour's namespace:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviours="clr-namespace:YourProject.Behviours"
I had a similar use case where I needed to build a text editor with RichTextBox. However, all text formatted changes: Font, Color, Italic, Bold must reflect on the TextBlock dynamically. I found few articles pointing me to Textblock.inlines.Add() which seemed helpful but only allow one change at a time or appending to the existing text.
However, Textblock.inlines.ElementAt(index of the existing text to format) can be utilized to apply the desired text format to the text located at that index. Below is my pseudo approach to resolving this issue. I hope this helps:
For RichTextBox:
selectedText.ApplyPropertyValue(TextElement.FontFamilyProperty, cbFontFamily.SelectedValue.ToString());
However, for the Textblock formatting to work I had to use Run run = new Run() concept which allows and works with:
Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(SelectedColorText))
FontFamily = new FontFamily(cbFontFamily.SelectedValue.ToString())
FontStyle = FontStyles.Italic
TextDecorations = TextDecorations.Underline
TextDecorations = TextDecorations.Strikethrough
FontWeight = (FontWeight)new FontWeightConverter().ConvertFromString(cbFontWeightBox.SelectedItem.ToString())
Additionally, I created a class with various fields and constructors. I also created a custom based class dictionary to capture all the changes made in the RichTextbbox. Finally, I applied all the captured information in the dictionary via a forloop.
TextBlock.Inlines.ElementAt(mItemIndex).Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(dictionaryItem.Value._applyColor.ToString()));
TextBlock.Inlines.ElementAt(mItemIndex).FontFamily = new FontFamily(ftItem.Value._applyFontFamily.ToString());
TextBlock.Inlines.ElementAt(mItemIndex).FontStyle = FontStyles.Italic;
TextBlock.Inlines.ElementAt(mItemIndex).TextDecorations = TextDecorations.Underline;
TextBlock.Inlines.ElementAt(mItemIndex).TextDecorations = TextDecorations.Strikethrough;