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
Related
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.
My English skill is poor because I'm not a native English speaker.
I have created as following a behavior that working at the TextBox control.
The behavior has a collection-type DP named Items.
class HighlightBehavior : Behavior<TextBox>
{
public List<TextStyle>Items
{
get { return (List<TextStyle>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(List<TextStyle>), typeof(HighlightBehavior), new PropertyMetadata(ItemsChanged));
private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// break point
}
}
And... I have created a MainWindow to use as following code above behavior.
<MainWindow>
<TextBox>
<i:interaction.Behaviors>
<behavior:HighlightBehavior/>
</i:interaction.Behavior>
</TextBox>
</MainWindow>
And I have written a MainWindowViewModel that has a collection-type DP named HighlightItems.
class MainWindowViewModel : ViewModelBase
{
public List<TextStyle> HighlightItems
{
get { return (List<TextStyle>)GetValue(HighlightItemsProperty ); }
set { SetValue(HighlightItemsProperty , value); }
}
public static readonly DependencyProperty HighlightItemsProperty =
DependencyProperty.Register("HighlightItems", typeof(List<TextStyle>), typeof(HighlightBehavior), new PropertyMetadata(null));
public MainWindowViewModel()
{
SetValue(HighlightItemsProperty, new List<TextStyle>());
}
}
And I have bound the MainWindowViewModel to MainWindow and connected HighlightItems(DP) of MainWindowViewModel with Items(DP) of HighlightBehavior as the following code.
<MainWindow>
<TextBox>
<i:interaction.Behaviors>
<behavior:HighlightBehavior Items="{Binding HighlightItems, Mode=TwoWay}"/>
</i:interaction.Behavior>
</TextBox>
</MainWindow>
To sum up, the structure is the following figure.
I have expected that ItemsChanged of HighlightBehavior is called whenever Items changed.
But it is not called.
I want to get notification whenever collection-type DP(Items) of HighlightBehavior is changed.
What must I do to reach this goal?
Thank you for reading.
I'll wait for an answer.
I believe what you're looking for is ObservableCollection. This is a special type of collection which raises its CollectionChanged event whenever an item is added, removed, changed or moved.
I recommend the following:
Instead of declaring HighlightItems as List<TextStyle>, declare it as ObservableCollection<TextStyle>.
Add another method to HighlightBehavior to handle CollectionChanged, for example:
HighlightItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
Your current implementation of ItemsChanged will be called whenever HighlightItems is set. Use that to attach an event handler to CollectionChanged like so:
var col = (ObservableCollection<TextStyle>)e.NewValue;
if (col != null) { col.CollectionChanged += HighlightItemsCollectionChanged; }
Don't forget to remove any existing event handler to the previous collection in case HighlightItems is set move than once. You can add this to ItemsChanged along with the previous snippet:
col = (ObservableCollection<TextStyle>)e.OldValue;
if (col != null) { col.CollectionChanged -= HighlightItemsCollectionChanged; }
HighlightItemsCollectionChanged will now be called whenever an item is added or removed from HighlightItems. Do whatever you need to do in this method, or if you want the code to also run when the collection itself is replaced, you can make another method that actually does what you want, and then call that method from both ItemsChanged and HighlightItemsCollectionChanged.
Thank you.
I have changed the code following your advice and now I can receive a notification when the element of the collection is changed.
I knew about the ObservableCollection but I didn't know how to use right about CollectionChanged event.
In fact, previous I tried to use the ObservableCollection and registered the CollectionChanged delegate method at the Constructer as following but it is not called.
public ObservableCollection<TextStyle> Items
{
get { return (ObservableCollection<TextStyle>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
// Using a DependencyProperty as the backing store for Items. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<TextStyle>), typeof(HighlightBehavior),
new PropertyMetaData(null));
public HighlightBehavior()
{
SetValue(ItemsProperty, new ObservableCollection<TextStyle>());
Items.CollectionChanged += OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// The code when the collection is changed.
}
Now, I have registered the CollectionChanged delegate method in the PropertyChangedCallback method as following and it(OnCollectionChanged method at the following code) is called.
public ObservableCollection<TextStyle> Items
{
get { return (ObservableCollection<TextStyle>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
// Using a DependencyProperty as the backing store for Items. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<TextStyle>), typeof(HighlightBehavior),
new PropertyMetaData(ItemsChanged));
private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var col = (ObservableCollection<TextStyle>)e.NewValue;
if (col != null) { col.CollectionChanged += OnCollectionChanged; ; }
col = (ObservableCollection<TextStyle>)e.OldValue;
if (col != null) { col.CollectionChanged -= OnCollectionChanged; }
}
public HighlightBehavior()
{
SetValue(ItemsProperty, new ObservableCollection<TextStyle>());
}
Thank you for your answer in detail.
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;
}
}
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
}
I have a view model that provides a RelayCommand LoadImage.
Typically I would use a button and bind the command to this button.
However I would like to call the LoadImage command from view's codebehind (I need to do some view related stuff that must not be put into view model)
The one way I am aware is to create an event handler for the button, e.g. Button_Click.
In Button_Click I would cast DataContext to the corresponding ViewModel and use this instance to call (DataContext as MyViewModel).LoadImage.Execute(...)
This is odd as I need to know the view model.
What I am trying, is to bind LoadImage not to a button but to a resource in the view, so the Button_Click event just need to call FindResource with a given name and cast it to ICommand without the necessity to know the specific ViewModel.
Is this possible? The command itself is not static as it needs to know the context in what it is called.
You can make it by creating a behavior, which requires Prism referred in your project:
public class LoadImageBehavior : Behavior<Button>
{
public public static static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof (ICommand), typeof (LoadImageBehavior));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += AssociatedObject_Click;
}
private void AssociatedObject_Click(object sender, RoutedEventArgs e)
{
//Logic...
if(Command != null && Command.CanExecute(null))
Command.Execute(null);
//Logic...
}
}
On Xaml:
<Button>
<i:Interaction.Behaviors>
<Behaviors:LoadImageBehavior Command="{Binding LoadImageCommand}"/>
</i:Interaction.Behaviors>
</Button>
Based on Bill Zhangs idea of behaviours I've created a generic version which is quite control agnostic and which allows to be reused.
The required assembly is
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
I've created a Trigger action that passes the execution along to an event handler:
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using System;
namespace Misc
{
public class CommandWithEventAction : TriggerAction<UIElement>
{
public event Func<object, object> Execute;
public static DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandWithEventAction), null);
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
public static DependencyProperty ParameterProperty = DependencyProperty.Register("Parameter", typeof(object), typeof(CommandWithEventAction), null);
public object Parameter
{
get
{
return GetValue(ParameterProperty);
}
set
{
SetValue(ParameterProperty, value);
}
}
protected override void Invoke(object parameter)
{
var result = Execute(Parameter);
Command.Execute(result);
}
}
}
To avoid any logic in a custom behaviour this allows to hook up any event to an event callback followed by a command call.
XAML:
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<misc:CommandWithEventAction Command="{Binding LoadImageCommand}" Parameter="Custom data" Execute="CommandWithEventAction_OnExecute"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Execute
</Button>
This will pass the "Custom data" string boxed as object to a function called
CommandWithEventAction_OnExecute
its signature of Func<object,object> may use the parameter and need to return something that will then be boxed into object and passed to the LoadImageCommand