How can I build a Binding Markup Extension that translates text? - wpf

I am rewriting a Delphi 7 application in WPF. One requirement is that all labels, headings, messages etc. are translatable to another language. I am required to use the existing in-house translation engine, versus .NET i18n using resources.
I would very much like to extend the Binding markup extension to be able to specify the language the resultant string is in. This will only be for one way binding, e.g. a simple {"Binding NameLabel Lang=Twi"} so the label for the Name field is displayed in Twi.
I'm sure I can inherit from the binding object, and override some method to call the translation service (through Service Locator} just before it delivers the value asked for from the data context.

You can derived from Binding but you can't override the behaviour to change the returned value i.e. you can't intercept the code in between because ProvideValue method is marked as sealed in BindingBase class so you can't override it and change in derived class.
Definition in BindingBase:
public override sealed object ProvideValue(IServiceProvider serviceProvider);
But in case you interested in custom markup extension which will return i18n string, you can do this way:
public class CustomBinding : MarkupExtension
{
public string Lang { get; set; }
public static object GetValue(DependencyObject obj)
{
return obj.GetValue(ValueProperty);
}
public static void SetValue(DependencyObject obj, Binding value)
{
obj.SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.RegisterAttached("Value", typeof(object),
typeof(CustomBinding));
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget pvt = serviceProvider as IProvideValueTarget;
DependencyObject targetObject = pvt.TargetObject as DependencyObject;
if (targetObject == null)
{
return String.Empty;
}
string value = GetValue(targetObject).ToString();
// You get the bound value here. Put i18n code here
// and return actual value. (Access Lang property to get the culture)
return value;
}
}
As you can see i have declare attached property instead of normal DP because for that you have to derive from DependencyObject but we are already deriving from MarkupExtension so can't do multiple inheritance. Have to settle with attached property to provide binding support from XAML.
Usage in XAML:
<TextBlock local:CustomBinding.Value="{Binding PropertyName}"
Text="{local:CustomBinding Lang=Twi}"/>
Of course you have to define local namespace in XAML.

Related

IMarkupExtension with DependencyProperties

I'm trying to create a custom markup extension using IMarkupExtension<T> that has some DependencyProperties for binding. However, I am struggling to resolve the problem of the markup extension being resolved at XAML parse time, and the bindings only later. I don't seem to ever get something through the bindings: they're always null and never call their change callback.
The docs mention something about returning the instance of the markup extension (under "Returning the Current Markup Extensions Instance"), but that seems to make stuff explode because it's the wrong type for the target. This SL5 MultiBinding seems to return a proxy binding to an internal source object, but I can't manage to get that working: my bindings still don't ever set.
I can't seem to find any solid information how how to actually implement markup extensions with DependencyProperties (even though it seemed like something a lot of people were excited about with SL5...). Can anyone offer any guidance or tutorials?
Specifically, what I'm trying to do is create a markup extension that can dynamically construct a path to do a binding to a list, like so:
{my:ListLookup ListPath='List' Index={Binding Index}}
I'm wanting it to basically output a Binding that would look like {Binding List[Index]}, where Index is dynamic. The purpose of doing this over, say, a MultiBinding on the list and index, is so that we are binding directly to the object and get change notifications. (If there's a better way of doing this...)
I've fiddled with this a lot more and I've found the solution. It's based on the implementation of the SL5 MultiBinding that I linked to in the question.
The trick is that a Binding on a MarkupExtension will never be evaluated because it doesn't have a DataContext or something, but if you take the BindingExpression from it and throw it into a proxy Attached Property (attached to the target object) then you can get the Binding to resolve.
Below is a simple MarkupExtension that demonstrates this. All it's doing is taking a single Binding and outputting its value (obeying changes appropriately), but it shows how it holds together. This can be extended to solve the dictionary issue I was talking about, along with this problem in general.
public class SimpleBindingMarkupExtension : DependencyObject, IMarkupExtension<object>, INotifyPropertyChanged
{
public object Binding
{
get { return (object)GetValue(BindingProperty); }
set { SetValue(BindingProperty, value); }
}
public static readonly DependencyProperty BindingProperty =
DependencyProperty.Register(
"Binding",
typeof(object),
typeof(SimpleBindingMarkupExtension),
new PropertyMetadata(null));
public static readonly DependencyProperty ProxyAttachedBindingProperty =
DependencyProperty.RegisterAttached(
"ProxyAttachedBinding",
typeof(object),
typeof(SimpleBindingMarkupExtension),
new PropertyMetadata(null, OnProxyAttachedBindingChanged));
public static readonly DependencyProperty AttachedMarkupExtensionProperty =
DependencyProperty.RegisterAttached(
"AttachedMarkupExtension",
typeof(SimpleBindingMarkupExtension),
typeof(SimpleBindingMarkupExtension),
new PropertyMetadata(null));
private object _bindingSource;
public object BindingSource
{
get { return _bindingSource; }
set
{
_bindingSource = value;
OnPropertyChanged("BindingSource");
}
}
private static void OnProxyAttachedBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Pull the MarkupExtension from the attached property
var markupExtension = (SimpleBindingMarkupExtension) d.GetValue(AttachedMarkupExtensionProperty);
markupExtension.ProxyAttachedBindingChanged(e.NewValue);
}
private void ProxyAttachedBindingChanged(object value)
{
BindingSource = value;
}
public object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = (IProvideValueTarget) serviceProvider.GetService(typeof (IProvideValueTarget));
DependencyObject targetObject = target.TargetObject as DependencyObject;
if (targetObject == null)
return null;
// Attach this MarkupExtension to the object so we can find it again from attached property change callbacks
targetObject.SetValue(AttachedMarkupExtensionProperty, this);
// Put binding onto proxy attached property, so it actually evaluates
var localValue = ReadLocalValue(BindingProperty);
var bindingExpression = localValue as BindingExpression;
if (bindingExpression == null)
{
return localValue;
}
Binding originalBinding = bindingExpression.ParentBinding;
BindingOperations.SetBinding(targetObject, ProxyAttachedBindingProperty, originalBinding);
// Give the target a proxy Binding that binds to a property on the MarkupExtension
Binding binding = new Binding
{
Path = new PropertyPath("BindingSource"),
Source = this
};
return binding.ProvideValue(serviceProvider);
}
#region INotifyPropertyChanged
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Usage:
<TextBlock Text="{local:SimpleBindingMarkupExtension Binding={Binding Text}}"/>
As mentioned, this example will produce the same result as just saying Text="{Binding Text}", but shows the solution.

MarkupExtension with binding parameters

I am working on a custom MarkupExtension in which I need a non string parameters from XAML to construct the new object. Is it possible to use a non-string parameter binding on a field in DataContext scope?
In other words, how can I do something like this?
<ListBox ItemsSource="{Binding Source={local:MyMarkupExtension {x:Type Button},IncludeMethods={Binding Source=CustomerObject.IsProblematic}}}" />
where IncludeMethods=CustomerObject.IsProblematic give me this error:
Binding cannot be set on the 'IncludeMethods' property of type 'TypeDescriptorExtension'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Can anyone help me?
thanks
A 'Binding' can only be set on a DependencyProperty of a DependencyObject - it is true. The problem is that MarkupExtension class does not derive from DependencyObject, that's why it is not possible to set binding on it's properties.
[EDIT]
Workaround is using ValueConverters. Another workaround is to change C# language to allow multiple inheritance. By the way, in Silverlight MarkupExtension implements IMarkupExtension interface, so I tried to implement it in my custom extension and derive it from DependecyObject, added DependencyProperty there and set binding to it. It doesn't crash, but the binding is actually set after ProvideValue() is called. So even in Silverlight there's no solution (or it is difficult - see link provided in Klaus78's answer). In WPF MarkupExtension doesn't implement any interface, so you cannot bind to it's properties.
So as other have said, please first consider using a ValueConverter. This is the proper approach for manipulating bindings.
If however, you still want to use a MarkupExtension with bindings to the view-model or data context then you can create the binding manually in the markup extension class. This is similar to the approach taken by #nicolay.anykienko but we don't need to create an attached property.
As an example, I have created a currency symbol markup extension. The default behaviour is to use the CultureInfo.CurrentCulture but a few view-models have their own CultureInfo property that are different from the current culture. So for these view-models the XAML needs to bind to this property. Note that this could easily be done with a Converter instead, but for the sake of an example here is the markup extension:
public class CurrencySymbolExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
var targetProvider = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetElement = targetProvider.TargetObject as FrameworkElement;
var targetProperty = targetProvider.TargetProperty as DependencyProperty;
if (!String.IsNullOrEmpty(CultureBindingPath) &&
targetElement != null &&
targetProperty != null)
{
// make sure that if the binding context changes then the binding gets updated.
targetElement.DataContextChanged +=
(sender, args) => ApplyBinding(targetElement, targetProperty, args.NewValue);
// apply a binding to the target
var binding = ApplyBinding(targetElement, targetProperty, targetElement.DataContext);
// return the initial value of the property
return binding.ProvideValue(serviceProvider);
}
else
{
// if no culture binding is provided then use the current culture
return CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol;
}
}
private Binding ApplyBinding(DependencyObject target, DependencyProperty property, object source)
{
BindingOperations.ClearBinding(target, property);
var binding = new Binding(CultureBindingPath + ".NumberFormat.CurrencySymbol")
{
Mode = BindingMode.OneWay,
Source = source,
FallbackValue = CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol,
};
BindingOperations.SetBinding(target, property, binding);
return binding;
}
public string CultureBindingPath { get; set; }
}
This then gets used as follows:
<!-- Standard Usage -->
<TextBlock Text="{local:CurrencySymbol}"/>
<!-- With DataContext Binding -->
<TextBlock Text="{local:CurrencySymbol CultureBindingPath=ViewModelCulture}"/>
Where ViewModelCulture is the property on the view-model being used as the source of the binding.
I found a workaround for this problem.
The main idea is to define attached property for each parameter that requires binding.
public class MarkupExtensionWithBindableParam : MarkupExtension
{
public BindingBase Param1 { get; set; } // its necessary to set parameter type as BindingBase to avoid exception that binding can't be used with non DependencyProperty
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject targetObject;
DependencyProperty targetProperty;
if (target != null && target.TargetObject is DependencyObject && target.TargetProperty is DependencyProperty)
{
targetObject = (DependencyObject)target.TargetObject;
targetProperty = (DependencyProperty)target.TargetProperty;
}
else
{
return this; // magic
}
// Bind the Param1 to attached property Param1BindingSinkProperty
BindingOperations.SetBinding(targetObject, MarkupExtensionWithBindableParam.Param1BindingSinkProperty, Param1);
// Now you can use Param1
// Param1 direct access example:
object param1Value = targetObject.GetValue(Param1BindingSinkProperty);
// Param1 use in binding example:
var param1InnerBinding = new Binding() { Source = targetObject, Path = new PropertyPath("(0).SomeInnerProperty", Param1BindingSinkProperty) }); // binding to Param1.SomeInnerProperty
return param1InnerBinding.ProvideValue(serviceProvider); // return binding to Param1.SomeInnerProperty
}
private static DependencyProperty Param1BindingSinkProperty = DependencyProperty.RegisterAttached("Param1BindingSink", typeof(object)// set the desired type of Param1 for at least runtime type safety check
, typeof(MarkupExtensionWithBindableParam ), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));
}
Usage is straightforward:
<TextBlock Text="{local:MarkupExtensionWithBindableParam Param1={Binding Path='SomePathToParam1'}}"/>
This link is informative about
Custom Markup Extension with bindable properties
EDITSomeone made me note that this works only for Silverlight, because in WPF MarkupExtension doesn't implement IMarkupExtension interface. (Thank you EvAlex)

Is it possible to get x:Name of a DependencyObject (Silverlight)?

I have a DependencyObject (an Interactivity Behavior), and I'd like to get its x:Name (just get, not set) from code. Is it possible?
EDIT: Following AnthonyWJones's answer:
I've inserted the following code into my base behavior:
[EditorBrowsable(EditorBrowsableState.Never)]
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(BaseBehavior<T>), new PropertyMetadata(null));
I've given my behaviors x:Name, yet the Name property doesn't get filled.
If you want your Name property to be the same as the x:Name XAML name, then instead of implementing your own dependcy property, piggy-back on the existing one that is already registered. You can simply implement your name property as:
public string Name
{
get { return (string) base.GetValue(FrameworkElement.NameProperty); }
set { base.SetValue(FrameworkElement.NameProperty, value); }
}
If the class deriving from DependencyObject does not expose a Name property then you cannot determine the assigned x:Name. The x:Name value is store only in an internal object tree and there is no API to resolve value (the object) back to a key value (the name).
However if this is your own behaviour then simply add a Name dependency property to your behaviour. x:Name will assign is value to a Name property if present.
If this an existing behaviour you may be able to inherit from it to create a new class that has a Name property. Unfortunately some behaviours are sealed so you can't always do this.
You can create 'Name' AttachedProperty, and use NameAttachedProperty.GetName(DependencyObject)
Of course you will have to attach it to your element before using.
public static class NameAttachedProprty
{
public static readonly DependencyProperty NameProperty =
DependencyProperty.RegisterAttached("Name", typeof (string), typeof (NameAttachedProprty), new PropertyMetadata(default(string)));
public static void SetName(DependencyObject element, string value)
{
element.SetValue(NameProperty, value);
}
public static string GetName(DependencyObject element)
{
return (string) element.GetValue(NameProperty);
}
}

Using dependency properties in wpf

I'm not quite sure if I've got the right grasp on this or not, what I've read seems to agree with what I'm trying to do, however It doesn't seem to be working.
If I add an additional owner to a dependency property of a class, whenever the orig class dp changes, the change should get propagated to the additional owner, correct?
What I have is a custom control, which I want to set a property on, and then on certain objects that are within the custom control data template inherit this property value.
public class Class1: DependencyObject{
public static readonly DependencyProperty LongDayHeadersProperty;
public bool LongDayHeaders {
get { return (bool)GetValue(LongDayHeadersProperty); }
set { SetValue(LongDayHeadersProperty, value); }
}
static Class1(){
LongDayHeadersProperty = DependencyProperty.Register("LongDayHeaders", typeof(bool), typeof(Class1),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits));
}
}
public class Class2: DependecyObject{
public static readonly DependencyProperty LongDayHeadersProperty;
public bool LongDayHeaders{
get{ return(bool)GetValue(LongDayHeadersProperty); }
set{ SetValue(LongDayHeadersProperty, value); }
}
static Class2(){
LongDayHeadersProperty = Class1.LongDayHeadersProperty.AddOwner(typeof(Class2));
}
}
But if I assign a DependencyPropertyDescriptor to both properties, it only fires for the Class1 and Class2 doesn't change.
Have I missed something in my understanding?
UPDATE
After some testing, I'm not even sure if my child control is considered a child control within the logical or visual tree. I think it is, but the lack of success leads me to believe otherwise.
There a many class2's which exist in an observable collection of class1. This, to me, makes them childs of class1? But even if I use RegisterAttach on class2, and set the property in class1, it doesn't seem to have any effect?
As MSDN states, the Inherits flag only works when you use RegisterAttached to create the property. You can still use the property syntax for the property.
Update
For clarity, here is how I would define the properties:
public class Class1 : FrameworkElement
{
public static readonly DependencyProperty LongDayHeadersProperty =
DependencyProperty.RegisterAttached("LongDayHeaders",
typeof(bool),
typeof(Class1),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits));
public bool LongDayHeaders
{
get { return (bool)GetValue(LongDayHeadersProperty); }
set { SetValue(LongDayHeadersProperty, value); }
}
}
public class Class2: FrameworkElement
{
public static readonly DependencyProperty LongDayHeadersProperty =
Class1.LongDayHeadersProperty.AddOwner(typeof(Class2));
public bool LongDayHeaders
{
get{ return(bool)GetValue(LongDayHeadersProperty); }
set{ SetValue(LongDayHeadersProperty, value); }
}
}
If you want your children to be logical children of your control, you need to call the AddLogicalChild. Also, you should expose them through the LogicalChildren property. I must also point out that both classes must derive from FrameworkElement or FrameworkContentElement, as the logical tree is only defined for these elements.
Since you are using an ObservableCollection, you would handle the collection changed events and Add/Remove the children depending on the change. Also, the LogicalChildren property can just return your collection's enumerator.
You are confusing DependencyProperties with Attached (Dependency) Properties.
A DP is for when a class wants bindable, stylable etc properties on itself. Just like .NET properties, they are scoped within their classes. You can register for a property changed event on individual objects, but not globally. TextBox.Text is an example of this. Note that Label.Text is unrelated to TextBox.Text.
An AP is for when a class wants to decorate another object with additional properties. The class that declares the AP is able to listen for property changed events on ALL instances of other objects that have this AP set. Canvas.Left is an example of this. Note that you always have to qualify this setter: <Label Text="Hi" Canvas.Left="50"/>

Attached Property and Binding

I'm creating an attached behavior in order to set a regular property of a class:
public class LookupHelper
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.RegisterAttached("ItemsSource", typeof(object), typeof(LookupHelper), new UIPropertyMetadata(null, OnItemsSourceChanged));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as MyControl;
if(control == null)
return;
control.ItemsSource = (IEnumerable)e.NewValue;
}
public static object GetItemsSource(GridColumn column)
{
return column.GetValue(ItemsSourceProperty);
}
public static void SetItemsSource(GridColumn column, object value)
{
column.SetValue(ItemsSourceProperty, value);
}
}
Here, ItemsSource property on MyControl is a regular property, so I can not bind it in Xaml, hence this attached behavior.
Now, when I use this attached property using string or objects it works and breakpoint I set is hit, but when I set it with Binding markup, it never runs. Why isn't this working?
<MyControl ctrl:LookupHelper.ItemsSource="DataSource"/>; //It works
<MyControl ctrl:LookupHelper.ItemsSource="{Binding Path=MyDataSource}"/>; //Does not work
What I need to do is to set the ItemsSource property to the value specified by the Binding.
In your Get and Set methods, you're defining the receiving object as GridColumn where it should be DependencyObject.
You might also want to change the type of your DP from object to IEnumerable since your casting to that in your change handler.
Can you please post the markup you are using? Also, If the actual property exists on an object and makes sense there then I think you should be using a regular dependency property on that object instead of an attached property on a helper class.
Edit
From MSDN:
The signature for the GetPropertyName accessor must be:
public static object GetPropertyName(object target)
and the signature for the SetPropertyName accessor must be:
public static void SetPropertyName(object target, object value)
In your case, is GridColumn the correct target type?

Resources