MvvmCross: GestureRecognized binding to ViewModel action - iphone

There is such ability to bind buttons actions directly like this:
var set = this.CreateBindingSet<...
set.Bind(button).To(x => x.Go);
but what's about UITapGestureRecognizer, for instance. How should I bind it (it's tap action) in such elegant way?
Thank you!

Just for reference. Newer version of MvvMcross includes a UIView method extension (see MvxTapGestureRecognizerBehaviour) out of the box that you can use to bind the tap gesture:
using Cirrious.MvvmCross.Binding.Touch.Views.Gestures;
// in this case "Photo" is an MvxImageView
set.Bind(Photo.Tap()).For(tap => tap.Command).To("OpenImage");

You could add this yourself if you wanted to.
e.g. something like
public class TapBehaviour
{
public ICommand Command { get;set; }
public TapBehaviour(UIView view)
{
var tap = new UITapGestureRecognizer(() =>
{
var command = Command;
if (command != null)
command.Execute(null);
});
view.AddGestureRecognizer(tap);
}
}
public static class BehaviourExtensions
{
public static TapBehaviour Tap(this UIView view)
{
return new TapBehaviour(view);
}
}
// binding
set.Bind(label.Tap()).For(tap => tap.Command).To(x => x.Go);
I think that would work - but this is coding live here!
Advanced> If you wanted to, you could also remove the need for the For(tap => tap.Command) part by registering a default binding property for TapBehaviour - to do this override Setup.FillBindingNames and use:
registry.AddOrOverwrite(typeof (TapBehaviour), "Command");
After this, then the binding could be:
set.Bind(label.Tap()).To(x => x.Go);

Related

MAUI GraphicsView mouse click event

I'm new to .Net Maui. Trying to draw my own shapes using GraphicsView and IDrawable. I would like to have the shapes clickable.
Is there a way how to achieve that? Maybe there is another interface something like IClickable I'm not aware of. Or I should use different approach thanGraphicsView.
Thanks :-)
I was able to get this to work using the TapGestureRecognizer and a class that implements ICommand.
In this example I have the GraphicsView as a member called _view in the same class that implements both IDrawable and ICommand, but your design could be different and it should still work.
public class Block : IDrawable, ICommand
{
protected GraphicsView _view = new GraphicsView();
public Block()
{
_view.Drawable = this;
_view.GestureRecognizers.Add(new TapGestureRecognizer
{
Command = this
});
}
void ICommand.Execute(object cmdObject)
{
// Handle the Clicked event here
}
bool ICommand.CanExecute(object cmdObject)
{
return true;
}
event EventHandler ICommand.CanExecuteChanged
{
add { }
remove { }
}
public void Draw(ICanvas canvas, RectF dirtyRect)
{
// Draw on canvas here
}
}

Change a ViewModel Property from a different class and update the View - MVVM

I need to change the Visibility of a Button on a View from method call from within a class.
I have tried accessing the VeiwModel by exposing it in the class, and then had success in changing the Property "ShowRedHat" from true to false, but this does not update the Visibility of the Button in the View. This also double loads the ViewModel, which is not acceptable in my solution.
Any help is appreciated.
The class:
public class HatEngine
{
public void SetShowRedHat()
{
????.ShowRedHat = false;
}
}
The Property in the ViewModel:
public class MyViewModel : ObservableObject
{
private bool _showRedHat;
public bool ShowRedHat
{
get { return _showRedHat; }
set
{
OnPropertyChanged(ref _showRedHat, value);
}
}
}
The Button in the View:
<Button Content="Red Hat"
Command="{Binding RedHatCommand}"
Visibility="{Binding ShowRedHat, Converter={StaticResource BoolToVis}}"/>
If the purpose of HatEngine is to be a service that is used by MyViewModel, then something like the following be the start of getting what you need.
This example uses dependency injection via the constructor; this is common in MVVM and if you're not familiar with it, I would highly recommend looking into it further.
// define delegate for event to be fired from HatEngine instances
public delegate void HatEngineNotifyEventHandler(object sender, bool shouldShow);
// interface declaration for HatEngine - this is important for injecting mocks for unit testing
public interface IHatEngine
{
event HatEngineNotifyEventHandler Notify;
void SetShowRedHat(bool show);
}
// simple IHatEngine implementation
public sealed class HatEngine : IHatEngine
{
public event HatEngineNotifyEventHandler Notify;
public void SetShowRedHat(bool show) => OnNotify(show);
private void OnNotify(bool shouldShow) =>
Notify?.Invoke(this, shouldShow);
}
public class MyViewModel : ObservableObject
{
private readonly IHatEngine _hatEngine;
private bool _showRedHat;
// MyViewModel consumes an IHatEngine instance and subscribes to its Notify event
public MyViewModel(IHatEngine hatEngine = null)
{
// many MVVM frameworks include a DI container that should be used here
// to resolve an IHatEngine instance; however, for simplicity for this
// example just create HatEngine() directly
_hatEngine = hatEngine ?? new HatEngine();
// when the event is received, update ShowRedHat accordingly
_hatEngine.Notify += (_, shouldShow) => ShowRedHat = shouldShow;
}
public bool ShowRedHat
{
get => _showRedHat;
set => OnPropertyChanged(ref _showRedHat, value);
}
}
You can just bind an integer since Visibility is an Enum, check documentation since in some versions Hidden option is not available and Collapsed becomes 1, however normally you can just use these below:
Visible [0] - Display the element.
Hidden [1] Do not display the element, but reserve space for the
element in layout.
Collapsed [2] Do not display the element, and do not reserve space for
it in layout.

Dynamic Binding UIWebView in MVVMCross

I'm trying to make a change to sample project Cirrious.Conference. In particular in the Touch View at SessionView class and at this class
https://github.com/slodge/MvvmCross-Tutorials/blob/master/Sample%20-%20CirriousConference/Cirrious.Conference.Core/ViewModels/SessionLists/BaseSessionListViewModel.cs
on method
protected void NavigateToSession(Session session)
{
ShowViewModel<SessionViewModel>(new { key = session.Key });
}
I'd like to openl a UIWebView (in app) binding LoadRequest with a property of class Session (suppose to have a Property URL...). I have created a UIWebView object in the SessionView but it's not possible to create a Swisse Binding...Maybe it's possible with a customBinding...
How could i'll do it?
Since UIWebView doesn't expose a property for the LoadRequest, then you can't bind directly to it.
If you want to use binding for LoadRequest, then 3 options available to you are:
Inherit MyWebView from UIWebView, add a C# property that drives LoadRequest and then use that class in your UI and that property in your Swiss binding - e.g.:
[Register("MyWebView")]
public class MyWebView : UIWebView
{
public MyWebView()
{
}
public MyWebView(IntPtr handle) : base(handle)
{
}
private string _myUrl;
public string MyUrl
{
get { return _myUrl; }
set
{
if (_myUrl == value) return;
_myUrl = value;
LoadRequest(value); // or similar (I've not checked the syntax!)
}
}
}
Implement a custom target Swiss binding and add it to your Setup.cs. The process for this is described in this Custom Bindings presentation - which also includes links to some examples (one of them is in the Conference app)
If this property will never change, then don't use binding and instead just call LoadRequest in your MvxViewController ViewDidLoad - e.g.
public void ViewDidLoad()
{
base.ViewDidLoad();
var myViewModel = (MyViewModel)ViewModel;
var url = myViewModel.Url;
TheWebView.LoadRequest(url);
}

mvvmcross custom binding to eventhandler

I am trying to implement LongClick functionality on a view and read the following which provided some info
mvvmcross touch command binding in android
Searched unsuccessfully for IMvxCommand within the code so assume this may be outdated? So I attempted a best effort but cannot get any LongClick functionality - probably due to limited knowledge of C# and eventhandlers. I implemented the following but was not sure of the MvxRelayCommand usage.
public class LongClickEventBinding: MvxBaseAndroidTargetBinding
{
private readonly View _view;
private MvxRelayCommand<JobJob> _command;
public LongClickEventBinding(View view)
{
_view = view;
_view.LongClick += ViewOnLongClick;
}
private void ViewOnLongClick(object sender, View.LongClickEventArgs eventArgs)
{
if (_command != null)
{
_command.Execute();
}
}
public override void SetValue(object value)
{
_command = (MvxRelayCommand<JobJob>)value;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_view.LongClick -= ViewOnLongClick;
}
base.Dispose(isDisposing);
}
public override Type TargetType
{
get { return typeof(MvxRelayCommand<JobJob>); }
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
And
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterFactory(new MvxCustomBindingFactory<View>("LongClick", view => new LongClickEventBinding(view)));
}
And
public ICommand JobSelectedCommand
{
get { return new MvxRelayCommand<JobJob>(NavigateToJobTasks); }
}
public void NavigateToJobTasks(JobJob jobJob)
{
RequestNavigate<JobTaskListViewModel>(new { key = jobJob.JobID });
}
And
<Mvx.MvxBindableListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="{'ItemsSource':{'Path':'GroupedList'},'LongClick':{'Path':'JobSelectedCommand'}}"
local:MvxItemTemplate="#layout/listitem_job_old"/>
However when I run code on the emulator and LongClick mouse button on listitem not much happens.
Does the following need to be implemented in the View
public event EventHandler<View.LongClickEventArgs> LongClick;
Any help / pointers appreciated.
For lists, vNext MvxBindableListView has supported ItemLongClick for a while anyway - see
https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.Binding.Droid/Views/MvxBindableListView.cs#L77
Note that this binding hooks into the ListView's ItemLongClick rather than into LongClick
Using this in your axml, you should be able to just do:
<Mvx.MvxBindableListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="{'ItemsSource':{'Path':'GroupedList'},'ItemLongClick':{'Path':'JobSelectedCommand'}}"
local:MvxItemTemplate="#layout/listitem_job_old"/>
If this doesn't work then please fire a bug report on Github issues.
If you wanted to do your custom binding on a generic (non list) View, then your code would need to switch to ICommand instead of IMvxCommand, and you also couldn't really pass in the Item argument - so you'd need to just use MvxRelayCommand on the ViewModel.
I've added View-level LongClick support to the issues list - https://github.com/slodge/MvvmCross/issues/165
But for a ListView it is probably the ItemLongClick you are actually interested in

MvvmCross Monotouch C# - Binding Int Property - Mode: TwoWay

I am new to MvvmCross and I have a question.
I noticed that the following binding code works in one way only:
{ this, "{'CurrentIndex':{'Path':'CurrentIndex','Mode':'TwoWay'}}" }
CurrentIndex is an Int Property in the View
CurrentIndex is also an Int Property in the ViewModel
This way works!
ViewModel => View
But not this way!
View => ViewModel
I have a collection of ViewControllers and my goal was to call a DeleteCommand for the CurrentIndex in the viewModel.
However,
"Android and Touch 2 way bindings are incomplete"
Reference: MvvmCross experiences, hindsight, limitations?
My guess is the TwoWay mode only works for Controls (UILabel, UITextfield, ...) but not for Properties.
So, is there a good way to make it works in both ways? Or Are there any alternatives to my problem?
Patrick
In order for a binding to transfer any value between a View to a ViewModel, then it needs to hook into some event when the value changes.
In the ViewModel, this event is always the event in the INotifyProperty interface.
In the View/Activity, there is one single pattern employed - so each binding has to hook into a separate event. For example, the Text on EditText is hooked up using the TextChanged event (see MvxEditTextTextTargetBinding.cs) while the value in a SeekBar is hooked up using a Listener object rather than an event (see MvxSeekBarProgressTargetBinging.cs).
So if you wanted to implement this two-way binding for your activity, then you could do this by:
declaring an event - CurrentIndexChanged - in your activity (MyActivity) which is fired whenever CurrentIndex changes
declare a custom binding for your MyActivity which programmatically links CurrentIndex and CurrentIndexChanged
adding the custom binding to the binding registry during Setup
For example, your activity might include:
public event EventHandler CurrentIndexChanged;
private int _currentIndex;
public int CurrentIndex
{
get { return _currentIndex; }
set { _currentIndex = value; if (CurrentIndexChanged != null) CurrentIndexChanged(this, EventArgs.Empty); }
}
And you might then declare a binding class like:
public class MyBinding : MvxPropertyInfoTargetBinding<MyActivity>
{
public MyBinding (object target, PropertyInfo targetPropertyInfo)
: base(target, targetPropertyInfo)
{
View.CurrentIndexChanged += OnCurrentIndexChanged;
}
public override MvxBindingMode DefaultMode
{
get
{
return MvxBindingMode.TwoWay;
}
}
private void OnCurrentIndexChanged(object sender, EventArgs ignored)
{
FireValueChanged(View.CurrentIndex);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (isDisposing)
{
View.CurrentIndexChanged -= OnCurrentIndexChanged;
}
}
}
And you'd need to tell the binding system about this binding in setup like:
registry.RegisterFactory(new MvxSimplePropertyInfoTargetBindingFactory(typeof(MyBinding), typeof(MyActivity), "CurrentIndex"));
However... at a practical level, if you are operating in C# rather than in XML, then you might be better off in this case using C# to simply update the ViewModel rather than using declarative binding in this case.
To be clear... in this case, I would most probably just write the Activity property as:
public int CurrentIndex
{
get { return _currentIndex; }
set { _currentIndex = value; ViewModel.CurrentIndex = value; }
}
Or... I'd consider not having this property in the Activity at all.
If it helps, there's some more information on custom bindings in:
MonoTouch MVVMCross binding to instance variables
In MvvmCross how do I do custom bind properties
Hope this helps! IMHO the bindings are there to help you when you're working in XML - you don't have to use them...
Stuart
UPDATE If you are going to do lots of these and follow the same name pattern - using property named X with changed EventHandler event named XChanged then something like this might work - it uses reflection to find the event automagically:
public class MyBinding<T> : MvxPropertyInfoTargetBinding<T>
where T : class
{
private readonly PropertyInfo _propertyInfo;
private readonly EventInfo _eventInfo;
public MyBinding(object target, PropertyInfo targetPropertyInfo)
: base(target, targetPropertyInfo)
{
_propertyInfo = targetPropertyInfo;
var eventName = _propertyInfo.Name + "Changed";
_eventInfo = View.GetType().GetEvent(eventName);
if (_eventInfo == null)
{
throw new MvxException("Event missing " + eventName);
}
if (_eventInfo.EventHandlerType != typeof(EventHandler))
{
throw new MvxException("Event type mismatch for " + eventName);
}
var addMethod = _eventInfo.GetAddMethod();
addMethod.Invoke(View, new object[] { new EventHandler(OnChanged) });
}
public override MvxBindingMode DefaultMode
{
get
{
return MvxBindingMode.TwoWay;
}
}
private void OnChanged(object sender, EventArgs ignored)
{
var value = _propertyInfo.GetValue(View, null);
FireValueChanged(value);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (isDisposing)
{
var removeMethod = _eventInfo.GetRemoveMethod();
removeMethod.Invoke(View, new object[] { new EventHandler(OnChanged) });
}
}
}