I'm using Xamarin.Forms and Prism to create my mobile app.
I have a screen with 2 entries. When entering the screen, I'd like to set the focus on the first entry.
Then after the user entered data in this entry and validated it, I'd like to set the focus to the second entry.
Based on first answer:
I should do something wrong. I've created a small new Prism project to test it :
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:testEntry"
x:Class="testEntry.Views.MainPage"
Title="{Binding Title}">
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Label Text="Welcome to Xamarin Forms and Prism!" />
<local:MyEntry Placeholder="" x:Name="entry1" />
<Button Text="set focus on entry1" Clicked="Button_Clicked"/>
</StackLayout>
</ContentPage>
MainPage.xaml.cs
using Xamarin.Forms;
namespace testEntry.Views
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
entry1.Focus(); //Not Working
}
private void Button_Clicked(object sender, EventArgs e)
{
entry1.Focus(); //Working
}
}
}
MyEntry.cs (in Main project)
using Xamarin.Forms;
namespace testEntry
{
public class MyEntry : Entry
{
}
}
MyEntryRenderer.cs (in Android Project)
using Android.Content;
using Android.Views;
using Android.Views.Accessibility;
using Xamarin.Forms.Platform.Android;
namespace testEntry.Droid
{
public class MyEntryRenderer : EntryRenderer
{
public MyEntryRenderer(Context context) : base(context)
{
}
public static void Focus(View view)
{
view.SendAccessibilityEvent(EventTypes.ViewFocused);
}
}
}
Unfortunately, still nofocus on my field :'(
Finally, and thanks to Saamer, I found another way of doing it by using EventAggregator.
public class FocusChanged : PubSubEvent<String> { }
Then in my view model :
IEventAggregator _ea;
public MainPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator) : base(navigationService)
{
_ea = eventAggregator;
}
In the viewModel, whenever I want to set the focus to a field, I'm sending an event :
_ea.GetEvent<FocusChanged>().Publish("Source");
And in my view's code behind, I handle this event:
IEventAggregator _ea;
public MainPage(IEventAggregator eventAggregator)
{
InitializeComponent();
_ea = eventAggregator;
_ea.GetEvent<FocusChanged>().Subscribe(SetFocusOnControl); //Name of method which will handle this event
}
/// set the focus on entry based on parameter
/// each event value will set focus on a specific entry (example: source is linked to entry txtScanSrc)
private async void SetFocusOnControl(String fieldName)
{
Entry l_view;
switch(fieldName)
{
case "source": l_view = this.FindByName<Entry>("txtScanSrc"); break;
case "quantity": l_view = this.FindByName<Entry>("txtQty"); break;
case "tote": l_view = this.FindByName<Entry>("txtScanTote"); break;
case "pallet": l_view = this.FindByName<Entry>("txtScanPalout"); break;
case "destination": l_view = this.FindByName<Entry>("txtScanDest"); break;
default: l_view = this.FindByName<Entry>("txtScanSrc"); break;
}
await WaitAndExecute(500, () => { l_view.Focus(); });
}
There's a way of doing this using the Accessibility APIs of each of the platforms. Xamarin forms doesn't have all the platform features of accessibility yet so you d have to create a custom renderer and then call the focus method in a life cycle event of the page.
So calling this Focus function would cause the app to focus on that element. You generally don't want to do it because the app purposefully focuses on what it does so accessible users have a consistent experience. But if you really want to override the default behavior, in Android it's something like this
public static void Focus(View view)
{
view.SendAccessibilityEvent(EventTypes.ViewFocused);
}
And in iOS, you have to use the PostNotification apis which will be some variation of this
UIAccessibility.PostNotification(UIAccessibilityPostNotification.ScreenChanged, entry element)
You can look more into Accessibility Focus to get the exact answer
Related
I need to just show a message (Under certain circumstance) when I'm leaving a screen.
Found that there's a method called OnDisappearing() that is called when the form is being unloaded (Or also being overlaped by a new one).
What I found:
https://forums.xamarin.com/discussion/89563/intercept-page-leaving-event
https://learn.microsoft.com/en-us/dotnet/api/xamarin.forms.page.ondisappearing?view=xamarin-forms
Issue is that if I just copy the code as it is I get an error cause of the override (no suitable method found to override) that won't let me leave the code as is:
*Same happens with OnBackButtonPressed()
Modified it and just left it without the override and it just won't be called by any mean..
protected void OnDisappearing()
{
Exiting();
}
private async void Exiting()
{
System.Threading.Tasks.Task tmpShouldExit = Application.Current.MainPage.DisplayAlert("Hello", "Hi", "OK");
}
Is something I'm missing?
Is there any other method I can use?
Thanks
As Jason made me notice.
I was placing this in the ViewModel and it has to be in the code of the View cause you're overriding a Method of the Page.
Then if you want to access a method of the ViewModel from the view you can create a BindingContext to do so:
using MyProject.PageModels;
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace MyProject.Pages
{
public partial class MyViewPage : ContentPage
{
public MyViewPage()
{
InitializeComponent();
NavigationPage.SetBackButtonTitle(this, string.Empty);
}
protected override void OnDisappearing()
{
base.OnDisappearing();
var pageViewModel = (MyViewModel)this.BindingContext;
if(pageViewModel.CertainConditionShowAlert())
{
System.Threading.Tasks.Task tmpShouldExit = Application.Current.MainPage.DisplayAlert("Hi", "Hello", "OK");
}
}
}
}
My Master page is defined in a separate ContentPage with it's own ViewModel which handles commands for navigating and swapping out the detail page, but it's not working; navigating to a Detail Page from the Master Page VM causes the Master page to disappear. As I understanding you need to navigate from the MasterDetailPage VM. Is there any way to pass the Master Page's VM to the MasterDetailPage VM, or should I be doing something else?
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
x:Class="NGT.Views.MyMasterDetailPage"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:pages="clr-namespace:NGT.Views"
MasterBehavior="Split">
<MasterDetailPage.Master>
<pages:MyMenuPage/>
</MasterDetailPage.Master>
</MasterDetailPage>
and in the Master's Page ViewModel (not the MasterDetailPage ViewModel):
public class MyMenuPageViewModel : ChildBaseViewModel, INavigationAware
{
public MyMenuPageViewModel (INavigationService navigationService)
{
this.navigationService = navigationService;
this.ChangeDetailPage = new DelegateCommand(this.OpenDetailPage)
}
public async void OpenDetailPage()
{
await this.NavigationService.NavigateAsync("FAQPage", animated: false);
}
}
For now I have created an event for every time I want to swap out my detail page and invoke the event from MyMenuPageVM like so OnPageChanges?.Invoke("Page1"); I then subscribe to the event on my MasterDetailPage VM to handle swapping out the detail page.
On MyMenuPageVM (Master Page):
public delegate void MyEventHandler(string page);
public static event MyEventHandler OnPageChanges;
Subscribe to event on MasterDetailPageVM and handle.
public void OnNavigatedTo(INavigationParameters parameters)
{
MyMenuPageViewModel.OnPageChanges += this.ChangePage;
}
public async void ChangePage(string page)
{
switch (page)
{
case "Page1":
await this.navigationService.NavigateAsync("Page1");
break;
case "Page2":
await this.navigationService.NavigateAsync("Page2");
break;
}
}
Not sure this is the right thing, but it works. :)
I would like to use Rg.Plugins.Popup for Xamarin.Forms but unfortunately I cannot add PopupPage to the project. I am using VIsual Studio 2017. In AddNewItem window there is no PopupPage at all.
I tried to add ContentPage instead like this:
public partial class CustomPopupPage : ContentPage
{
public CustomPopupPage ()
{
InitializeComponent ();
}
}
but whenever i try to change type ContentPage to PopupPage I get following error: Partial declarations of 'CustomPopupPage' must not specify different base classes.
The problem is that second partial class is in auto-generated file CustomPopupPage.xaml.g.cs and I cannot modify that file because each time application is compiling it rewrites that file.
I think I am missing something obvious here because demo is working fine.
PopupPage is a subclass of ContentPage .So you have to add a new ContentPage and change the superclass both in xaml and code benind .
Firstly , install the package Rg.Plugins.Popup from nuget in share project and specific platform (iOS and Android).
The plugin requires to be initialized. To use a PopupPage inside an application, each platform application must initialize the Rg.Plugins.Popup. This initialization step varies from platform to platform and is discussed in the following sections.
iOS ->AppDelegate.cs
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Rg.Plugins.Popup.Popup.Init();
global::Xamarin.Forms.Forms.Init ();
LoadApplication (new App ());
return base.FinishedLaunching (app, options);
}
Android->MainActivity
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Rg.Plugins.Popup.Popup.Init(this, bundle);
Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication (new App ());
}
xaml
<?xml version="1.0" encoding="utf-8" ?>
<pages:PopupPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations;assembly=Rg.Plugins.Popup"
x:Class="MyProject.MyPopupPage">
<!--You can set an animation in the xaml file or in the csharp code behind-->
<pages:PopupPage.Animation>
<animations:ScaleAnimation
PositionIn="Center"
PositionOut="Center"
ScaleIn="1.2"
ScaleOut="0.8"
DurationIn="400"
DurationOut="300"
EasingIn="SinOut"
EasingOut="SinIn"
HasBackgroundAnimation="True"/>
</pages:PopupPage.Animation>
<!--You can use any elements here which are extended from Xamarin.Forms.View-->
<StackLayout
VerticalOptions="Center"
HorizontalOptions="Center"
Padding="20, 20, 20, 20">
<Label
Text="Test"/>
</StackLayout>
</pages:PopupPage>
in code behind
public partial class MyPopupPage : Rg.Plugins.Popup.Pages.PopupPage
{
public MyPopupPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
}
}
Update
It seems an existing issue of vs 2017 , on VS 2019 it works fine . And I will post this issue to product teams .
I am using Prism for Xamarin Forms and Implemented Modularity in the applications. I have .Droid Project, Default PCL Project which has App.xaml file and other content pages. And I have 4 PCL projects which are created as Modules by implementing IModule. Modules are configured in the App.xaml.cs.
My problem is when app initialized , the login page, home page which are in default PCL project loaded properly. But any content page in Modules are Initializing exactly three times every time I navigated to it using _navigationService.NavigateAsync("") from another page either from the same module or the other module. Content page constructor was called three times along with the corresponding ViewModel constructor, but the OnNavigationCompleted in VM is running only once.
May be because of this issue or some other reason, my Content page in the module is taking few more fractions to load fully. This can be easily noticed while loading. Overall Navigation of Content Page in the module is not so smooth.
Is there any solution for this? I have already tried keeping an empty content
in the module page without any controls and the result is same.
Below are the code snippets of my project.
Configure Module in app.xaml.cs
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
Type module1 = typeof(QNPL.Mobile.Module1);
Type module2 = typeof(QNPL.Mobile.Module2);
Type module3 = typeof(QNPL.Mobile.Module3);
Type module4 = typeof(QNPL.Mobile.Module4);
ModuleCatalog.AddModule(
new ModuleInfo()
{
ModuleName = module1.Name,
ModuleType = module1,
InitializationMode = InitializationMode.OnDemand
});
ModuleCatalog.AddModule(
new ModuleInfo()
{
ModuleName = module2.Name,
ModuleType = module2,
InitializationMode = InitializationMode.OnDemand
});
ModuleCatalog.AddModule(
new ModuleInfo()
{
ModuleName = module3.Name,
ModuleType = module3,
InitializationMode = InitializationMode.OnDemand
});
ModuleCatalog.AddModule(
new ModuleInfo()
{
ModuleName = module4.Name,
ModuleType = module4,
InitializationMode = InitializationMode.OnDemand
});
}
private async void MenuItemClick(object value)
{
if (value != null)
{
MenuDetail menuItem = (MenuDetail)value;
SelectedMenu = menuItem;
if (!string.IsNullOrEmpty(SelectedMenu.ModuleName))
{
_moduleManager.LoadModule(SelectedMenu.ModuleName);
}
await _navigationService.NavigateAsync(SelectedMenu.URL, navParams);
}
}
Module Page:
using Microsoft.Practices.Unity;
using Prism.Modularity;
using Prism.Unity;
using QNPL.Mobile.Module1.API;
using QNPL.Mobile.Module1.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace QNPL.Mobile.Module1
{
public class Module1 : IModule
{
private readonly IUnityContainer _unityContainer;
public Module1(IUnityContainer unityContainer)
{
_unityContainer = unityContainer;
}
public void Initialize()
{
_unityContainer.RegisterTypeForNavigation<Module1HomePage>();
_unityContainer.RegisterPopupNavigationService();
_unityContainer.RegisterType<IApiInterface, ApiInterface>();
}
}
}
Content Page in Module1:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="QNPL.Mobile.Module1.Views.Module1HomePage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:prismBehaviors="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
x:Name="Module1HomePage"
Title="Home Page"
prism:ViewModelLocator.AutowireViewModel="True">
<ContentPage.Content>
</ContentPage.Content>
</ContentPage>
ContentPage .cs file:
using Xamarin.Forms;
namespace QNPL.Mobile.Module1.Views
{
public partial class Module1HomePage : ContentPage
{
public Module1HomePage()
{
InitializeComponent();
}
}
}
i create a dependency property to close a view from view model,
dependencyProperty:
public static class WindowBehaviors
{
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.RegisterAttached("IsOpen"
, typeof(bool),
typeof(WindowBehaviors),
new UIPropertyMetadata(false, IsOpenChanged));
private static void IsOpenChanged(DependencyObject obj,DependencyPropertyChangedEventArgs args)
{
Window window = Window.GetWindow(obj);
if (window != null && ((bool)args.NewValue))
window.Close();
}
public static bool GetIsOpen(Window target)
{
return (bool)target.GetValue(IsOpenProperty);
}
public static void SetIsOpen(Window target, bool value)
{
target.SetValue(IsOpenProperty, value);
}
}
and use it in my xaml like this:
<window
...
Command:WindowBehaviors.IsOpen="True">
it work's fine,but when i want to bind it to a property in viewModel,it dosen't work,and i guess,it dosen't work because i define the resource later in xaml.
in xaml:
<Window.Resources>
<VVM:myVieModel x:Key="myVieModel"/>
</Window.Resources>
and i don't know what should i do,where should i put this:
Command:WindowBehaviors.IsOpen="{binding Isopen}"
public MainWindow()
{
InitializeComponent();
// DO THIS
this.DataContext = Resources["myVieModel"];
}
You need to bind the data context for the scope where your binding is in. Usually this is fairly high up in your XAML, usually the first element in your form or control.
In your case, the data context beeing a static resource the folllowing should work:
<grid DataContext="{StaticResource myVieModel}">
<!-- the code with the binding goß into here -->
</grid>
Actually this is the same as ebattulga suggests, just the XAML way (no code behind).
Thanks for your helps,i fixed it and here is my solution,
i used to use MVVMToolkit but now i'm useing MVVMlight and as you know in MVVMLight,we just define Application Resources Once in App.xaml.so we can bind all the window's properties simply,hope this can help some people who has the same problem!!
app.xaml
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
and in the window(view)
DataContext="{Binding DefaultSpecItemVM, Source={StaticResource Locator}}"
and it works perfect.:D