In my Xamarin.Forms app I have a Navigation Service (based on Mastering Xamarin.Forms), and I now want to Push and Pop Modal Pages.
So I adopted the NavigateTo function from
public async Task NavigateTo<TVM, TParameter>(TParameter parameter) where TVM : BaseViewModel
{
await NavigateToView(typeof(TVM));
if (XamarinFormsNav.NavigationStack.Last().BindingContext is BaseViewModel<TParameter>)
{
((BaseViewModel<TParameter>)XamarinFormsNav.NavigationStack.Last().BindingContext).Init(parameter);
}
}
into a new one for Modal Pages
public async Task NavigateToModal<TVM, TParameter>(TParameter parameter) where TVM : BaseViewModel
{
await NavigateToViewModal(typeof(TVM));
if (XamarinFormsNav.ModalStack.Last().BindingContext is BaseViewModel<TParameter>)
{
((BaseViewModel<TParameter>)XamarinFormsNav.ModalStack.Last().BindingContext).Init(parameter);
}
}
The Modal Page is a login form called from StartPageViewModel when no login credentials can be found
await NavService.NavigateToModal<LoginPageViewModel, LoginData>(loginData);
Page and ViewModel appear and work as expected. But it does not disappear.
The NavigationService method is
public void RemoveModalView()
{
XamarinFormsNav.PopModalAsync(true);
}
But nothing happens, the I get the same result when I call PopModalAsync from the ViewModel of the Page.
How can I properly close a Modal Page?
Thank you!
Related
I'm hoping someone can help me out. I'm developing a Xamarin Forms 5 app using Shell. The app is 90% done, and has been working perfectly.
I've now run into an issue with a specific view. On a view, I'm calling Shell.Current.GoToAsync(route), and the app is not navigating to the route specified, It's going BACKWARDS (Popping).
I'm 100% certain that all routes are registered properly, and that I'm specifying the correct route when navigating.
Has anyone ever seen this behavior? I'm completely stumped.
This is the navation service functions being used:
public async Task PopAsync()
{
await Shell.Current.Navigation.PopAsync();
}
public async Task GoBackAsync()
{
await Shell.Current.GoToAsync("..");
}
public async Task ReplaceCurrent<TViewModel>(string parameters = null) where TViewModel : BaseViewModel
{
await GoToAsync<TViewModel>(#"../", parameters);
}
public async Task PushAsync<TViewModel>(string parameters = null) where TViewModel : BaseViewModel
{
await GoToAsync<TViewModel>("", parameters);
}
private async Task GoToAsync<TViewModel>(string routePrefix, string parameters) where TViewModel : BaseViewModel
{
var route = routePrefix + typeof(TViewModel).Name;
if (!string.IsNullOrWhiteSpace(parameters))
{
route += $"?{parameters}";
}
await Shell.Current.GoToAsync(route);
}
the route value being passed is "DeviceInspectionEditViewModel".
The routes are registered as follows:
Routing.RegisterRoute(nameof(WorkOrderViewModel), typeof(WorkOrderView));
Routing.RegisterRoute(nameof(InspectionListViewModel), typeof(InspectionListView));
Routing.RegisterRoute(nameof(DeviceInspectionViewModel), typeof(DeviceInspectionView));
Routing.RegisterRoute(nameof(DeviceInspectionEditViewModel), typeof(DeviceInspectionEditView));
At the time GoToAsync is called, the NavigationStack looks like this:
[0] - {Page} null
[1] - WorkOrderView
[2] - InspectionListView
[3] - DeviceInspectionView
This is the code that we're using:
private async Task Edit(CertDevice dev)
{
var sw = Logger.LogEntry();
await _navigationService.PushAsync<DeviceInspectionEditViewModel>();
Logger.LogExit(sw);
}
IMPORTANT NOTE
I'm seeing "The user pressed the hardware back button" in the debug logs.
A subclass of the view was attempting to deal with hardware back button presses by capturing OnNavigating. This was a user error.
I'm currently working on the POM of a web app, that allows to open modals from the navigation bar. The navigation bar stays the same for every page you're on. Each modal can be opened from every page.
I have defined a page object for each modal. Also the navigation bar is a pageobject,
What would be the best way to return to the page, that the modal was opened from?
So for example, you are on the Page FooPage and open modal AboutModal. What is the best way to return to FooPage? It should also work for BarPage and other Pages.
My first approach was, that i define a BasePage Object, which only includes the webdriver and navigationbar. i extend every Page on the web app from this BasePage. Then i could do something like this:
Code for FooPage:
public class FooPage: BasePage
{
private NavigationBar NavBar;
public FooPage(IWebDriver driver): base(driver)
{
...
this.NavBar = new NavigationBar(driver);
}
public NavigationBar Navigate()
{
return NavBar;
}
...
}
public class NavigationBar
{
...
public openAboutModal(BasePage currentPage)
{
log.Info("Open About Modal");
Action.Click(NavigationBarElements.AboutButton);
return new AboutModal(Driver, currentPage);
}
}
public class AboutModal
{
...
protected BasePage ReturnPage;
public AboutModal(IWebDriver driver, BasePage returnPage)
{
...
this.ReturnPage = returnPage;
}
public BasePage CloseAboutModal()
{
return this.ReturnPage;
}
...
}
This is not practical and not intuitive, because we have to remember on which pageobject we currently are, when writing tests. Also only the methods from BasePage are available, which means we have to additionaly navigate back to the page we wanted to be on.
So instead of writing
public class ModalTests
{
[Test]
public void CheckAboutModal()
{
Login() // FooPage
.Navigate() //NavigationBar
.openAboutModal() // AboutModal
.doSomeStuff() //AboutModal
.CloseAboutModal(); //FooPage
}
}
we have to do
public void CheckAboutModal()
{
Login() // FooPage
.Navigate() //NavigationBar
.openAboutModal(new FooPage(Driver)) // AboutModal
.doSomeStuff() // AboutModal
.CloseAboutModal() // BasePage
.Navigate() //NavigationBar
.ToFooPage(); // FooPage
}
}
How can I return to the calling Page of the modal, without making Testwriting to complicated?
Rather than write your test as one giant method-chaining call, use variables whenever you need to refer back to a certain page model. Your test can simply become:
var foo = Login();
foo.Navigate()
.openAboutModal()
.doSomeStuff()
.CloseAboutModal();
// Continue your test after closing the modal
foo.SomeOtherOperation();
In cases like this, the modal doesn't need to return anything. The CloseAboutModal() method can be a void return type. Your test should understand the larger context in which the modal is being used, and create local variables appropriately in order to "return" back to the main page.
I have a scenario where I want to call a method when the user of the app navigates to a certain tab of the TabbedPage.
Example: If I navigate to tab no. 3 of my TabbedPage, a certain method shall be called.
How do I achieve that?
By default all tabs of the TabbedPage are loaded when I start the app.
I am writing in Xamarin - C#.
Best regards!
There are two sample ways to achieve that.
One is using OnAppearing method inside the needed item of tab page.
For example, the tab no. 3 of TabbedPage is ItemsPage, then its ItemsPage.xaml.cs code as follows:
public partial class ItemsPage : ContentPage
{
public ItemsPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
// Call your needed method here
}
}
The another way is using OnCurrentPageChanged methond inside the tabbedpage.xaml.cs.
For example, the code as follows:
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
if(CurrentPage.Title == "tab no. 3 title")
{
// call your needed method
}
//Console.WriteLine(CurrentPage.Title);
}
}
I am trying to make an app with a Main Page that is a plain Content Page (i.e. no toolbar) which has a button that leads into a Navigation Page (with toolbar). I'm struggling to find any resources to help me out in this specific case.
I have tried the following. In my App.xaml:
public partial class App : Application
{
public App ()
{
InitializeComponent();
MainPage = new App5.MainPage();
Pallets = new NavigationPage(new Pallets()); //Error: 'Pallets' is a type but is used like a variable
}
...
}
Above is the error I get on Pallets. I could change Pallets for MainPage and write the whole app with Navigation pages, which I will do for now, but I would prefer not to.
In my MainPage.xaml.cs, I'm trying to switch the view when the button is clicked:
public partial class MainPage : ContentPage
{
...
async void Button_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new Pallets());
}
}
I have seen examples such as this that include Navigation Page to Navigation Page buttons, but not plain Content Page to Navigation Page buttons.
I've following architecture:
desktop application, .Net 4.5, C#, WPF, MVVM Light, Messenger, IoC - ViewModel locator, so ViewModels doen't know anyhing about Views.
I have main view with data grid of some elements, and I want to display details of each individual element in new/child windows after double click on data grid.
I've bind event double click on main view to main view model. From this event handler in main view model, message is sent via Messanger.
New view (new/child window) is created in main view via delegate of also double click.
New/child window is a view which locate his view model and this view model register to the specific message in his constructor.
The problem is that new/child window (new view, and view model so on) is created too late, because message is already sent when new view model register for it.
Do you know maybe some patterns for such architecture. Any ideas will be appreciated.
It would help to know exactly what you try to do.
If your problem is just to display a detailed Window when double click on a row, I would say: create only one childWindow at start, and play with its visbility when required.
If you really need a new window each time, you could create it from your viewModel with an injected service for example.
In any case, you never has to create your window from main view! Either you create one window at start, either you dynamically create it from view model.
You cannot hope to create it from view and send the message in your view model.
Edit about the injected service, you could use something like that:
public interface IWindowService
{
void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window;
}
public class WindowService : IWindowService
{
private readonly IUIDispatcher _dispatcher;
public WindowService(IUIDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window
{
_dispatcher.Run(() => OpenThreadSafe<TWindow>(viewModel));
}
private static void OpenThreadSafe<TWindow>(ViewModelBase viewModel) where TWindow : Window
{
var view = (TWindow) Activator.CreateInstance(typeof(TWindow), viewModel);
view.Show();
}
}
public class UIDispatcher : IUIDispatcher
{
public void Run(Action action)
{
var dispatcher = DispatcherHelper.UIDispatcher;
if (dispatcher == null)
{
action();
return;
}
DispatcherHelper.CheckBeginInvokeOnUI(action);
}
Note this DispatcherHelper come from MVVMlight, but you could erplace it easily.
Hope it helps.
The problem is that the ViewModel Locator creates the viewmodel instance only when it is needed (lazy loading).
just configure the ViewModelLocator to instantiate the viewmodel eager instead of lazy. This is done by passing the parameter "true" to the IoC Container.
Sample:
namespace Administration.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//Eager Loading
SimpleIoc.Default.Register<UserManagementViewModel>(true);
//Lazy Loading
SimpleIoc.Default.Register<InformationManagementViewModel>();
}
public UserManagementViewModel UserManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<UserManagementViewModel>();
}
}
public InformationManagementViewModel InformationManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<InformationManagementViewModel>();
}
}
public static void Cleanup()
{
SimpleIoc.Default.Unregister<UserManagementViewModel>();
SimpleIoc.Default.Unregister<InformationManagementViewModel>();
}
}
}