I have a custom MarkupExtension that simulates binding. It works well in normal assignments but not when used in Style Setters, for example:
<Setter Property="Content" Value="{local:MyExtension}" />
results in a XamlParseException:
A 'Binding' cannot be set on the 'Value' property of type 'Setter'.
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
This is implementation of the extension:
public class MyExtension : MarkupExtension
{
public MyExtension()
{
Value = 123;
}
public object Value
{
get;
set;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var binding = new Binding("Value")
{
Source = this,
};
return binding.ProvideValue(serviceProvider);
}
}
What's the problem?!
Kind of guessing, but it's likely because the XAML compiler has special built-in support for the Binding class, allowing its usage in this scenario (and others). The Binding class is also a MarkupExtension, but unfortunately it seals its implementation of ProvideValue().
That said, you might just get away with this:
public class MyBinding : Binding
{
private object value;
public object Value
{
get { return this.value; }
set
{
this.value = value;
this.Source = value;
}
}
}
Since ProvideValue will return the Binding instance anyway.
From the documentation, it looks like the object must be freezable (so they can be shared between various interested parties)
http://msdn.microsoft.com/en-us/library/system.windows.setter.value.aspx
"Data binding and dynamic resources within the object is supported if the specified value is a Freezable object. See Binding Markup Extension and DynamicResource Markup Extension."
why don't you
return Value
inside the ProvideValue??
else
You can bind to only DependencyProperty. make a dependency property for Value in your MyExtension Class!
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(Object), typeof(MyContentControl), new UIPropertyMetadata());
Related
I have previously used the following code I found somewhere to implement a BindingProxy in WPF.
public class BindingProxy : Freezable
{
public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return this.GetValue(DataProperty); }
set { this.SetValue(DataProperty, value); }
}
}
I have tried to create a similar class for my Xamarin application which looks like this.
public class BindingProxy : BindableObject
{
// Using a DependencyProperty as the backing store for Data.
// This enables animation, styling, binding, etc...
public static readonly BindableProperty DataProperty = BindableProperty.Create(nameof(Data), typeof(object), typeof(BindingProxy), null);
public object Data
{
get { return this.GetValue(DataProperty); }
set { this.SetValue(DataProperty, value); }
}
}
This is the closest I could come up with to the Freezable class in Xamarin, unfortunately however when I declare my xaml as so
<ContentPage.Resources>
<binding:BindingProxy x:Key="BindingProxy" Data="{Binding BindingContext}" />
</ContentPage.Resources>
the Data property is never set to the BindingContext (or any other value) and returns the default value (null).
Can anyone provide any insight to what I might be doing wrong?
According to the answers here resources are not provided with the BindingContext and therefore do not support databinding.
Depending on your use case there might be another option to get the specific BindingContext, so you maybe want to explain that a little.
In my WPF application, we have extended the DataGridTextColumn with a class named SampleTextBoxColumn, and created a dependency property Sample inside it.
Example
public class SampleTextBoxColumn : DataGridTextColumn
{
public static readonly DependencyProperty SampleProperty =
DependencyProperty.Register("Sample", typeof(object), typeof(SampleTextBoxColumn), new FrameworkPropertyMetadata(null));
public object Sample
{
get { return (object)GetValue(SampleProperty); }
set { SetValue(SampleProperty, value); }
}
}
Now in XAML, I want to set value of this property Sample depending upon conditions i.e. want to set this property inside DataTrigger.
Now Since .net does not provide a way to style the DataGridColumn, So I am not able to do this.
Or, in generic, How can we set Dependency Property of DataGridTextColumn in Style.
I have written a control with a bindable property. This control also has a method to modify that property's value:
public class MyControl : ContentView // WPF: inherited from UserControl
{
// Xamarin:
public static readonly BindableProperty MyValueProperty = ...
// WPF:
// public static readonly DependencyProperty MyValueProperty = ...
public int MyValue
{
get { return (int) GetValue(MyValueProperty); }
set { SetValue(MyValueProperty, value); }
}
public void Reset()
{
MyValue = 0;
}
}
I am using that control in a normal XAML page and update MyValue via binding:
<local:MyControl x:Name="TheControl"
MyValue="{Binding MyValueSource, Mode=OneWay}" />
The binding initially propagates changes from MyValueSource to MyValue. But as soon as I call the Reset() method once, the binding is overwritten by the 0 and updates to MyValueSource are no longer pulled.
I suppose any direct assignment of MyValue is intended to replace a OneWay binding. With a TwoWay binding, the change is just propagated back to MyValueSource and the binding remains functional.
If Reset() was in the view model, I could do this:
public void Reset()
{
// TheControl.MyValue = 0; // Bad practice, destroys the binding
MyValueSource = 0; // Good practice, preserves the binding
}
I don't want to implement the reset logic (which is more complex than in this reduced example) in every VM though, so it's located in the view/control.
So I wonder - can you assign a bindable property's value from the control's code behind and still preserve a possible OneWay binding? I know this means the VM does not get the changed value; binding OneWay is likely not correct if the control updates the property as well; you should rather use a TwoWay binding then.
But if someone says OneWay in XAML, I'd rather have it behave that way down to the wire than implement some "OneWay until you call Reset()" behavior.
Side note: I am working in Xamarin, but I guess the behavior is the same for WPF.
Taken and fleshed out from #Clemens' comment:
WPF
You can use the SetCurrentValue method on an DependencyObject (i. e. the control) to change the current effective value of a DependencyProperty. Unlike SetValue, with SetCurrentValue any triggers, data bindings and styles to that property remain intact.
public void Reset()
{
// this.SetValue(MyValueProperty, 0); // Replaces the binding
this.SetCurrentValue(MyValueProperty, 0); // Keeps the binding
}
Remember that if you defined a OneWay binding, the view model will not be notified about the changed value, and that any change to the VM's MyValueSource property will override the control's value again (if the property is implemented correctly).
Xamarin
There is currently no proper way to assign a BindableProperty's value without replacing a OneWay binding attached to it. BindableObject (the control's base class) does not have any method comparable to WPF's SetCurrentValue and SetValue will allways replace the binding.
However, if you change the binding to BindingMode.TwoWay, the internal value change is propagated back to the view model. You should probably do this anyway to keep the control and the VM synchronized.
public void Reset()
{
// Replaces any OneWay bindings
// Updates MyValueSource for TwoWay bindings
this.SetValue(MyValueProperty, 0);
}
Here is the Hacky WPF equivalent for Xamarin, for OneWay binding:
public static class BindingObjectExtensions
{
public static Binding GetBinding(this BindableObject self, BindableProperty property)
{
if (self == null)
{
throw new ArgumentNullException(nameof(self));
}
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
var methodInfo = typeof(BindableObject).GetTypeInfo().GetDeclaredMethod("GetContext");
var context = methodInfo?.Invoke(self, new object[] { property });
var propertyInfo = context?.GetType().GetTypeInfo().GetDeclaredField("Binding");
return propertyInfo?.GetValue(context) as Binding;
}
public static void SetCurrentValue(this BindableObject self, BindableProperty property, object value)
{
if (self == null)
{
throw new ArgumentNullException(nameof(self));
}
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
var backupBinding = self.GetBinding(property);//backup binding
var backupConverter = backupBinding.Converter;//backup orig. converter
self.SetValue(property,value);//removes the binding.
backupBinding.Converter = new DefaultValueConverter {DefaultValue = value};//change the converter
self.SetBinding(property, backupBinding);//target should be updated to the default value
var converterField = backupBinding.GetType().GetTypeInfo().GetDeclaredField("_converter");
converterField.SetValue(backupBinding, backupConverter);//restore the converter
}
}
//the default value converter class
[ContentProperty(nameof(DefaultValue))]
public class DefaultValueConverter : BindableObject, IValueConverter, IMarkupExtension<DefaultValueConverter>
{
public object DefaultValue
{
get => GetValue(DefaultValueProperty);
set => SetValue(DefaultValueProperty, value);
}
public static readonly BindableProperty DefaultValueProperty =
BindableProperty.Create(nameof(DefaultValue), typeof(object), typeof(DefaultValueConverter));
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return DefaultValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DefaultValue;
}
public DefaultValueConverter ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return ((IMarkupExtension<DefaultValueConverter>) this).ProvideValue(serviceProvider);
}
}
I would like to do something like a static variable in normal programming, only in XAML using Dependency Properties.
Meaning I would like the property to :
Be one instance
Be visible by every element
Be bindable
How do I do that ?
It sounds like you want an attached property that always applies to every element. I think the easiest way to make that work would be through the CoerceValueCallback of the dependency property, where you could force it to always return the static value regardless of the element's local value (you would update the static value in the PropertyChangedCallback).
This seems like an odd way to use the dependency property system, though. Maybe you just need a central binding source? You can bind to a static instance by assigning Binding.Source using x:Static:
{Binding Source={x:Static Member=global:GlobalObject.SharedInstance},
Path=SharedValue}
Note that SharedValue isn't a static property; it's a property of an instance accessed from the static SharedInstance property:
public class GlobalObject {
private static readonly GlobalObject _instance = new GlobalObject();
public static GlobalObject SharedInstance { get { return _instance; } }
public object SharedValue { get; set; }
}
Easy.
Create an attached DependencyProperty on the DependencyObject Type.
public static readonly DependencyProperty DerpProperty =
DependencyProperty.RegisterAttached(
"Derp",
typeof(DependencyObject),
typeof(Herp),
new FrameworkPropertyMetadata());
public static void SetDerp(DependencyObject element, Herp value)
{
element.SetValue(DerpProperty, value);
}
public static Herp GetDerp(DependencyObject element)
{
return (Herp)element.GetValue(DerpProperty);
}
Defined on any type, it can be used on any type as well. In this example, it creates a new property called Derp on all DependencyObject instances that gets/sets an associated Herp value.
Assuming this is defined in a type called LolKThx in the namespace WpfFtw, you might use it in this way...
<Textblock
xmlns:lol="clr-namespace:WpfFtw"
lol:LolKThx.Derp="There's an implicit conversion for string -> Herp, btw" />
You can specify callbacks in your FrameworkPropertyMetadata to perform any action you need on setting/getting values.
I've created a dependency property like this:
public partial class MyControl: UserControl
{
//...
public static DependencyProperty XyzProperty = DependencyProperty.Register("Xyz",typeof (string),typeof (MyControl),new PropertyMetadata(default(string)));
public string Xyz
{
get { return (string) GetValue(XyzProperty ); }
set { SetValue(XyzProperty , value); }
}
//...
}
Then bind it to my wpf window and everything worked fine.
When I tried to add some logic to the setter I notice it wasn't being called. I modify the get;Set up to a point now they look like this:
get{return null;}
set{}
And it is still works! How come? What's the use of that GetValue/SetValue calls?
The WPF data binding infrastructure uses the DependencyProperty directly, the Xyz property is a convenience interface for the programmer.
Take a look at the PropertyMetadata in the call to DependencyProperty.Register, you can supply a callback that will run when the property value is changed, this is where you can apply your business logic.
The DependencyProperty is the backing store for the XyzProperty. If you access the property through the DependencyProperty interface, it completely bypasses the Property's Get/Set accessor.
Think of it this way:
private int _myValue = 0;
public int MyValue
{
get { return _myValue; }
set { _myValue = value; }
}
In this instance, if I manually assign _myValue = 12, obviously the "Set" accessor for the MyValue property won't be called; I completely bypassed it! The same is true for DependencyProperties. WPF's binding system uses the DependencyProperty interfaces directly.