I'm new to xamarin and mvvmcross and I would like to wire up a simple button click from my ios project to my viewmodel.
using System;
using MvvmCross.Binding.BindingContext;
using MvvmCross.iOS.Views;
using Colingual.Core.ViewModels;
namespace Colingual.iOS
{
public partial class LoginView : MvxViewController
{
public LoginView() : base("LoginView", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
var set = this.CreateBindingSet<LoginView, LoginViewModel>();
set.Bind(Username).To(vm => vm.Username);
set.Bind(Password).To(vm => vm.Password);
set.Bind(btnLogin).To(vm => vm.MyAwesomeCommand);
set.Apply();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}
}
I would like to wire up btnlogin to myawesomecommand.
using MvvmCross.Core.ViewModels;
namespace Colingual.Core.ViewModels
{
public class LoginViewModel : MvxViewModel
{
readonly IAuthenticationService _authenticationService;
public LoginViewModel(IAuthenticationService authenticationService)
{
_authenticationService = authenticationService;
}
string _username = string.Empty;
public string Username
{
get { return _username; }
set { SetProperty(ref _username, value); }
}
string _password = string.Empty;
public string Password
{
get { return _password; }
set { SetProperty(ref _password, value); }
}
public bool AuthenticateUser() {
return true;
}
MvxCommand _myAwesomeCommand;
public IMvxCommand MyAwesomeCommand
{
get
{
DoStuff();
return _myAwesomeCommand;
}
}
void DoStuff()
{
string test = string.Empty;
}
}
}
As you can see I've got mvxCommand with the name of MyAwesomecommand but I want to handle some logic from a button click in my other project. Anyone know what I should do?
I've done more looking around and found an answer here.
MvxCommand _myAwesomeCommand;
public IMvxCommand MyAwesomeCommand
{
get { return new MvxCommand(DoStuff); }
}
void DoStuff()
{
string test = string.Empty;
}
The idea is to have your mvxcommand getter which returns a new command that takes the method as a parameter.
When clicking the button btnLogin, you can access void DoStuff in viewmodel.
Related
My TabbedPage uses a Binding Property, which is defined in the tabbed page's ViewModel, for showing a Badge text.
I am setting the badge property when initializing the view (actually when it (re)appears). However, sometimes the badge text is changing from outside of my ViewModel(s), this is because I have a SignalR method which is called when a new message is being added by another application.
Though, when this happens the OnAppearing method of my tabbed viewmodel is obviously not called. So the question is, how can I 'notify' the tabbedpage viewmodel that the badge text should be changed.
I think the (best) way to do this is using somekind of Event. Since all of my ViewModels inherit from a 'ViewModelBase' I could implement the event notification / change in the ViewModelBase and override the property in my TabbedPage ViewModel.
Though, sadly my knowledge about using Events / EventArgs is limited and the stuff I found about it is not working.
Is using EventArgs the best way to solve this problem? And if so, could anyone give any pointers how to implement it properly.
*On a side-note, I am also using Prism
My TabbedPage ViewModel:
public class RootTabbedViewModel : ViewModelBase, IPageLifecycleAware
{
private readonly INavigationService _navigationService;
private int _messageCount;
public RootTabbedViewModel(INavigationService navigationService)
: base(navigationService)
{
_navigationService = navigationService;
}
public int MessageCount
{
get { return _messageCount; }
set { SetProperty(ref _messageCount, value); }
}
public void OnDisappearing()
{
}
void IPageLifecycleAware.OnAppearing()
{
// (omitted) Logic for setting the MessageCount property
}
}
ViewModelVase:
public class ViewModelBase : BindableBase, IInitialize, IInitializeAsync, INavigationAware, IDestructible, IActiveAware
{
public event EventHandler MessageAddedEventArgs; // this should be used to trigger the MessageCount change..
protected INavigationService NavigationService { get; private set; }
public ViewModelBase(INavigationService navigationService)
{
NavigationService = navigationService;
Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
IsNotConnected = Connectivity.NetworkAccess != NetworkAccess.Internet;
}
private bool _isNotConnected;
public bool IsNotConnected
{
get { return _isNotConnected; }
set { SetProperty(ref _isNotConnected, value); }
}
~ViewModelBase()
{
Connectivity.ConnectivityChanged -= Connectivity_ConnectivityChanged;
}
async void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
{
IsNotConnected = e.NetworkAccess != NetworkAccess.Internet;
if (IsNotConnected == false)
{
await DataHubService.Connect();
}
}
public virtual void Initialize(INavigationParameters parameters)
{
}
public virtual void OnNavigatedFrom(INavigationParameters parameters)
{
}
public virtual void OnNavigatedTo(INavigationParameters parameters)
{
}
public virtual void Destroy()
{
}
public virtual Task InitializeAsync(INavigationParameters parameters)
{
return Task.CompletedTask;
}
}
SignalR Datahub which should trigger the event:
public static class DataHubService2
{
// .. omitted some other SignalR specific code
public static async Task Connect()
{
try
{
GetInstanse();
hubConnection.On<Messages>("ReceiveMessage", async (message) =>
{
if(message != null)
{
// event that message count has changed should be triggered here..
}
});
}
catch (Exception ex)
{
// ...
}
}
}
As pointed out by #Jason, this specific problem is a good use case for using the MessagingCenter.
In the end the implementation looks as following:
public static class DataHubService2
{
// .. omitted some other SignalR specific code
public static async Task Connect()
{
try
{
GetInstanse();
hubConnection.On<Messages>("ReceiveMessage", async (message) =>
{
if(message != null)
{
MessagingCenter.Send("UpdateMessageCount", "Update");
}
});
}
catch (Exception ex)
{
// ...
}
}
}
public class RootTabbedViewModel : ViewModelBase, IPageLifecycleAware
{
private readonly INavigationService _navigationService;
private int _messageCount;
public RootTabbedViewModel(INavigationService navigationService)
: base(navigationService)
{
_navigationService = navigationService;
MessagingCenter.Subscribe<string>("UpdateMessageCount", "Update", async (a) =>
{
await UpdateMessageCount();
});
}
public int MessageCount
{
get { return _messageCount; }
set { SetProperty(ref _messageCount, value); }
}
public void OnDisappearing()
{
}
void IPageLifecycleAware.OnAppearing()
{
UpdateMessageCount();
}
async Task UpdateMessageCount()
{
int messageCount = await App.Database.GetNewMessageCountAsync();
MessageCount = messageCount.ToString();
}
}
Weirdly enough, but I can't to get it workable in ViewModel.
public class SmallWindow_ViewModel
{
public string My_property
{
get { return _my_property; }
set { Set(ref _my_property, value); }
}
private string _my_property;
public void SmallWindow_unloaded()
{
Properties.Settings.Default.My_property_setting = My_property;
Properties.Settings.Default.Save();
}
}
If it is placed in Code Behind it works.
experts! I'm new in learning WPF and MVVM. I decided to develop some small WPF application. So I have a LoginViewModel which interacts with Database. Once the login operation is successful I need to hide the LoginView and display the MainView. I'm using the Caliburn.Micro for these purposes. But I have got a problem - I don't know how can I hide the LoginView and show the MainView. I would be appreciated if someone would help me to solve this problem :(
I've already tried to use the following actions:
- Using the IEventAggregator _events: _events.PublishOnUIThread("message"), but no result :(
- Using the Conductor.Collection.OneActive and ActiveteItem()/DeactivateItem() methods, but still no result :(
Here is my Bootstrapper class:
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
ConventionManager.AddElementConvention<PasswordBox>(
PasswordBoxHelper.BoundPasswordProperty,
"Password",
"PasswordChanged");
}
protected override void Configure()
{
_container.Instance(_container);
_container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IApiHelper, ApiHelper>()
.Singleton<IEventAggregator, EventAggregator>()
.Singleton<ILoggedInUserModel, LoggedInUserModel>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
Also here is my LoginViewModel:
public class LoginViewModel : Conductor<IScreen>.Collection.OneActive
{
private IWindowManager _windowManager;
private IApiHelper _apiHelper;
private ILoggedInUserModel _user;
private IEventAggregator _events;
private string _login = "test";
private string _password = "test";
private bool _isAdmin;
public ShellViewModel(IWindowManager windowManager, IApiHelper apiHelper, ILoggedInUserModel user, IEventAggregator events)
{
_windowManager = windowManager;
_apiHelper = apiHelper;
_user = user;
_events = events;
}
public string Login
{
get { return _login; }
set
{
_login = value;
NotifyOfPropertyChange(() => Login);
NotifyOfPropertyChange(() => CanPerformLogin);
}
}
public string Password
{
get { return _password; }
set
{
_password = value;
NotifyOfPropertyChange(() => Password);
NotifyOfPropertyChange(() => CanPerformLogin);
}
}
public bool IsAdmin
{
get { return _isAdmin; }
set
{
_isAdmin = value;
NotifyOfPropertyChange(() => IsAdmin);
NotifyOfPropertyChange(() => CanPerformLogin);
}
}
public bool CanPerformLogin
{
get
{
bool output = false;
if (Login?.Length > 0 && Password?.Length > 0)
{
output = true;
}
return output;
}
}
public ShellViewModel(IApiHelper apiHelper, IWindowManager windowManager)
{
_windowManager = windowManager;
_apiHelper = apiHelper;
}
public void OpenSignUpView()
{
_windowManager.ShowWindow(new SignUpViewModel());
}
public async Task PerformLogin()
{
//Here I need to hide my LoginView and show the ShellView
}
public void CloseForm()
{
TryClose();
}
And here is my ShellViewModel:
public class ShellViewModel : Screen, IHandle<LogOnEvent>
{
private IWindowManager _windowManager;
private IApiHelper _apiHelper;
private ILoggedInUserModel _user;
private IEventAggregator _events;
public SelectYourTreeViewModel(IWindowManager windowManager, IApiHelper apiHelper, ILoggedInUserModel user, IEventAggregator events)
{
_windowManager = windowManager;
_apiHelper = apiHelper;
_user = user;
_events = events;
_events.Subscribe(this);
}
public void Handle(LogOnEvent message) //Here I've tried to use events to show/or hide view
{
System.Windows.MessageBox.Show("Some text!");
}
Can you please show the structure of the WindowManager class. ? You can open a window in WPF like this:
new LoginView().Show();
And so close it again.
myAlreadyOpenedLoginView.Close().
Here LoginView must be a window and inherit from the class Window.
Sorry in advance because I'm a newbie to this. I'm trying to trigger a method with a string in another viewmodel, and I've read that using MvxMessenger is basically my best option to do this.
What I don't understand because I can barely find any documentation/sample code to get me on my way is how to accomplish this
Whenever I click on a button in FilterViewModel, I also want it to trigger a method in the SearchHistoryViewModel with a string from the FilterViewModel.
Basically, if the SearchHistoryViewModel code is even correct, how do I send/Publish this message correctly?
FilterViewModel
public class SearchHistoryFilterViewModel : MvxViewModel
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value;
RaisePropertyChanged(() => Name);
}
}
public SearchHistoryFilterViewModel(IMvxMessenger messenger)
{
//_token = messenger.Subscribe
//_messenger = messenger;
}
public IMvxCommand FilterCommand
{
get
{
return new MvxCommand(FilterByName);
}
}
public void FilterByName()
{
//Whenever this method is triggered, send a message with the Name in it
SearchFilterMessage message = new SearchFilterMessage(this, Name);
//Send message
... ? /////////////////////
}`
SearchHistoryViewModel
public class SearchHistoryViewModel : MvxViewModel
{
//properties
...
...
private readonly MvxSubscriptionToken _token;
//ctor
public SearchHistoryViewModel(ISearchHistoryService searchHistoryService, IMvxNavigationService navigationService, IMvxMessenger messenger)
{
_searchHistoryService = searchHistoryService;
_navigationService = navigationService;
/*Subscribe - Whenever a SearchFilterMessage is received, trigger the
OnFilterMessage method */
_token = messenger.Subscribe<SearchFilterMessage>((message => {
OnFilterMessage(message.FilterName); })
);
}
//methods
....
....
....
/* Do this Whenever the SearchFilterMessage is received*/
public async void OnFilterMessage(string name)
{
HistoryItems = await _searchHistoryService.GetHistoryByName(name);
}
Alright, so I was confused on how to declare the messenger itself, and use it without the tokens, not in the constructor. Solution was predictably easy :(
Basically declare another ImvxMessenger and via injection set it, then call on that one to Publish it in another method
private IMvxMessenger _messenger;
public SearchHistoryFilterViewModel(IMvxMessenger messenger)
{
//_token = messenger.Subscribe...
//messenger.Publish<SearchFilterMessage>(FilterByName());
_messenger = messenger;
}
public void FilterByName()
{
Debug.WriteLine(Name);
SearchFilterMessage message = new SearchFilterMessage(this, Name);
//Send message
_messenger.Publish<SearchFilterMessage>(message);
}
Closing a MvxAppCompatDialogFragment via the back button doesn't seem to work completely. The button I clicked to trigger the dialog remains disabled after the dialog is dismissed. It's almost like the Task is stuck. If I change to MvxDialogFragment then the back button will close the dialog as expected, and the button I clicked to trigger the dialog is enabled again after the dialog is dismissed. I'm trying to use MvxAppCompatDialogFragment because I'm using MvxAppCompatActivity. Am I doing something wrong or is this a bug in MvvmCross 5.2.1?
Here is the ViewModel:
public class ConfirmationViewModel : MvxViewModel<ConfirmationConfiguration, bool?>, IMvxLocalizedTextSourceOwner
{
private readonly IMvxNavigationService _mvxNavigationService;
public ConfirmationViewModel(IMvxNavigationService mvxNavigationService)
{
_mvxNavigationService = mvxNavigationService;
}
public override void Prepare([NotNull] ConfirmationConfiguration parameter)
{
if (parameter == null) throw new ArgumentNullException(nameof(parameter));
Title = parameter.Title;
Body = parameter.Body;
PositiveCommandText = !string.IsNullOrEmpty(parameter.YesCommandText)
? parameter.YesCommandText
: LocalizedTextSource.GetText("Yes");
NegativeCommandText = !string.IsNullOrEmpty(parameter.NoCommandText)
? parameter.NoCommandText
: LocalizedTextSource.GetText("No");
}
private bool? _confirmationResult;
public bool? ConfirmationResult
{
get => _confirmationResult;
private set => SetProperty(ref _confirmationResult, value);
}
private string _title;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
private string _body;
public string Body
{
get => _body;
set => SetProperty(ref _body, value);
}
private string _positiveCommandText;
public string PositiveCommandText
{
get => _positiveCommandText;
set => SetProperty(ref _positiveCommandText, value);
}
private string _negativeCommandText;
public string NegativeCommandText
{
get => _negativeCommandText;
set => SetProperty(ref _negativeCommandText, value);
}
private IMvxAsyncCommand _yesCommand;
public IMvxAsyncCommand PositiveCommand => _yesCommand ?? (_yesCommand = new MvxAsyncCommand(OnPositiveCommandAsync));
private async Task OnPositiveCommandAsync()
{
ConfirmationResult = true;
await _mvxNavigationService.Close(this, ConfirmationResult);
}
private IMvxAsyncCommand _noCommand;
public IMvxAsyncCommand NegativeCommand => _noCommand ?? (_noCommand = new MvxAsyncCommand(OnNegativeCommandAsync));
private async Task OnNegativeCommandAsync()
{
ConfirmationResult = false;
await _mvxNavigationService.Close(this, ConfirmationResult);
}
public IMvxLanguageBinder LocalizedTextSource => new MvxLanguageBinder("", GetType().Name);
public IMvxLanguageBinder TextSource => LocalizedTextSource;
}
public class ConfirmationConfiguration
{
public string Title { get; set; }
public string Body { get; set; }
public string YesCommandText { get; set; }
public string NoCommandText { get; set; }
}
Here is the View:
[MvxDialogFragmentPresentation(Cancelable = true)]
[Register(nameof(ConfirmationFragment))]
public class ConfirmationFragment : MvxAppCompatDialogFragment<ConfirmationViewModel>
{
public ConfirmationFragment()
{
RetainInstance = true;
}
public ConfirmationFragment(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
RetainInstance = true;
}
public override Dialog OnCreateDialog(Bundle savedInstanceState)
{
var builder = new AlertDialog.Builder(Activity)
.SetTitle(ViewModel.Title)
.SetMessage(ViewModel.Body)
.SetPositiveButton(ViewModel.PositiveCommandText, OnPositiveButton)
.SetNegativeButton(ViewModel.NegativeCommandText, OnNegativeButton);
return builder.Create();
}
private async void OnNegativeButton(object sender, DialogClickEventArgs e)
{
if (ViewModel.NegativeCommand.CanExecute())
{
await ViewModel.NegativeCommand.ExecuteAsync();
}
}
private async void OnPositiveButton(object sender, DialogClickEventArgs e)
{
if (ViewModel.PositiveCommand.CanExecute())
{
await ViewModel.PositiveCommand.ExecuteAsync();
}
}
}
I'm navigating to the dialog like this:
var confirmation = await Mvx.Resolve<IMvxNavigationService>().Navigate<ConfirmationViewModel, ConfirmationConfiguration, bool?>(
new ConfirmationConfiguration()
{
Body = "Hello, World!",
Title = "Testing"
});
If I change the base class from MvxAppCompatDialogFragment to MvxDialogFragment then it all works as expected.
This was indeed an issue in MvvmCross v5.2.1 (thanks for reporting!). As a workaround for now you can add this code in your DialogFragment class:
public override void OnCancel(IDialogInterface dialog)
{
base.OnCancel(dialog);
ViewModel?.ViewDestroy();
}
public override void DismissAllowingStateLoss()
{
base.DismissAllowingStateLoss();
ViewModel?.ViewDestroy();
}
public override void Dismiss()
{
base.Dismiss();
ViewModel?.ViewDestroy();
}
I've looked into this. What happens is that when you press the back button it closes the View without calling the NavigationSerivce.Close(). This prevents the result Task to be called and either set or cancel the action. I'm not sure if this is a bug or just behavior. A workaround could be to call Close from ConfirmationViewModel ViewDissapearing, or cancel the Task yourself.