MvxAppCompatDialogFragment cancellation via Back button is not completely working - c#

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.

Related

Changing TabbedPage binding property from a (different) class - Xamarin.Forms

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();
}
}

How show MainViewModel and hide LoginViewModel when login operation is successful

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.

MvvmCross - handle button click in viewmodel

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.

Cannot navigate back in Template10

I'm trying to navigate back to my MainPage from this secondary page. I tried to use NavigationService.GoBack() but I got NullReferenceException.
I didn't change anything from the viewmodel. What I intended to do is to save user input into SQLite first, then navigate back to MainPage
Here is my code from DetailPage.xaml.cs
private SQLiteService database = new SQLiteService();
DetailPageViewModel vm = new DetailPageViewModel();
public DetailPage()
{
InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Disabled;
}
private void yesButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
var _name = Name.Text;
var _uptake = UptakeTime.SelectedIndex + 1; // database index Morning start at 1
var _intake = int.Parse(Intake.Text);
vm.ProcessData(_name, _intake, _uptake);
}
Here is the DetailPageViewModel.cs
SQLiteService database = new SQLiteService();
public DetailPageViewModel()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
Value = "Designtime value";
}
}
private string _Value = "Default";
public string Value { get { return _Value; } set { Set(ref _Value, value); } }
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
Value = (state.ContainsKey(nameof(Value))) ? state[nameof(Value)]?.ToString() : parameter?.ToString();
await Task.CompletedTask;
}
public override async Task OnNavigatedFromAsync(IDictionary<string, object> pageState, bool suspending)
{
if (suspending)
{
pageState[nameof(Value)] = Value;
}
await Task.CompletedTask;
}
public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
{
args.Cancel = false;
await Task.CompletedTask;
}
public void GotoMainPage() =>
NavigationService.GoBack();
public void ProcessData(string _name, int _type, int _uptake)
{
database.AddNewItem(_name, _uptake, _type);
GotoMainPage();
}
Side note : I tried to access the GotoMainPage from Detail.xaml.cs by using vm.GotoMainPage(), but it still returned exception
To navigate between different Pages use the Frame.Navigate method.
An example for page Navigation to a xaml Page called Mainpage is:
this.Frame.Navigage(typeof(Mainpage));
For further Information look at the Documentation: Frame.Navigate
The Namespace being used is called System.Windows.Controls.

CefGlue How to get HTML source?

i raised questions in google group , they told me to Implement 'CefStringVisitor' class,i did so.
namespace Xilium.CefGlue.WPF.Customer
{
class MyCefStringVisitor : CefStringVisitor
{
private string html;
protected override void Visit(string value)
{
html = value;
}
public string Html
{
get { return html; }
set { html = value; }
}
}
}
But i receive the Text word , instead of HTML soucrce.
How can i get HTML source?
Try this
private sealed class SourceVisitor : CefStringVisitor
{
private readonly Action<string> _callback;
public SourceVisitor(Action<string> callback)
{
_callback = callback;
}
protected override void Visit(string value)
{
_callback(value);
}
}
protected override void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
{
if (frame.IsMain)
{
string HtmlSourceCode;
var visitor = new SourceVisitor(text =>
{
BeginInvoke(new Action(() =>
{
HtmlSourceCode = text;
}));
});
frame.GetSource(visitor);
}
}

Categories

Resources