Databinding TextBlock Runs in Silverlight / WP7 - silverlight

I'm using Silverlight on Windows Phone 7.
I want to display the first part of some text in a TextBlock in bold, and the rest in normal font. The complete text must wrap. I want the bolded part to contain text from one property in my ViewModel, and the plain text to contain text from a different property.
The TextBlock is defined in a DataTemplate associated with a LongListSelector.
My initial attempt was:
<TextBlock TextWrapping="Wrap">
<TextBlock.Inlines>
<Run Text="{Binding Property1}" FontWeight="Bold"/>
<Run Text="{Binding Property2}"/>
</TextBlock.Inlines>
</TextBlock>
This fails at runtime with the spectacularly unhelpful "AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR". This is a known issue because the Run element is not a FrameworkElement and cannot be bound.
My next attempt was to put placeholders in place, and then update them in code:
<TextBlock Loaded="TextBlockLoaded" TextWrapping="Wrap">
<TextBlock.Inlines>
<Run FontWeight="Bold">Placeholder1</Run>
<Run>Placeholder2</Run>
</TextBlock.Inlines>
</TextBlock>
In the code-behind (yes I am desparate!):
private void TextBlockLoaded(object sender, RoutedEventArgs e)
{
var textBlock = (TextBlock)sender;
var viewModel = (ViewModel)textBlock.DataContext;
var prop1Run = (Run)textBlock.Inlines[0];
var prop2Run = (Run)textBlock.Inlines[1];
prop1Run.Text = viewModel.Property1;
prop2Run.Text = viewModel.Property2;
}
This seemed to work, but because I am using the LongListSelector, although items get recycled, the Loaded codebehind event handler doesn't re-initialize the Runs, so very quickly the wrong text is displayed...
I've looked at using the LongListSelector's Linked event (which I already use to free up images that I display in the list), but I can't see how I can use that to re-initialize the Runs' text properties.
Any help appreciated!

I finally found a solution that works for me.
As I mention in the comment, Paul Stovell's approach would not work.
Instead I used a similar approach to add an attached property to the TextBlock, bound to the TextBlock's DataContext, and attached properties on the runs, indicating which ViewModel properties they should be bound to:
<TextBlock TextWrapping="Wrap"
Views:BindableRuns.Target="{Binding}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Views:BindableRuns.Target="Property1"/>
<Run Views:BindableRuns.Target="Property2"/>
</TextBlock.Inlines>
</TextBlock>
Then in my attached TextBox Target (datacontext) property's changed event, I update the Runs, and subscribe to be notified of changes to the TextBox Target properties. When a TextBox Target property changes, I updated any associated Run's text accordingly.
public static class BindableRuns
{
private static readonly Dictionary<INotifyPropertyChanged, PropertyChangedHandler>
Handlers = new Dictionary<INotifyPropertyChanged, PropertyChangedHandler>();
private static void TargetPropertyPropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
if(!(dependencyObject is TextBlock)) return;
var textBlock = (TextBlock)dependencyObject;
AddHandler(e.NewValue as INotifyPropertyChanged, textBlock);
RemoveHandler(e.OldValue as INotifyPropertyChanged);
InitializeRuns(textBlock, e.NewValue);
}
private static void AddHandler(INotifyPropertyChanged dataContext,
TextBlock textBlock)
{
if (dataContext == null) return;
var propertyChangedHandler = new PropertyChangedHandler(textBlock);
dataContext.PropertyChanged += propertyChangedHandler.PropertyChanged;
Handlers[dataContext] = propertyChangedHandler;
}
private static void RemoveHandler(INotifyPropertyChanged dataContext)
{
if (dataContext == null || !Handlers.ContainsKey(dataContext)) return;
dataContext.PropertyChanged -= Handlers[dataContext].PropertyChanged;
Handlers.Remove(dataContext);
}
private static void InitializeRuns(TextBlock textBlock, object dataContext)
{
if (dataContext == null) return;
var runs = from run in textBlock.Inlines.OfType<Run>()
let propertyName = (string)run.GetValue(TargetProperty)
where propertyName != null
select new { Run = run, PropertyName = propertyName };
foreach (var run in runs)
{
var property = dataContext.GetType().GetProperty(run.PropertyName);
run.Run.Text = (string)property.GetValue(dataContext, null);
}
}
private class PropertyChangedHandler
{
private readonly TextBlock _textBlock;
public PropertyChangedHandler(TextBlock textBlock)
{
_textBlock = textBlock;
}
public void PropertyChanged(object sender,
PropertyChangedEventArgs propertyChangedArgs)
{
var propertyName = propertyChangedArgs.PropertyName;
var run = _textBlock.Inlines.OfType<Run>()
.Where(r => (string) r.GetValue(TargetProperty) == propertyName)
.SingleOrDefault();
if(run == null) return;
var property = sender.GetType().GetProperty(propertyName);
run.Text = (string)property.GetValue(sender, null);
}
}
public static object GetTarget(DependencyObject obj)
{
return obj.GetValue(TargetProperty);
}
public static void SetTarget(DependencyObject obj,
object value)
{
obj.SetValue(TargetProperty, value);
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.RegisterAttached("Target",
typeof(object),
typeof(BindableRuns),
new PropertyMetadata(null,
TargetPropertyPropertyChanged));
}

I suggest you give the BindableRun a try. I've only used it in WPF, but I don't see why it wouldn't work in Silverlight.

Related

Set the content of a TextBlock

Say I have a TextBlock that looks like this:
<TextBlock TextWrapping="Wrap" >
Text in <Run Foreground="Red">red</Run> and <Run Foreground="Red">more</Run>
</TextBlock>
That works fine and the text is shown in red as you think it would. However, in my real example this text:
Text in <Run Foreground="Red">red</Run> and <Run Foreground="Red">more</Run>
Comes from a service call. (Including the tags.)
Is there a way to supply this text to the TextBlock at runtime?
In fact you have to convert all the input text into Inlines collection (of type TextElementCollection). However doing so requires parsing the input text yourself which is not easy. So why not use the existing parser supported by XamlReader. By using this class, you can parse a XAML code to get an instance of a TextBlock from which you can get the Inlines collection and add that for your actual TextBlock. Here is the code:
var text = "<TextBlock>Text in <Run Foreground=\"Red\">red</Run> and <Run Foreground=\"Red\">more</Run></TextBlock>";
var pc = new System.Windows.Markup.ParserContext();
pc.XmlnsDictionary[""] = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
pc.XmlnsDictionary["x"] = "http://schemas.microsoft.com/winfx/2006/xaml";
var tbl = System.Windows.Markup.XamlReader.Parse(text, pc) as TextBlock;
yourTextBlock.Inlines.Clear();
yourTextBlock.Inlines.AddRange(tbl.Inlines.ToList());
Your actual input text should be wrapped in the pair <TextBlock> & </TextBlock> before being used by the next code.
I thought I would post the control that I made from King Kong's answer. (Just in case it is useful to someone else.)
public class FormatBindableTextBlock : TextBlock
{
private static readonly ParserContext parserContext;
static FormatBindableTextBlock()
{
parserContext = new System.Windows.Markup.ParserContext();
parserContext.XmlnsDictionary[""] = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
parserContext.XmlnsDictionary["x"] = "http://schemas.microsoft.com/winfx/2006/xaml";
}
public static readonly DependencyProperty FormattedContentProperty =
DependencyProperty.Register("FormattedContent", typeof(string), typeof(FormatBindableTextBlock), new UIPropertyMetadata(null, OnFormattedContentPropertyChanged));
public string FormattedContent
{
get { return (string)GetValue(FormattedContentProperty); }
set { SetValue(FormattedContentProperty, value); }
}
private static void OnFormattedContentPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
FormatBindableTextBlock textBlock = sender as FormatBindableTextBlock;
if (textBlock == null)
return;
string newFormattedContent = e.NewValue as string;
if (string.IsNullOrEmpty(newFormattedContent))
newFormattedContent = "";
newFormattedContent = "<TextBlock>" + newFormattedContent + "</TextBlock>";
TextBlock tbl = System.Windows.Markup.XamlReader.Parse(newFormattedContent, parserContext) as TextBlock;
textBlock.Inlines.Clear();
textBlock.Inlines.AddRange(tbl.Inlines.ToList());
}
}

Silverlight TextBox VisibilityChanged event

I'm working on a Windows Phone app using Silverlight C# and XAML. My page contains a ListBox which renders a list of databound objects that the user can manipulate, i.e. add/rename/delete.
I've got it working that the add/rename of items is done in-place, i.e. by swapping a TextBlock for a TextBox depending on the state of the object (bool IsEditable property) and making use of a parameterized VisibilityConverter to manage the opposite Visibility states.
<UserControl.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter" True="Visible" False="Collapsed"/>
<local:VisibilityConverter x:Key="InvertedVisibility" True="Collapsed" False="Visible"/>
</UserControl.Resources>
...
<TextBlock Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource InvertedVisibility}}" />
<TextBox Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource VisibilityConverter}}"/>
The thing is that I also want the TextBox to automatically grab focus when it becomes visible, so that the on-screen keyboard pops up without the user having to tap the TextBox.
Since there's no VisibilityChanged event on a regular TextBox, I subclassed TextBox to TextBox2 and added my own:
public class TextBox2 : TextBox
{
public TextBox2()
{
DefaultStyleKey = typeof(TextBox);
}
public static readonly DependencyProperty VisibilityChangedProperty = DependencyProperty.Register(
"VisibilityChanged",
typeof(string),
typeof(TextBox2),
new PropertyMetadata("Set the VisibilityChanged event handler"));
public event VisibilityChangedEventHandler VisibilityChanged;
public delegate void VisibilityChangedEventHandler(object sender, EventArgs e);
public new Visibility Visibility
{
get
{
return base.Visibility;
}
set
{
if (base.Visibility != value)
{
base.Visibility = value;
VisibilityChanged(this, new EventArgs());
}
}
}
}
Now my XAML looks like this:
<TextBlock Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource InvertedVisibility}}"/>
<local:TextBox2 Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource VisibilityConverter}}" VisibilityChanged="ListEdit_VisibilityChanged"/>
And the event handler like this:
void ListEdit_VisibilityChanged(object sender, EventArgs e)
{
TextBox textBox = (TextBox)sender;
if (textBox.Visibility == System.Windows.Visibility.Collapsed)
return;
textBox.Focus();
}
The TextBox2 renders properly and behaves just like a TextBox at runtime, but my VisibilityChanged event handler is not firing when the databinding flips the value of IsEditable.
IsEditable defines the Visibility and the TextBox2 does become visible correctly, so the databinding is working.
I can cause the event to fire programmatically by getting hold of the TextBox2 instance and setting the Visibility of that in code. That also works.
But this databinding scenario being responsible for setting the Visibility seems not to work.
Any ideas why not?
Here are 2 solutions that I use.
Solution 1 needs no sub class, but solution 2 is more reusable.
1. You can subscribe to the Loaded event of the TextBox, and force a focus, like so:
void TextBox_Loaded_Focus(object sender, System.Windows.RoutedEventArgs e) {
ForceFocusControl((Control)sender);
}
void ForceFocusControl(Control control) {
control.Focus();
if (FocusManager.GetFocusedElement() != control) {
Dispatcher.BeginInvoke(() => ForceFocusControl(control));
}
}
This solution goes into a recursive loop though, you might want to add some checks to make it safer.
2. Keep your subclass TextBox2, and rather create a private MyVisibility dependency property that you bind to the Visibility property of the base class, but also specify a DependencyProperty_Changed handler, like so:
/// <summary>
/// <see cref="TextBox2"/> will focus itself when it becomes visible.
/// </summary>
public sealed class TextBox2 : TextBox {
public TextBox2() {
SetBinding(TextBox2.MyVisibilityProperty, new Binding("Visibility") { Source = this });
}
static readonly DependencyProperty MyVisibilityProperty = DependencyProperty.Register(
/* name = */ "MyVisibilityProperty",
/* property type = */ typeof(Visibility),
/* owner type = */ typeof(TextBox2),
/* meta = */ new PropertyMetadata(MyVisibilityProperty_Changed));
static void MyVisibilityProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
TextBox2 TextBox2 = (TextBox2)d;
if (TextBox2.Visibility == Visibility.Visible) {
TextBox2.Focus();
}
}
}
This is how my TextBox2 class looks now:
public class TextBox2 : TextBox
{
public event VisibilityChangedEventHandler VisibilityChanged;
public delegate void VisibilityChangedEventHandler(object sender, EventArgs e);
public static readonly DependencyProperty VisibilityChangedProperty = DependencyProperty.Register(
"VisibilityChanged", typeof(VisibilityChangedEventHandler), typeof(TextBox2), null);
static readonly DependencyProperty MirrorVisibilityProperty = DependencyProperty.Register(
"MirrorVisibility", typeof(Visibility), typeof(TextBox2), new PropertyMetadata(MirrorVisibilityChanged));
public TextBox2()
{
SetBinding(TextBox2.MirrorVisibilityProperty, new Binding("Visibility") { Source = this });
}
static void MirrorVisibilityChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((TextBox2)obj).VisibilityChanged(obj, null); // raise event
}
}

Mixed font types in resx file for localization

I am doing some localization and have bumped into a problem that I cant seem to figure out.
I need the following displayed: tcpCO₂ (where tcp is in italic)
<Italic>tcp</Italic> CO₂
I thought I could just put the HTML in my .resx file and all would be marry, but the content of the output shows the html including brackets etc. Does anyone have inputs in this matter?
I use an attached behavior to display rich XAML text in a TextBlock:
public static class TextBlockBehavior
{
[AttachedPropertyBrowsableForType(typeof(TextBlock))]
public static string GetRichText(TextBlock textBlock)
{
return (string)textBlock.GetValue(RichTextProperty);
}
public static void SetRichText(TextBlock textBlock, string value)
{
textBlock.SetValue(RichTextProperty, value);
}
public static readonly DependencyProperty RichTextProperty =
DependencyProperty.RegisterAttached(
"RichText",
typeof(string),
typeof(TextBlockBehavior),
new UIPropertyMetadata(
null,
RichTextChanged));
private static void RichTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = o as TextBlock;
if (textBlock == null)
return;
var newValue = (string)e.NewValue;
textBlock.Inlines.Clear();
if (newValue != null)
AddRichText(textBlock, newValue);
}
private static void AddRichText(TextBlock textBlock, string richText)
{
string xaml = string.Format(#"<Span>{0}</Span>", richText);
ParserContext context = new ParserContext();
context.XmlnsDictionary.Add(string.Empty, "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
var content = (Span)XamlReader.Parse(xaml, context);
textBlock.Inlines.Add(content);
}
}
You can use it like this:
<TextBlock bhv:TextBlockBehavior.RichText="{x:Static Resources:Resource.CO2}">
However, in your case don't have access to the TextBlock directly, since it's in the caption template of the LayoutPanel. So you need to redefine this template to apply the behavior on the TextBlock:
<dxdo:LayoutPanel Name="PanelCo2" Caption="{x:Static Resources:Resource.CO2}">
<dxdo:LayoutPanel.CaptionTemplate>
<DataTemplate>
<TextBlock bhv:TextBlockBehavior.RichText="{Binding}">
</DataTemplate>
</dxdo:LayoutPanel.CaptionTemplate>
</dxdo:LayoutPanel>

WPF- Problem with TextBox in DataTemplate

I am working on an application where Repository objects are displayed via a DataTemplate that contains a modified version of a TextBox, which supports binding to the SelectionStart, SelectionLength, and VerticalOffset.
The DataTemplate looks like this:
<DataTemplate DataType="{x:Type m:Repository}">
<controls:ModdedTextBox
x:Name="textBox" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
BindableSelectionStart="{Binding SelectionStart, UpdateSourceTrigger=PropertyChanged}"
BindableSelectionLength="{Binding SelectionLength, UpdateSourceTrigger=PropertyChanged}"
BindableVerticalOffset="{Binding VerticalOffset, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
The problem is that when I change the Repositorythat is currently being displayed; the SelectionStart, SelectionLength, and VerticalOffset all seem to be getting set to 0, even when those properties of the Repository object are not 0.
I think that this is happening in the instant before the text is displayed when the SelectionStart, SelectionLength, and VerticalOffset can not be more than 0. This does not only set the actual properties of the TextBox to zero, but also updates the bindings and sets the properties of the Repository object to zero.
Is there any way that I can prevent this from happening?
--Edit--
I don't know if posting dl links to projects is a no-no or not on SO, but here is a link to a project I created to demonstrate the problem I am having: http://dl.dropbox.com/u/1520079/RepositoryProblemDemo.zip
When you run the demo-app the selection you can click the "Switch Repository" button to change the repository that is displayed in the textbox. If you look to the right of the textbox the current repository's properties all get set to zero when you switch to the other one.
A difference between this demo and my actual app is that in my app repositories will be switched via hotkeys, not a button.
The problem is due to the fact that the bindings are evaluated in serial, and when the Text property is changed it causes all selection information to be removed (you can see this by putting breakpoints on your ModdedTextBox event handlers). As the BindableSelection... bindings are still active at that point, it causes the selection information to be reset.
Depending on the exact behaviour you want there is probably a way to work around this, but you would need to know a little more detail...
Edit in response to comments:
This solution isn't exactly answering your original question, and it probably isn't great practice, but it does at least work...
Try altering your ModdedTextBox so that instead of exposing bindable properties for the selection information, expose a single DP of type Repository and bind to that:
<local:ModdedTextBox
x:Name="textBox"
Repository="{Binding CurrentRepository}"
TextWrapping="Wrap"
/>
Then handle the changed event on your DP to set the text box properties:
public static DependencyProperty RepositoryProperty =
DependencyProperty.Register("Repository",
typeof(Repository), typeof(ModdedTextBox), new PropertyMetadata(null, OnRepositoryChanged));
public Repository Repository
{
get { return (Repository)base.GetValue(RepositoryProperty); }
set { base.SetValue(RepositoryProperty, value); }
}
private static void OnRepositoryChanged(DependencyObject senderObject, DependencyPropertyChangedEventArgs e)
{
var sender = (ModdedTextBox)senderObject;
var oldRepository = e.OldValue as Repository;
var newRepository = e.NewValue as Repository;
if (oldRepository != null)
{
oldRepository.Text = sender.Text;
oldRepository.SelectionStart = sender.SelectionStart;
//etc
}
if (newRepository != null)
{
sender.Text = newRepository.Text;
sender.SelectionStart = newRepository.SelectionStart;
//etc
}
}
This is essentially removing the serial nature of the binding evaluation.
Note: You could also achieve the same using attached properties, which would be better than subclassing TextBox, but this is closer to your original attempts so I figure its easier to explain!
Here is a re-write of the other solution. This one takes into account the text property not being bound before the other properties.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public class SelectionBindingTextBox : TextBox
{
public static readonly DependencyProperty BindableSelectionStartProperty =
DependencyProperty.Register(
"BindableSelectionStart",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionStartChanged));
public static readonly DependencyProperty BindableSelectionLengthProperty =
DependencyProperty.Register(
"BindableSelectionLength",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionLengthChanged));
private bool isBindingComplete = false;
public SelectionBindingTextBox()
: base()
{
this.SelectionChanged += this.OnSelectionChanged;
this.TextChanged += this.OnTextChanged;
}
public int BindableSelectionStart
{
get
{
return (int)this.GetValue(BindableSelectionStartProperty);
}
set
{
this.SetValue(BindableSelectionStartProperty, value);
}
}
public int BindableSelectionLength
{
get
{
return (int)this.GetValue(BindableSelectionLengthProperty);
}
set
{
this.SetValue(BindableSelectionLengthProperty, value);
}
}
private static void OnBindableSelectionStartChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (textBox.isBindingComplete)
{
textBox.SetupSelection();
}
}
private static void OnBindableSelectionLengthChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (textBox.isBindingComplete)
{
textBox.SetupSelection();
}
}
private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
if (isBindingComplete)
{
this.BindableSelectionStart = this.SelectionStart;
this.BindableSelectionLength = this.SelectionLength;
}
}
private void OnTextChanged(object sender, RoutedEventArgs e)
{
if (!isBindingComplete)
{
SetupSelection();
}
isBindingComplete = true;
}
private void SetupSelection()
{
// this.Focus();
this.SelectionLength = this.BindableSelectionLength;
this.SelectionStart = this.BindableSelectionStart;
}
}
}
Well update of binding depends on the order in which WPF or Silverlight Engine will evaluate, looks like your SelectionStart and SelectionEnd bindings are updated before Text, so when Text gets changed, SelectionStart and SelectionEnd are both changed back to zero.
The only way is to hook for TextChanged event and refresh the bindings of SelectionStart and SelectionEnd or in WPF you can extend textbox as follow
public class MyTextBox : TextBox{
protected override OnTextChanged(TextChangedEventArgs e){
BindingExpression be = this.GetBindingExpression(SelectionStartProperty);
if(be!=null){
be.UpdateTarget();
}
be = this.GetBindingExpression(SelectionEndProperty);
if(be!=null){
be.UpdateTarget();
}
be = this.GetBindingExpression(VerticalOffsetProperty);
if(be!=null){
be.UpdateTarget();
}
}
}
Well here there is a trick, you still have to change above logic to fit in your logic because everytime text updates this will update binding, so you have to find out when to refresh these bindings. Because this will consistantly fail to change your textbox's value in runtime as text will modify and selection will goto previous selection only.

WPF Data Binding with StringFormat when UpdateSourceTrigger is PropertyChanged

I want to have a textbox append a specific string once the user tabs out of the control, i.e. LostFocus, however I prefer the textbox to validate as the user types, so UpdateSourceTrigger is set to PropertyChanged.
Is there a way to get this to work in WPF?
Looked at this question which is similar but wondering if there is a cleaner solution?
My XAML is:
<TextBox Text="{Binding Path=MyBindingPath,
StringFormat='\{0} -HELLO',
TargetNullValue={x:Static sys:String.Empty},
ValidatesOnDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged}"/>
You can set UpdateSourceTrigger to Explicit and in the TextChanged event handler of the TextBox you can call UpdateSource explicitly after performing the things you want.
//write the code you want to run first and then the following code
BindingExpression exp = this.textBox1.GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();
This seems to be the cleanest approach I have come up with.
This essentially sets events that will turn off the string formatting when the user has focus on the textbox, and restores the stringformat when the user leaves the textbox.
<TextBox Text="{Binding ValueToBind, UpdateSourceTrigger=PropertyChanged}" utilities:Formatting.StringFormat="N2" />
public class Formatting
{
public static readonly DependencyProperty StringFormatProperty = DependencyProperty.RegisterAttached("StringFormat", typeof(string), typeof(Formatting), new PropertyMetadata("", OnStringFormatPropertyChanged));
private static void OnStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FrameworkElement element && e.OldValue != e.NewValue)
{
element.Loaded += (sender, args) =>
{
// Debug.Print("Loaded");
UpdateStringFormat(element.IsFocused ? null : (string)e.NewValue, element);
};
element.LostFocus += (sender, args) =>
{
// Debug.Print("Lost Focus");
UpdateStringFormat((string)e.NewValue, element);
};
element.GotFocus += (sender, args) =>
{
// Debug.Print("Got Focus");
UpdateStringFormat(null, element);
};
}
}
private static void UpdateStringFormat(string stringFormat, FrameworkElement element)
{
var bindingExpression = element.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null && bindingExpression.ParentBinding != null)
{
Binding parentBinding = bindingExpression.ParentBinding;
if (parentBinding.StringFormat == stringFormat)
return;
// Debug.Print("Updating String Format");
bindingExpression.UpdateSource();
Binding newBinding = new Binding
{
Path = parentBinding.Path,
Mode = parentBinding.Mode,
UpdateSourceTrigger = parentBinding.UpdateSourceTrigger,
StringFormat = stringFormat
};
element.SetBinding(TextBox.TextProperty, newBinding);
}
}
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static void SetStringFormat(DependencyObject obj, string stringFormat)
{
obj.SetValue(StringFormatProperty, stringFormat);
}
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static string GetStringFormat(DependencyObject obj)
{
return (string)obj.GetValue(StringFormatProperty);
}
}

Resources