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
Related
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
}
}
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.
I am using mvvmcross with Unity, but it doesn't have a built-in UIThreadDispatcher, there are many people had written it owns dispatcher which doesn't have a common interface. The following is my own implementation. Instead of create different MvxXXXUIThreadDispatcher, is there any other approach ?
public abstract class MvxUnityUIThreadDispatcher
: MvxMainThreadDispatcher
{
protected MvxUnityUIThreadDispatcher()
{
}
public bool RequestMainThreadAction(Action action)
{
UIThreadDispatcher.Instance.InvokeOrEnqueueOnMainThread(() => ExceptionMaskedAction(action));
return true;
}
}
The thread dispatcher is used by Mvx to make sure that UI controls are not called on non-UI threads.
If Unity doesn't need this, then you can just call action() in your implementation.
In the https://github.com/et-nowis/mvx-unity-ngui MvvmCross implementation it looks like they used SynchronizationContext:
public abstract class MvxUnityUIThreadDispatcher
: MvxMainThreadDispatcher
{
private readonly SynchronizationContext _uiSynchronizationContext;
protected MvxUnityUIThreadDispatcher()
{
if (SynchronizationContext.Current == null)
SynchronizationContext.SetSynchronizationContext(new UnitySynchronizationContext());
_uiSynchronizationContext = SynchronizationContext.Current;
}
public bool RequestMainThreadAction(Action action)
{
if (_uiSynchronizationContext == SynchronizationContext.Current)
action();
else
_uiSynchronizationContext.Post(state => ExceptionMaskedAction(action), null);
return true;
}
}
from https://github.com/et-nowis/mvx-unity-ngui/blob/master/Assets/External/MvvmCross/Cirrious/Cirrious.MvvmCross.Unity/Views/MvxUnityUIThreadDispatcher.cs - see source file there for their license terms on this code
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) });
}
}
}
The Navigation framework in Windows Phone 7 is a cut down version of what is in Silverlight. You can only navigate to a Uri and not pass in a view. Since the NavigationService is tied to the View, how do people get this to fit into MVVM. For example:
public class ViewModel : IViewModel
{
private IUnityContainer container;
private IView view;
public ViewModel(IUnityContainer container, IView view)
{
this.container = container;
this.view = view;
}
public ICommand GoToNextPageCommand { get { ... } }
public IView { get { return this.view; } }
public void GoToNextPage()
{
// What do I put here.
}
}
public class View : PhoneApplicationPage, IView
{
...
public void SetModel(IViewModel model) { ... }
}
I am using the Unity IOC container. I have to resolve my view model first and then use the View property to get hold of the view and then show it. However using the NavigationService, I have to pass in a view Uri. There is no way for me to create the view model first. Is there a way to get around this.
Instead of passing the view through the constructor. You could construct the view first via the NavigationService and pass it into the view-model. Like so:
public class ViewModel : IViewModel
{
private IUnityContainer container;
private IView view;
public ViewModel(IUnityContainer container)
{
this.container = container;
}
public ICommand GoToNextPageCommand { get { ... } }
public IView
{
get { return this.view; }
set { this.view = value; this.view.SetModel(this); }
}
public void GoToNextPage()
{
// What do I put here.
}
}
PhoneApplicationFrame frame = Application.Current.RootVisual;
bool success = frame.Navigate(new Uri("View Uri"));
if (success)
{
// I'm not sure if the frame's Content property will give you the current view.
IView view = (IView)frame.Content;
IViewModel viewModel = this.unityContainer.Resolve<IViewModel>();
viewModel.View = view;
}
If you are using Mvvm Light you could try:
Windows Phone 7 — Navigation between pages using MVVM Light Messaging
(See similar post: Silverlight Navigation using Mvvm-light(oobe)+MEF?)
My opinion is that the view-model should be created and registered at application startup. By placing it inside the root DataContext all pages will automatically get a reference to it without any code-behind or IoC tricks.
// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
m_ViewModel = new PrimaryViewModel(RootFrame) ;
RootFrame.DataContext = m_ViewModel;
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
m_ViewModel = new PrimaryViewModel(RootFrame) ;
m_ViewModel.Activated(PhoneApplicationService.Current.State);
RootFrame.DataContext = m_ViewModel;
}
If you are using MVVM architecture,then you can pass navigationPage after registering using Messenger. Create a model class (say NavigateToPageMessage) with a string(say PageName) variable. You want to pass string from homepage.xaml to newpage.xaml,then in Homepage viewmodel just send the message like this under the command you binded (say HomeNavigationCommand)
private void HomeNavigationCommandHandler()
{
Messenger.Default.Send(new NavigateToPageMessage {PageName = "newpage"});
}
In the newpage Viewmodel,you should register the messenger like this,
Messenger.Default.Register<NavigateToPageMessage>(this, (action) => ReceiveMessage(action));
private object ReceiveMessage(NavigateToPageMessage action)
{
var page = string.Format("/Views/{0}.xaml", action.PageName);
NavigationService.Navigate(new System.Uri(page,System.UriKind.Relative));
return null;
}
//Assuming your views are in View Folder