Twoway-Bind View's Dependencyproperty to Viewmodel's Property

Twoway-bind view's DependencyProperty to viewmodel's property?

I use Caliburn.Micro for separating the ViewModel from the View. Still, it might work the same way in MVVM. I guess MVVM sets the view's DataContext property to the instance of the ViewModel, either.

VIEW

// in the class of the view: MyView
public string ViewModelString // the property which stays in sync with VM's property
{
get { return (string)GetValue(ViewModelStringProperty); }
set
{
var oldValue = (string) GetValue(ViewModelStringProperty);
if (oldValue != value) SetValue(ViewModelStringProperty, value);
}
}

public static readonly DependencyProperty ViewModelStringProperty =
DependencyProperty.Register(
"ViewModelString",
typeof(string),
typeof(MyView),
new PropertyMetadata(OnStringValueChanged)
);

private static void OnStringValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
// do some custom stuff, if needed
// if not, just pass null instead of a delegate
}

public MyView()
{
InitializeComponent();
// This is the binding, which binds the property of the VM
// to your dep. property.
// My convention is give my property wrapper in the view the same
// name as the property in the VM has.
var nameOfPropertyInVm = "ViewModelString"
var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
this.SetBinding(SearchStringProperty, binding);
}

VM

// in the class of the ViewModel: MyViewModel
public string ViewModelStringProperty { get; set; }

Note, that this kind of implementation lacks completely of implementation of the INotifyPropertyChanged interface. You'd need to update this code properly.

Writing back a dependency property value to the view model in two-way binding

Unless I'm overlooking something, you just need to do:

private JSValue MapZoom_OnMapZoomChanged(JSValue[] arguments)
{
string newZoom= arguments[0];
this.Zoom = newZoom; // here
return null;
}

The dependency property pattern is setup so that from the outside you can just treat Zoom as a normal c# property (setter and getter). If you look at the setter for that property it calls SetValue(), which is on the DependencyObject base class and does the magic to notify the binding engine of the change (also see: Register())

Update

A simple way to prevent the change from sending the update to the map when the map was the one providing the change in the first place:

private bool IsMapUpdateSuppressed = false;

private JSValue MapZoom_OnMapZoomChanged(JSValue[] arguments)
{
string newZoom= arguments[0];
this.IsMapUpdateSuppressed = true;
this.Zoom = newZoom;
this.IsMapUpdateSuppressed = false;
return null;
}

private static void OnZoomPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
GoogleMap control = source as GoogleMap;
if (!control.IsMapUpdateSuppressed)
{
control.SetZoom(e.NewValue.ToString());
}
}

This allows the DP value to change, process, and notify the binding engine as usual, but just prevents the call to SetZoom

Two Way binding to a Dependency Property in a User Control and call a method

Implement INotifyPropertyChanged in your ViewModel

public class Sample : INotifyPropertyChanged
{
private string input = string.Empty;
public string Input
{
get
{
return input;
}
set
{
input = value;
NotifyPropertyChanged("Input");
InputChanged();
}
}

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

}

In your case, you can do it in the code behind of your usercontrol

WPF - How to keep Dependency Property and View Model property in sync?

I can see that your code is passing the IntValue from MainWindowViewModel's property to IncrementIntView's dependency property to IncrementIntViewModel's property.

The increment button is updating the IncrementIntViewModel's IntValue property. Unfortunately, whatever happens in the IncrementIntViewModel is not being reflected back to the IncrementIntView's IntValue dependency property. The TwoWay Mode is not between IncrementIntView's dependency property and IncrementIntViewModel's property, but it is between MainWindowViewModel's property to IncrementIntView's dependency property.

The easy solution: Bind the MainWindow's Label to IncrementIntViewModel's IntValue property without bothering the View's property.

<local:IncrementIntView x:Name="iiv" IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
<Label Content="{Binding DataContext.IntValue, ElementName=iiv}" />
<!--You need to specify DataContext.IntValue, because you have same name for both view's dependency property and viewmodel's property-->

Here you can see that MainWindowViewModel's IntValue is not that important, because it just passes the value to IncrementIntViewModel once and never have the value updated ever.

The other solution: You need to trigger value change back to MainViewModel's property.

First thing first, there is no connection between MainViewModel and IncrementIntViewModel. One solution is to make the MainViewModel to be singleton, so that when increment is done inside the IncrementIntViewModel, you want to update the MainViewModel's property as well.

In MainViewModel.cs

public static MainWindowViewModel SingletonInstance { get; set; }
public MainWindowViewModel()
{
if (SingletonInstance == null)
{
SingletonInstance = this;
}
}

In IncrementIntViewModel.cs

public void IncrementInt()
{
IntValue++;
MainWindowViewModel.SingletonInstance.IntValue = IntValue;
}

The other other solution: Similar to the above solution, but we don't need to make Singleton instance of MainWindowViewModel, because MainWindow is singleton to begin with.

In IncrementIntViewModel.cs

public void IncrementInt()
{
IntValue++;
((MainWindowViewModel)App.Current.MainWindow.DataContext).IntValue = IntValue;
}

If your intention is to update the IntValue from IncrementViewModel's property to IncrementView's dependency property, then you might ask why you need to do this, because MVVM is supposed to separate between V and VM. V should be looking to VM, but not the other way around.

WPF CustomControl unable to bind dependency property two way to view model

The reason for why you code does not work as expected is that you're assigning new value to your StringProperty while still handling the previous change to that property. I don't really know the mechanics behind that (possibly it's some kind of mechanism meant to prevent potentially infinite recursive calls?), but I am 100% that that is the culprit.

To solve your problem it is sufficient to defer new value assignment until the control is returned from your handler, which can be easily achieved by using the Dispatcher associated with your control:

private static void PropertyChangedCallback(DependencyObject dO,
DependencyPropertyChangedEventArgs e)
{
var textBox = (CustomTextBox) dO;
if (textBox.StringProperty == null)
return;
DoSomething(textBox.StringProperty)
//following lambda will be queued for execution rather than executed immediately
//and is guaranteed to be executed after this handler has finished
textBox.Dispatcher.InvokeAsync(() => textBox.StringProperty = null);
}

If you're using .NET version prior to 4.5 you will need to use Dispatcher.BeginInvoke method instead.

WPF usercontrol Twoway binding Dependency Property

Bind the TextBox.Text property in the UserControl to its SampleProperty like this:

<TextBox Text="{Binding SampleProperty,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>

Now you could simply remove your OnSamplePropertyChanged callback.


You might also register SampleProperty to bind two-way by default like this:

public static readonly DependencyProperty
SamplePropertyProperty = DependencyProperty.Register(
"SampleProperty", typeof(string), typeof(UserControl1),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

Bind ViewModel's property to UserControl's child control and itself DependencyProperty

The view model property setter is not implemented correctly.

It should look like shown below, i.e. use the correct property name nameof(VoltInVm) and probably not check m_V != value before calling Set, because that is already done by the Set method.

private int voltInVm;

public int VoltInVm
{
get { return voltInVm; }
set { Set<int>(ref voltInVm, value, nameof(VoltInVm)); }
}

When you now bind the UserControl's property like

<local:Unit Volt="{Binding VoltInVm}"/>

the PropertyChangedCallback of the Volt dependency property will be called each time the VoltInVm property changes its value.

It is however unclear what (o, e) => ((Unit)o).OnVoltChanged(o, e) in the dependency property registration is supposed to be. It should certainly look like this:

(o, e) => ((Unit)o).OnVoltChanged((int)e.NewValue)

and the method should be declared with an int argument instead of double:

private void OnVoltChanged(int volt) ...

Or - certainly better - change all voltage property types to double, in the control and in the view model.



Related Topics



Leave a reply



Submit