I am working on a WPF application using reactiveui, and am having difficulty getting two way binding working in my custom dependency property. I can get this working using WPF binding but not using the reactiveui.
I have a user control called IPAddressControl, which takes in an IP address input and provides a dependency property ValidIpAddress, shown below.
public static readonly DependencyProperty ValidIpAddressProperty =
DependencyProperty.Register(
"ValidIpAddress",
typeof(bool),
typeof(IPAddressControl),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnValidIpAddressPropertyChanged)));
public bool ValidIpAddress
{
get
{
return (bool)GetValue(ValidIpAddressProperty);
}
set
{
SetValue(ValidIpAddressProperty, value);
}
}
private static void OnValidIpAddressPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IPAddressControl IPAddressControl = d as IPAddressControl;
IPAddressControl.ValidIpAddress = (bool)e.NewValue;
}
In my view, I set up the binding as follows :
public partial class MyView : ReactiveUserControl<MyViewModel>
public MyView()
{
InitializeComponent();
ViewModel = new MyViewModel();
this.WhenActivated(view =>
{
this.Bind(ViewModel, vm => vm.ValidIpAddress, v => v.ipaddress.ValidIpAddress);
});
}
And define my view model property
public class MyViewModel : ReactiveObject
{
bool _validIpAddress = false;
public bool ValidIpAddress
{
get => this._validIpAddress;
set => this.RaiseAndSetIfChanged(ref _validIpAddress, value);
}
}
When the property gets updated in IPAddressControl
IPAddressControl.ValidIPaddress = true;
I would expect MyViewModel.ValidIpAddress.Set to be called, however this is not happening.
When I revert to the standard WPF implementation, the two way binding works well, i.e. I set the binding in the myview.xaml
ValidIpAddress="{Binding Path=ValidIpAddress}"
then set the data context in constructor MyView();
this.DataContext = new MyViewModel()
So it appears I am doing something wrong in relation to the reactiveui binding.
I'd appreciate if someone could help.
Thanks.
Related
I have a main WPF window which will host a bunch of UserControls.
I want Every UC to have there own logic so I added a VM like this :
UC Constructor
public IfcUpdaterViewModel IfcUpdaterViewModel;
public IfcUpdaterView()
{
IfcUpdaterViewModel = new IfcUpdaterViewModel();
DataContext = IfcUpdaterViewModel;
InitializeComponent();
}
And I added a ICommand DP to bind it in the main form like this :
UC Dependency
public static readonly DependencyProperty UpdateCommandProperty =
DependencyProperty.Register("UpdateCommand", typeof(ICommand), typeof(IfcUpdaterView), new UIPropertyMetadata());
/// <summary>
/// Dependency to bind the update command
/// </summary>
public ICommand UpdateCommand
{
get => (ICommand) GetValue(UpdateCommandProperty);
set => SetValue(UpdateCommandProperty, value);
}
the binding is set with this :
MainForm
public void ToDocument()
{
_ifcUpdater = new IfcUpdaterView();
MainPresenter.Content = _ifcUpdater;
_ifcUpdater.SetBinding(IfcUpdaterView.UpdateCommandProperty, new Binding(nameof(MainDataContext.UpdateIfcFile))
{
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
}
But with this the command binding isn't working. but if I remove the DataContext the binding working fine but I loose my VM logic.
Is there a way to combine both ?
I have followed several tutorials on consuming native views in Xamarin.Forms, But I didn't succeed in Binding Command to From ViewModel to the Native View.
Here is the code for the custom Native control in android:
public class MyFAB : FloatingActionButton
{
public Command Command { get; set; }
public MyFAB (Context context) : base(context)
{
this.SetImageResource(Resource.Drawable.ic_add_white_24dp);
Click += (sender, e) =>
{
Command?.Execute(null);
};
}
}
Here is the Xaml Code:
<droidCustom:AddFAB x:Arguments="{x:Static formsDroid:Forms.Context}" UseCompatPadding="true" Command="{Binding AddCategoryCommand}"
AbsoluteLayout.LayoutBounds="1,1,AutoSize,AutoSize" AbsoluteLayout.LayoutFlags="PositionProportional"/>
The View is shown properly, but the command is not fired, and when I debug, the Command is never assigned it is always null.
When I go through blog posts online, they say the command binding will not need any bindable property... but here I still get issues.
You've created a simple Command property. To achieve this, you must create a BindableProperty instead.
Change the Command's property declaration to this and it should work:
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(MyFAB), null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Adding support to command parameters
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(MyFAB), null);
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
And the Click handler:
Click += (sender, e) =>
{
Command?.Execute(CommandParameter);
};
I hope it helps you. Take a look at the official Microsoft docs about BindablePropperties to more detailed explanation
Given I have this UserControl:
public class MyStringUserControl : UserControl
{
public string MyString
{
get { return (string)GetValue(MyStringProperty); }
set { SetValue(MyStringProperty, value); }
}
public static readonly DependencyProperty MyStringProperty =
DependencyProperty.Register("MyString", typeof(string), typeof(MyStringUserControl),
new FrameworkPropertyMetadata(null));
}
And this ViewModel:
public class MyStringViewModel
{
public string MyString { get; set; }
}
Now I use the MyStringUserControl in another View like this:
<controls:MyStringUserControl MyString="{Binding SomeStringProperty} />
I'am looking for an elegant way to bind this string back to the MyStringViewModel.
I also don't feel comfortable with the fact that I have to duplicate every property in the UserControl code behind and ViewModel. Is there a better way to do this?
Edit #1:
The reason I want to do this is because of unit testing (creating a UserControl takes very long even without InitializeComponent)
There is absolutely no point in duplicating your properties. Using MVVM does not mean that you need to have a view model for every UserControl. When I use a UserControl as a part of a view, I rarely use a separate view model for it. Sometimes, I'll just use the DependencyPropertys in the UserControl code behind, while other times I'll just data bind to the parent view model directly. It all depends on what you want to do with the data.
If I was intent on doing this, I would use DependencyProperty's PropertyChanged event handler to set my ViewModel's property, and my ViewModel's PropertyChanged event handler to set my DependencyProperty. Having said that I've never had a reason to go down this particular road.
private SomeViewModel _viewModel;
public static readonly DependencyProperty MyStringProperty = DependencyProperty.Register("MyString", typeof(string), typeof(MyStringUserControl), new PropertyMetadata(OnMyStringChanged));
public MyStringUserControl()
{
InitializeComponent();
_viewModel = new SomeViewModel();
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
this.DataContext = _viewModel;
}
private static void OnMyStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyStringUserControl)d).OnMyStringChanged(e.NewValue);
}
private void OnMyStringChanged(string newValue)
{
_viewModel.SomeProperty = newValue;
}
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SomeProperty":
SetValue(MyStringProperty, _viewModel.SomeProperty);
break;
default:
break;
}
}
I have created a custom TextEditor control that inherits from AvalonEdit. I have done this to facilitate the use of MVVM and Caliburn Micro using this editor control. The [cut down for display purposes] MvvTextEditor class is
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public MvvmTextEditor()
{
TextArea.SelectionChanged += TextArea_SelectionChanged;
}
void TextArea_SelectionChanged(object sender, EventArgs e)
{
this.SelectionStart = SelectionStart;
this.SelectionLength = SelectionLength;
}
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.SelectionLength = (int)args.NewValue;
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
set { SetValue(SelectionLengthProperty, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Now, in the view that holds this control, I have the following XAML:
<Controls:MvvmTextEditor
Caliburn:Message.Attach="[Event TextChanged] = [Action DocumentChanged()]"
TextLocation="{Binding TextLocation, Mode=TwoWay}"
SyntaxHighlighting="{Binding HighlightingDefinition}"
SelectionLength="{Binding SelectionLength,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
Document="{Binding Document, Mode=TwoWay}"/>
My issue is SelectionLength (and SelectionStart but let us just consider the length for now as the problem is the same). If I selected something with the mouse, the binding from the View to my View Model works great. Now, I have written a find and replace utility and I want to set the SelectionLength (which has get and set available in the TextEditor control) from the code behind. In my View Model I am simply setting SelectionLength = 50, I implement this in the View Model like
private int selectionLength;
public int SelectionLength
{
get { return selectionLength; }
set
{
if (selectionLength == value)
return;
selectionLength = value;
Console.WriteLine(String.Format("Selection Length = {0}", selectionLength));
NotifyOfPropertyChange(() => SelectionLength);
}
}
when I set SelectionLength = 50, the DependencyProperty SelectionLengthProperty does not get updated in the MvvmTextEditor class, it is like the TwoWay binding to my control is failing but using Snoop there is no sign of this. I thought this would just work via the binding, but this does not seem to be the case.
Is there something simple I am missing, or will I have to set up and event handler in the MvvmTextEditor class which listens for changes in my View Model and updated the DP itself [which presents it's own problems]?
Thanks for your time.
This is because the Getter and Setter from a DependencyProperty is only a .NET Wrapper. The Framework will use the GetValue and SetValue itself.
What you can try is to access the PropertyChangedCallback from your DependencyProperty and there set the correct Value.
public int SelectionLength
{
get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectionLength. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor), new PropertyMetadata(0,SelectionLengthPropertyChanged));
private static void SelectionLengthPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textEditor = obj as MvvmTextEditor;
textEditor.SelectionLength = e.NewValue;
}
Here is another answer if you are still open. Since SelectionLength is already defined as a dependency property on the base class, rather than create a derived class (or add an already existing property to the derived class), I would use an attached property to achieve the same functionality.
The key is to use System.ComponentModel.DependencyPropertyDescriptor to subscribe to the change event of the already existing SelectionLength dependency property and then take your desired action in the event handler.
Sample code below:
public class SomeBehavior
{
public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(SomeBehavior), new PropertyMetadata(OnIsEnabledChanged));
public static void SetIsEnabled(DependencyObject dpo, bool value)
{
dpo.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsEnabledProperty);
}
private static void OnIsEnabledChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var editor = dpo as TextEditor;
if (editor == null)
return;
var dpDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(TextEditor.SelectionLengthProperty,editor.GetType());
dpDescriptor.AddValueChanged(editor, OnSelectionLengthChanged);
}
private static void OnSelectionLengthChanged(object sender, EventArgs e)
{
var editor = (TextEditor)sender;
editor.Select(editor.SelectionStart, editor.SelectionLength);
}
}
Xaml below:
<Controls:TextEditor Behaviors:SomeBehavior.IsEnabled="True">
</Controls:TextEditor>
This is how I did this...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
if (target.SelectionLength != (int)args.NewValue)
{
target.SelectionLength = (int)args.NewValue;
target.Select(target.SelectionStart, (int)args.NewValue);
}
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
//get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
Sorry for any time wasted. I hope this helps someone else...
Sorry to be cliche... but I'm pretty new to WPF and MVVM so I'm not sure how to handle this properly. I have a WinForms control within one of my views that I need to modify in it's code behind when an event is raised in the ViewModel. My view's datacontext is inherited so the viewmodel is not defined in the views constructor. How would I go about properly handling this? I am not using any frameworks with built in messengers or aggregators. My relevant code is below. I need to fire the ChangeUrl method from my ViewModel.
EDIT: Based on the suggestion from HighCore, I have updated my code. I am still not able to execute the ChangeUrl method however, the event is being raised in my ViewModel. What modifications need to be made??
UserControl.xaml
<UserControl ...>
<WindowsFormsHost>
<vlc:AxVLCPlugin2 x:Name="VlcPlayerObject" />
</WindowsFormsHost>
</UserControl>
UserControl.cs
public partial class VlcPlayer : UserControl
{
public VlcPlayer()
{
InitializeComponent();
}
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set
{
ChangeVlcUrl(value);
SetValue(VlcUrlProperty, value);
}
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null));
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}
}
view.xaml
<wuc:VlcPlayer VlcUrl="{Binding Path=ScreenVlcUrl}" />
ViewModel
private string screenVlcUrl;
public string ScreenVlcUrl
{
get { return screenVlcUrl; }
set
{
screenVlcUrl = value;
RaisePropertyChangedEvent("ScreenVlcUrl");
}
}
WPF does not execute your property setter when you Bind the property, instead you must define a Callback method in the DependencyProperty declaration:
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set { SetValue(VlcUrlProperty, value); }
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null, OnVlcUrlChanged));
private static void OnVlcUrlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var player = obj as VlcPlayer;
if (obj == null)
return;
obj.ChangeVlcUrl(e.NewValue);
}
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}