Bindable Native Views in XAML - With Commands!?!
Using the UpdateSourceEventName
attribute, you can introduce two way binding by having Forms listen for the event specified and then update the bindings when said event is fired.
However, that doesn’t help us when our app needs to react and take an action when an event occurs on the native view embedded within the XAML. So what’s a developer to do?
One way around this is to embed the native control in the code behind file and then all the events will be available for use. Handle that event, then invoke whatever command you want in your view-model.
That works – but it isn’t optimal. You have to mix code-behind with XAML … AND … either use Shared Projects or make creative use of partial classes. There has to be a better way … and there is …
Commanding From the Native View! Or Subclassing to the Rescue!
The solution to the problem really is rather simple and it evolves subclassing. All the demo code and a working solution can be found on GitHub here.
In the previous post on native XAML embedding with data binding, I mentioned there was a new extension method called SetBinding
on UIView
and Android.View
objects. It’s this method the Xamarin.Forms XAML parser calls when it comes across a native property definition specified in the XAML to provide data binding.
So … what would happen if the XAML parser would come across a property in the native control (which derives from either UIView
or Android.View
) that’s of a Command
type? Why, it would call SetBinding
on that property and then we would have data binding setup on a Command
– thus enabling commanding to a view model!
All we have to do is subclass whatever view we want to provide commanding for, add a Command
property, and then handle events appropriately to invoke the Execute
method of that Command
.
Let’s take a look at the code.
The View-Model
We’re going to build an app that pops a modal dialog onto the stack when a Droid FAB gets tapped, or an iOS look-alike FAB gets tapped.
That popping will happen when a Command
gets invoked within a view model. If we take a look at a portion of the view model code, we’ll see that there’s a property which controls whether or not the modal should be shown, and then the Command
which actually shows it.
bool _canShowModal;
public bool CanShowModal
{
get
{
return _canShowModal;
}
set
{
_canShowModal = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CanShowModal)));
ShowModalCommand.ChangeCanExecute();
}
}
Command _showModal;
public Command ShowModalCommand
{
get
{
if (_showModal == null)
{
_showModal = new Command(async () =>
{
var modalPage = new ModalPage();
await _nav.PushModalAsync(new NavigationPage(modalPage), true);
}, () =>
{
return CanShowModal;
});
}
return _showModal;
}
}
Note that the Command
also has a function to indicate whether the Command
can execute or not.
Setting Up The Platform Code
As I mentioned above, the key to this all is to subclass whatever native/platform specific view you want to host the command, and then add a Command
property to it.
For iOS, I created a simple class that inherits from UIView
which looks like a FAB and raises simple events whenever a touch on it ends. (The implementation is very simplistic, but will demonstrate everything we need.)
Then I created a subclass of that FAB look-alike class. It’s in there that I created a new Command
property. There’s nothing special about that property – no backing BindableProperty
or anything like that – it’s a plain C# property.
Then in the constructor I handle the event raised by the underlying control, check if the Command
property has been set, and if so, execute it.
The code looks like the following:
public class NativeXamlCmdButton : FabButton
{
public Command Command { get; set; }
public NativeXamlCmdButton()
{
ButtonPressed += (sender, e) =>
{
Command?.Execute(null);
};
}
}
Yeah, that really is it. Granted this code isn’t doing anything if the Command
shouldn’t be able to be executed … we’ll do that in the Droid version.
Speaking of the Droid version, here it is. This subclasses a FloatingActionButton
. When it’s enabled and sending commands – it shows a plus sign. When it’s not, it shows an X.
public class NativeXamlCmdButton : FloatingActionButton
{
bool canExecute = true;
public Command Command { get; set; }
public NativeXamlCmdButton() : base(Forms.Context)
{
SetImageResource(Resource.Drawable.ic_add_white_24dp);
Click += (sender, e) =>
{
if (canExecute)
Command?.Execute(null);
};
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
// Respond to changes in whether the command can execute or not
if (Command != null)
{
Command.CanExecuteChanged += (sender, e) =>
{
if (Command.CanExecute(null))
{
canExecute = true;
SetImageResource(Resource.Drawable.ic_add_white_24dp);
}
else
{
SetImageResource(Resource.Drawable.ic_highlight_off_white_24dp);
canExecute = false;
}
};
}
}
}
There’s definitely more going on here however. It’s still handling the Clicked
event and executing the Command
. However, it also handles the CanExecuteChanged
event. This way whenever the view model says the execution state has changed, this FAB can update its visual state (in this case, changing the image) and no longer executing the Command itself.
Consuming the Command
This is the easy part! It’s the same as it ever was – using the familiar XAML {Binding } syntax with properties on the class, map the Command
property on our new subclasses to whatever command you need to in the view model.
<iOS:NativeXamlCmdButton Command="{Binding ShowModalCommand}"
View.HorizontalOptions="Center" View.VerticalOptions="Center" />
<droid:NativeXamlCmdButton Command="{Binding ShowModalCommand}"
View.HorizontalOptions="Center" View.VerticalOptions="Center" />
That’s all there is!
Summary
There’s a little extra work involved – but it’s definitely worth it to subclass any native views that you want to access directly in XAML – to gain access to events that you want to tie Commands
to.
It can be as easy as creating a public Command
property – or with a little more work responding to CanExecuteChanged
events to control the native view’s UI state. And it wouldn’t be much more work to pass the CommandParameter
along as well.
All of this is made possible by the new extension methods on UIView
and Android.View
provided by the 2.3.3 version of Xamarin.Forms that the XAML parser automatically puts onto any exposed properties in the XAML definition.
Native view XAML embedding with data binding was already awesome – now it’s even more so with commanding!
All the demo code can be found on GitHub here.