There are plenty of ways to bind a SOURCE method to target property, either by ValueConverter or by ObjectDataProvider. However, what if I want to have the binding affect the target METHOD?
Consider the following example:
class ListBoxViewModel
{
public static readonly DependencyProperty CurrentItemProperty = DependencyProperty.Register("CurrentItem", typeof (object), typeof (ListBoxViewModel));
public object CurrentItem
{
get { return (object) GetValue(CurrentItemProperty); }
set { SetValue(CurrentItemProperty, value); }
}
}
I'd like to bind the property CurrentItem to ListBox's CollectionView. However, since the CurrentItem property of CollectionView is read-only, I can't bind to it directly. Instead, I have to execute MoveCurrentToPosition function. How can I do it?
If there is a different way to do that - Without binding to a method, I'd love to hear it too, however, the main question is how to bind to a method, if not in this case, then in a similar one. If it is impossible, what is the best alternative? E.g one idea that comes to mind is subscribing to the change notification of the dependency property (CurrentItem in this case) and running the procedural code from that function.
Thanks!
You can register your property with a property changed callback in which you then can update the CollectionView manually:
public static readonly DependencyProperty CurrentItemProperty =
DependencyProperty.Register
(
"CurrentItem",
typeof(object),
typeof(ListBoxViewModel),
new UIPropertyMetadata(null, CurrentItemChanged)
);
public object CurrentItem
{
get { return (object)GetValue(CurrentItemProperty); }
set { SetValue(CurrentItemProperty, value); }
}
private static void CurrentItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Update current item logic
}
Related
I've a simple control with dependency property like this
public class StatusProgress : Control
{
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(StatusProgress),
new FrameworkPropertyMetadata(null, (d, e) => (d as StatusProgress).TextUpdated(e.OldValue as string)));
private void TextUpdated(string text)
{
Trace.WriteLine("test");
}
}
then I have view model
public class ViewModelPageAnalyse : ViewModelPageBase
{
private string _progressText;
public string ProgressText
{
get { return _progressText; }
set
{
_progressText = value;
OnPropertyChanged(); // base class INotifyPropertyChanged implementation
}
}
}
Then there is a user control (displayed in window with ContentControl). User control is bound to view model with data template (maybe this is important)
<DataTemplate DataType="{x:Type local:ViewModelPageAnalyse}">
<local:UserControlAnalyse/>
</DataTemplate>
And this is the control in user control
<local:StatusProgress Text="{Binding ProgressText}"/>
Now the interesting part. When ProgressText in view model is set/changed, property changed callback is called twice. I see twice "test" in the debugger output window.
More interesting: when view is changed, for some reason callback is again called with e.NewValue = null, while there is nothing directly sets ProgressText to null.
I tried already to check if value is changed in the ProgressText setter before rising event, tried to set binding mode one-way, problem still - callback is called twice with same value, call stack looks same, but there are really a lot of calls within wpf to be really sure.
While double-shot is not a real issue, it bother me. Callback with null value is what my real problem (I think they are related). Anyone knows what is wrong?
Found a reason of the first problem: it was other control with Content. During transition it created a new Model (because Content is ViewModel) instead of reassigning existing user control. Totally my fault. Second problem still and I found this question (with workaround which is not suiting me).
Need help with
PropertyChanged callback is called with default value when ContentControl ViewModel is changed.
Which means null for Text in my case. Anyone? I couldn't figure out why is it called. My guess it is called by DataTemplate manager, if I can say so.
try to change this line:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(StatusProgress),
new FrameworkPropertyMetadata(null, (d, e) => (d as StatusProgress).TextUpdated(e.OldValue as string)));
with this:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(StatusProgress)
, new PropertyMetadata(""));
I am new to binding. I have binded slider value to my control's property and my controls property get changed when I change the slider value.
Now, when I need to change the slider value by changing my property value, it does not work..
I modified the xaml from some internet source, but still not get the expected output.
can anyone help me out...
<Grid>
<cc:MyControl Name="mycntrl" ZoomPercentage="{Binding ElementName=slider,Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></cc:MyControl>
<Slider Name="slider" Margin="20,20,20,400" Minimum="100" Maximum="400"></Slider>
</Grid>
Updated:
My code behind for my ZoomPercentage dependency property is below
public double ZoomPercentage
{
get
{
return (double)GetValue(ZoomPercentageProperty);
}
set
{
SetValue(ZoomPercentageProperty, value);
}
}
My dependency registration
public static readonly DependencyProperty ZoomPercentageProperty = DependencyProperty.Register("ZoomPercentage", typeof(double), typeof(MyControl), new FrameworkPropertyMetadata(ZoomPercentagePropertyChanged));
public static void ZoomPercentagePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (args.OldValue != null)
{
if ((double)args.NewValue != (double)args.OldValue)
{
MyControl mycontrol = obj as MyControl;
mycontrol .ZoomTo((int)((double)args.NewValue));
}
}
}
Your ZoomPercentage property should be implemented as a Dependencyproperty
Something like this
public class MyControl:UserControl
{
public MyControl() : base() { }
public double ZoomPercentage
{
get { return (double)this.GetValue(ZoomPercentageProperty); }
set { this.SetValue(ZoomPercentageProperty, value); }
}
public static readonly DependencyProperty ZoomPercentageProperty = DependencyProperty.Register(
"ZoomPercentage", typeof(double), typeof(MyControl:),new PropertyMetadata(0));
}
read more here
If you want a data bound control in the UI to update after changes made in code then you have to do one of two things. One option is to correctly implement the INotifyPropertyChanged interface in the class that you declared your Value property.
The other is to declare your Value property as a DependencyProperty, although you should only really do this in the code behind of your Window or UserControl and opt for the first method if you are using a view model. The purpose of these two methods is for you to 'plug in' to WPF notification framework, so that your UI control will update. Please read the linked pages for more information.
Consider this scenario, using MVVM:
On my ModelView, I have one property, of type "string", it does notify the change of properties through INotifyPropertyChanged.
In the view, there is (or not) one control, with a DependencyProperty "Notification" of a type which is not a string. That control may or may not change that property depending on facts that only the control knows (neither the ModelView or the View knows about those). That control might even be on other View which may or may not be on the current visual tree.
In the View, I need a bridge between that control's DependencyProperty and the ViewModel's property, so that changing the view property makes the control change its property, and changing the control's DependencyProperty makes the viewmodel's property change its value.
I've got it to work, but I don't think it's an elegant solution. I might be thinking fuzzy these days so I'm asking if there's something obvious that I might have missed.
The obvious way would be either having the ViewModel Property be a DependencyProperty (so it could be bound two ways), however that is not possible right now (plus, it'd break the MVVM pattern, adding view-specific implementations to the viewmodel).
The other obvious way would be binding the Control's DependencyProperty to the ViewModel's property: this works, but just for one view... several properties cannot (or, I don't know how to do it) be bound to the same DependencyProperty: when I set one binding, I lose the other.
Currently this is what I do:
public class BaseViewUserControl : UserControl
{
// Dependency property, bound to the view's property
public string AudioNotification
{
get { return (string)GetValue(AudioNotificationProperty); }
set { SetValue(AudioNotificationProperty, value); }
}
public static readonly DependencyProperty AudioNotificationProperty = DependencyProperty.Register("AudioNotification", typeof(string), typeof(BaseViewUserControl), new FrameworkPropertyMetadata("None", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnAudioNotificationPropertyChanged));
// Dependency property, bound to the control's dependency property
public AudioNotificationType AudioNotificationToControl
{
get { return (AudioNotificationType)GetValue(AudioNotificationToControlProperty); }
set { SetValue(AudioNotificationToControlProperty, value); }
}
public static readonly DependencyProperty AudioNotificationToControlProperty = DependencyProperty.Register("AudioNotificationToControl", typeof(AudioNotificationType), typeof(BaseViewUserControl), new FrameworkPropertyMetadata(AudioNotificationType.None, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, OnAudioNotificationToControlCoerceValue));
// Converter
private static IValueConverter _audioNotificationTypeConverter;
private static IValueConverter AudioNotificationTypeConverter
{
get { return _audioNotificationTypeConverter ?? (_audioNotificationTypeConverter = new AudioNotificationConverter()); }
}
private Binding _audioNotificationBinding;
private bool PrepareAudioNotificationControlBinding()
{
if (_audioNotificationBinding != null) return true;
var b = this.FindVisualTreeRoot().TryFindChild<AudioNotification>();
if (b == null) return false;
_audioNotificationBinding = new Binding { Source = b, Mode = BindingMode.TwoWay, Path = new PropertyPath("Notification") };
SetBinding(AudioNotificationToControlProperty, _audioNotificationBinding);
return true;
}
private static void OnAudioNotificationPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (!(source is BaseViewUserControl)) return;
var src = (BaseViewUserControl)source;
if(src.PrepareAudioNotificationControlBinding())
{
var val = AudioNotificationTypeConverter.ConvertValue<AudioNotificationType>(e.NewValue);
src.AudioNotificationToControl = val;
}
}
private static object OnAudioNotificationToControlCoerceValue(DependencyObject source, object basevalue)
{
if (!(source is BaseViewUserControl)) return basevalue;
var src = (BaseViewUserControl)source;
var val = AudioNotificationTypeConverter.ConvertBackValue<string>(basevalue);
src.AudioNotification = val;
return basevalue;
}
public BaseViewUserControl()
{
var ab = new Binding { Path = new PropertyPath("AudibleNotification"), Mode = BindingMode.TwoWay };
SetBinding(AudibleNotificationProperty, ab);
}
}
NOTE: I'm using this for several things, not just for audio notification (that's an example only). Do not rely on the names to give a solution (if any), this needs to be quite generic. Also, any typos come from simplifying the code to the problem (I've removed much code and changed some property names for clarification).
As I said, it works... I just find it quite not-elegant and I'm sure there should be a better solution than this.
Any suggestions will be more than welcome.
Update
Based on Julien's code, I made this Behavior, which does exactly what I wanted. I implemented it using Converter, but for clarity's sake, I ended up doing the conversion on the control itself and using strings to pass variables along (with an undocumented property in the control's if I still want to use the native data type)
public class BridgePropertyBinderBehavior : Behavior<DependencyObject>
{
public static BridgePropertyBinderBehavior PrepareBindingToControl(FrameworkElement sourceView, string viewModelPropertyPath, FrameworkElement targetControl, string controlPropertyPath)
{
var b = new BridgePropertyBinderBehavior();
BindingOperations.SetBinding(b, AProperty, new Binding(viewModelPropertyPath) { Source = sourceView.DataContext, Mode = BindingMode.TwoWay, BindsDirectlyToSource = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
BindingOperations.SetBinding(b, BProperty, new Binding(controlPropertyPath) { Source = targetControl, Mode = BindingMode.TwoWay });
Interaction.GetBehaviors(sourceView).Add(b);
return b;
}
public object A { get { return GetValue(AProperty); } set { SetValue(AProperty, value); } }
public static readonly DependencyProperty AProperty = DependencyProperty.Register("A", typeof(object), typeof(BridgePropertyBinderBehavior), new FrameworkPropertyMetadata(null, (d, e) => ((BridgePropertyBinderBehavior)d).OnAChanged(e.NewValue)));
public object B { get { return GetValue(BProperty); } set { SetValue(BProperty, value); } }
public static readonly DependencyProperty BProperty = DependencyProperty.Register("B", typeof(object), typeof(BridgePropertyBinderBehavior), new FrameworkPropertyMetadata(null, (d, e) => ((BridgePropertyBinderBehavior)d).OnBChanged(e.NewValue)));
private void OnAChanged(object value) { B = value; }
private void OnBChanged(object value) { A = value; }
protected override Freezable CreateInstanceCore()
{
return new BridgePropertyBinderBehavior();
}
}
Which I use like this on my view:
var audioNotificationControl = this.FindVisualTreeRoot().TryFindChild<AudioNotification>();
BridgePropertyBinderBehavior.PrepareBindingToControl(this, "AudioNotification", audioNotificationControl, "Notification");
or
<AudioNotification x:Name="Control">
<ia:Interaction.Behaviors>
<BridgePropertyBinderBehavior
A="{Binding Path=Notification, ElementName=Control, Mode=TwoWay}"
B="{Binding Path=AudioNotification, Mode=TwoWay}" />
</ia:Interaction.Behaviors>
</AudioNotification>
I've accepted his answer since it's what got me on the right track, thanks
If I understand you correctly, you need to bind one DP to two sources, one as a source and the other as a target. I actually have a behavior to do that.
The principle of this behavior is quite simple: it uses two dependency properties and makes the data of one (In) flows into the other (Out). Bind In with a one way binding and Out with a one way to source binding and you're done.
public class BindingBehavior : Behavior<DependencyObject> {
public static readonly DependencyProperty InProperty = DependencyProperty.Register(
"In",
typeof(object),
typeof(BindingBehavior),
new FrameworkPropertyMetadata(null, (d, e) => ((BindingBehavior) d).OnInPropertyChanged(e.NewValue)));
public static readonly DependencyProperty OutProperty = DependencyProperty.Register(
"Out",
typeof(object),
typeof(BindingBehavior),
new FrameworkPropertyMetadata(null));
// Bind OneWay
public object In {
get { return GetValue(InProperty); }
set { SetValue(InProperty, value); }
}
// Bind OneWayToSource
public object Out {
get { return GetValue(OutProperty); }
set { SetValue(OutProperty, value); }
}
private void OnInPropertyChanged(object value) {
Out = value;
}
protected override Freezable CreateInstanceCore() {
return new BindingBehavior();
}
}
This behavior needs a reference to System.Windows.Interactivity from Blend SDK, which you might be familiar with.
Assuming you remove your string property and only keep a AudioNotificationType one named AudtioNotification, the usage should be similar to:
<YourView x:Name="View">
<YourControl x:Name="Control" AudioNotification="{Binding Notification, ElementName=View}>
<i:Interaction.Behaviors>
<BindingBehavior
In="{Binding AudioNotification, ElementName=Control, Mode=OneWay}"
Out="{Binding YourVmProperty, Mode=OneWayToSource, Converter=YourConverter}" />
</i:Interaction.Behaviors>
</YourControl>
</YourView>
You can place the behavior on any element being on the correct name scope for resolving element names and having the view model as the data context.
This looks like it might be a useful time to add a layer of abstraction. I know. Ick. But bear with me.
What if you had a bridge object that an abitrary number of things can bind to that handles the notifications on change. It doesn't even need to be that complex. Just something that implements INotifyPropertyChanged and then has a property (or properties) that release a notification on change. That way, your ViewModel, your View and your control can all bind to the same property on this bridge object and when one of those changes the bridge object's property, all the others will know that it's time to change as well. As long as all the objects are bound two-way, everything should synch just fine.
That's essentially what you've done on your BaseViewUserControl, but encapsulating the behavior in a separate object might provide you flexibility benefits.
Am working with dependency objects here, not done much with them before but they look very useful.
Basically I've used these objects to be editable in a ListView. However, I then have to write these changes back to SQL. My question is, is there a way to record whether the data has been modified because I don't want to write back to SQL every time someone views the data. Currently I have this:
public class KPI : DependencyObject
{
public static readonly DependencyProperty DepartmentProperty = DependencyProperty.Register("Department", typeof(string), typeof(KPI), new UIPropertyMetadata(null));
public string Department
{
get { return (string)GetValue(DepartmentProperty); }
set { SetValue(DepartmentProperty, value); }
}
public static readonly DependencyProperty KPINumberProperty = DependencyProperty.Register("KPINumberProperty", typeof(int), typeof(KPI), new UIPropertyMetadata(null));
public int KPINumber
{
get { return (int)GetValue(KPINumberProperty); }
set { SetValue(KPINumberProperty, value); }
}
}
My idea was to have something like:
public static bool DataModified = false;
public static readonly DependencyProperty DepartmentProperty = DependencyProperty.Register("Department", typeof(string), typeof(KPI), new UIPropertyMetadata(null));
public string Department
{
get { return (string)GetValue(DepartmentProperty); }
set { SetValue(DepartmentProperty, value); DataModified = true; }
}
So every time something is edited the DataModified property will be set to TRUE, is this a good way of doing it? Or has somebody got a much better way of doing it?
Thanks in advance.
SumGuy.
This actually won't work if you're binding to the dependency property. The WPF binding engine does not actually use your CLR "Department" property, but rather uses "SetValue" on the dependency property directly. There is an easy solution to this though.
The UIPropertyMetadata has a field for a PropertyChangedCallback which will fire every time the value of the property is changed (either from a call to SetValue directly, or through the CLR property which is wrapping the SetValue call)
Here's an example:
public static readonly DependencyProperty DepartmentProperty =
DependencyProperty.Register("Department",
typeof(string),
typeof(KPI),
new UIPropertyMetadata(null, DepartmentPropertyChanged));
private static void DepartmentPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
KPI me = d as KPI;
if (me == null) return;
// Talk to your Business/Data layers here
}
public string Department
{
get { return (string)GetValue(DepartmentProperty); }
set { SetValue(DepartmentProperty, value); }
}
The DependencyObject (d) is the object who the property belongs to. In your case, this would be an instance of KPI.
For reference, here's a link to the UIPropertyMetadata MSDN documentation: http://msdn.microsoft.com/en-us/library/system.windows.uipropertymetadata.aspx
WPF binding system doesn't necessarily call the Department CLR property, instead it directly calls SetValue whenever it updates dependency property (in this case Department). That means, your CLR wrapper may not get called, which in turns means whatever code you've written in set block (of Department CLR property) would not be executed.
But don't worry, there is solution for it. While initializing DepartmentProperty you can pass a callback to UIPropertyMetadata which would be called everytime it updates the dependency property. All it means is that, you've to implement this:
public static readonly DependencyProperty DepartmentProperty = DependencyProperty.Register
(
"Department",
typeof(string),
typeof(KPI),
new UIPropertyMetadata(null, OnDepartmentChanged)
);
public string Department
{
get { return (string)GetValue(DepartmentProperty); }
set { SetValue(DepartmentProperty, value);}
}
static void OnDepartmentChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
(d as KPI).DataModified = true; //this is all you want!
}
I may be doing this all wrong... so hang with me
I am making a user control with a property which the user can bind to. The in setter for the property, I bind the PropertyChanged listener to the property so I can react to changes to its state. The code behind for this user control looks like this:
public static readonly DependencyProperty NodeProperty =
DependencyProperty.Register("Node", typeof(MockRequirementWrapper), typeof(RecNode2));
public MockRequirementWrapper Node
{
get
{
return (MockRequirementWrapper)GetValue(NodeProperty);
}
set
{
if(Node != null)
Node.PropertyChanged -= Update;
SetValue(NodeProperty, value);
Node.PropertyChanged += new PropertyChangedEventHandler(Update);
OnPropertyChanged(this, "Node");
}
}
then, in another user control, I bind to this property a node I've created elsewhere like this:
<local:RecNode2 Node="{Binding}"/>
What I am finding is the recnode exists and is bound to a node... but if I put a breakpoint in the setter, it never gets called. Am I misunderstanding how the binding works? How do I add my listener when the node changes?
The framework will always call GetValue and SetValue directly, the property is just for convenience and sould never contain logic besides those calls.
If you want to do something on changes register a PropertyChangedCallback in the Metadata when registering the DependencyProperty.
Taken from http://msdn.microsoft.com/en-us/library/ms753358.aspx:
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
"AquariumGraphic",
typeof(Uri),
typeof(AquariumObject),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnUriChanged)
)
);
public Uri AquariumGraphic
{
get { return (Uri)GetValue(AquariumGraphicProperty); }
set { SetValue(AquariumGraphicProperty, value); }
}