I am a newbie in xamarin forms app development, currently, I am facing an issue in overriding the toolbar back button onclick. In ios, I am able to achieve but in android its not working can anyone help me out on how to achieve this in my project.
By default it works on iOS and on Android physical back button only. If you want to also support the navigation bar button, you need to use custom platform logic. Take a look on this blog post: Let’s Override Navigation Bar back button click in Xamarin For. He creates a common content page with custom action for back button:
public class CoolContentPage : ContentPage
{
/// <summary>
/// Gets or Sets the Back button click overriden custom action
/// </summary>
public Action CustomBackButtonAction { get; set; }
public static readonly BindableProperty EnableBackButtonOverrideProperty =
BindableProperty.Create(
nameof(EnableBackButtonOverride),
typeof(bool),
typeof(CoolContentPage),
false);
/// <summary>
/// Gets or Sets Custom Back button overriding state
/// </summary>
public bool EnableBackButtonOverride
{
get
{
return (bool)GetValue(EnableBackButtonOverrideProperty);
}
set
{
SetValue(EnableBackButtonOverrideProperty, value);
}
}
}
And then he calls CustomBackAction inside OnOptionsItemSelected method in Anroid code.
Best way to intercept Back Navigation ( Navigation in general ) would be by adding your NavigationPageRenderer so you can control the events and cancel or redirect them, look at my answer How to intercept Navigation Bar Back Button Clicked in Xamarin Forms?
I came to this post with same question about Xamarin forms navigation back button and later discovered that since Xamarin.Forms Shell this is done easily by overriding the OnNavigating method in the AppShell.xaml.cs file as I have done here:
protected override void OnNavigating(ShellNavigatingEventArgs e)
{
// Make sure it's safe to examine the current page
if ((Shell.Current != null) &&
(Shell.Current.CurrentPage != null))
{
Console.WriteLine($"{e.Source} {Shell.Current.Title}");
if (
// Detect Back Navigation
e.Source == ShellNavigationSource.Pop &&
// Cancel or Not, based on (for example) the Title of the current page.
(Shell.Current.Title != "My Main Page"))
{
e.Cancel();
Shell.Current.GoToAsync("..");
}
}
base.OnNavigating(e);
}
In case anyone else stumbles upon, I put a sample on GitHub of what worked for me for Android and iOS both.
Related
I'm new to Xamarin.Forms and mobile app development, so patience & kindness is appreciated! Am building a barcode scanner app with Xamarin.Forms PCL, trying to use MVVM. The scanner is an EXTERNAL bluetooth device (so can't use ZXing).
This project has a fixed requirement to use the scanner as a keyboard-type input and for the user to be able to quickly swap out one bluetooth device for another brand (so no device-specific APIs can be used). A second requirement is for the user to never be allowed to type anything directly into the Entry control. Input should come from the scanner and only the scanner, so therefore we don't ever want the keyboard showing on the scanning page.
There are other pages that have Entry controls where the user WILL need access to the keyboard, and the scanner should be able to stay connected to bluetooth even when a non-scanning screen is displayed. Therefore, I need a reliable way to set the soft keyboard to never be displayed on the scanning page (there is only one input control on this page, and it's intended for scanner use only), but to allow the keyboard to be accessed on other pages.
When on the scanning page, we want focus to always be set on the scanner's Entry control, so when the control gets a Completed event, we do stuff with the value received, then clear out the control and re-set focus on it to prepare for the next scan.
I have been stumbling around writing custom controls and android renderers, and with setting up dependencies (preferred), both with partial success. Either way, there's a timing issue related to how soon focus is set on the control. If there's not enough of a delay before focus is set, the soft keyboard stays visible. In the code sample provided, I added a short sleep delay, which mostly works to keep the keyboard hidden. However, the keyboard still "flashes" on the screen briefly with each scan, which looks terrible. Would really prefer a solution that is less hacky and ugly.
Is there a good, simple way to remove the soft keyboard entirely for a page, while still allowing an input control to receive focus, so that a scanned barcode can be received? And/or any other suggestions that will allow me to still meet the requirements?
(PS: the scanning page does not currently use MVVM binding. Just trying to get the keyboard to go away first, then will work on binding.)
Below is one way I tried to solve it. There were others as well. NOTE: Ultimately I went with a completely different approach which I'll post as an answer.
The custom control (in PCL):
using Xamarin.Forms;
namespace MyPCL.Views
{
//See ScanEntryRenderer in the Android project.
public class ScanEntryControl : Entry
{
public ScanEntryControl() { }
}
}
The Xaml page (notice InputTransparent = "True" on the custom control. This is so the user cannot directly enter input on the android device. All input must come from the bluetooth scanner).
<?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:MyPCL.Views"
x:Class="MyPCL.Views.ScanTestPage"
Title="Scan Test Page" >
<ContentPage.Content>
<StackLayout>
<Label Text="Scanner Test" />
<local:ScanEntryControl x:Name="BarcodeEntry"
Completed="BarcodeEntryCompleted"
InputTransparent="True"/>
<Label x:Name="ResultLabel" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
The code behind for the form:
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace MyPCL.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ScanTestPage : ContentPage
{
public ScanTestPage()
{
InitializeComponent();
BarcodeEntry.Focus();
}
protected override void OnAppearing()
{
base.OnAppearing();
BarcodeEntry.Focus();
}
private void BarcodeEntryCompleted(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(BarcodeEntry.Text))
{
ResultLabel.Text = "You entered: " + BarcodeEntry.Text;
BarcodeEntry.Text = string.Empty;
}
BarcodeEntry.Focus();
}
}
}
The Android renderer:
using Android.Content;
using Xamarin.Forms;
using MyPCL.Views;
using MyPCL.Droid;
using Xamarin.Forms.Platform.Android;
using Android.Views.InputMethods;
[assembly: ExportRenderer(typeof(ScanEntryControl), typeof(ScanEntryRenderer))]
namespace MyPCL.Droid
{
public class ScanEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
((ScanEntryControl)e.NewElement).PropertyChanging += OnPropertyChanging;
}
if (e.OldElement != null)
{
((ScanEntryControl)e.OldElement).PropertyChanging -= OnPropertyChanging;
}
// Disable the Keyboard on Focus
this.Control.ShowSoftInputOnFocus = false;
}
private void OnPropertyChanging(object sender, PropertyChangingEventArgs propertyChangingEventArgs)
{
// Check if the view is about to get Focus
if (propertyChangingEventArgs.PropertyName == VisualElement.IsFocusedProperty.PropertyName)
{
// Dismiss the Keyboard
InputMethodManager imm = (InputMethodManager)this.Context.GetSystemService(Context.InputMethodService);
imm.HideSoftInputFromWindow(this.Control.WindowToken, 0);
}
}
}
}
I have been stumbling around writing custom controls and android renderers, and with setting up dependencies (preferred), both with partial success.
You can use EditText.ShowSoftInputOnFocus to achieve it in your scanning page, then the keyboard will not appear when your entry gets the focus:
using Android.Content;
using Android.Views.InputMethods;
using Edi;
using Edi.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ScanEntryControl), typeof(ScanEntryRenderer))]
namespace Edi.Droid
{
public class ScanEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
((ScanEntryControl)e.NewElement).PropertyChanging += OnPropertyChanging;
}
if (e.OldElement != null)
{
((ScanEntryControl)e.OldElement).PropertyChanging -= OnPropertyChanging;
}
// Disable the Keyboard on Focus
this.Control.ShowSoftInputOnFocus = false;
}
private void OnPropertyChanging(object sender, PropertyChangingEventArgs propertyChangingEventArgs)
{
// Check if the view is about to get Focus
if (propertyChangingEventArgs.PropertyName == VisualElement.IsFocusedProperty.PropertyName)
{
// incase if the focus was moved from another Entry
// Forcefully dismiss the Keyboard
InputMethodManager imm = (InputMethodManager)this.Context.GetSystemService(Context.InputMethodService);
imm.HideSoftInputFromWindow(this.Control.WindowToken, 0);
}
}
}
}
In other pages you can still use Entry, so the keyboard will be appear.
UPDATE:
ScanEntryControl class in PCL:
using Xamarin.Forms;
namespace Edi
{
public class ScanEntryControl : Entry
{
}
}
.xaml file:
<?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:Edi"
x:Class="Edi.MainPage">
<ContentPage.Content>
<StackLayout>
<local:ScanEntryControl Text="ScanEntryControl"/>
<Entry Text="Entry"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
This answer does not solve the original issue directly, in the sense that it does not involve an Entry control. However, it was the only thing that worked for me, and ended up being a more elegant solution:
The Bluetooth scanner was in HID mode (Human Interface Device) by default, meaning the only way it could interact with the app was by imitating key presses, thereby necessitating an Entry (EditText) control, or similar. I switched the scanner to SPP mode (Serial Port Profile) and adapted the code from this page (see also the GitHub repo here, and for more info on HID vs SPP see this document).
The resulting code activates the scanner and then "listens" for input. When input is received, it is displayed in a Label rather than an Entry control.
There were other problems with the Entry control that I didn't mention prior: often it would add a repeat character to the front of the barcode and/or chop off one or more characters from the end. The SPP solution solved all that as well. If anyone wants the code I came up with, let me know. It will take some work to put together in a generic example, so not posting it at the moment.
I was facing the same problem. I had found one sample over in the Xamarin forums that IMHO contained the key solution:
You must override Focus() and must not call the base method. This gives you full control over the virtual keyboard. In all other solutions I have seen the virtual keyboard appears sometimes.
Of course your custom Entry needs methods to show/hide the keyboard. You would call them in your OnFocus() method. My sample control (see below) also has a bindable property that allows you to show the virtual keyboard automatically on Focus. So you may decide for every field if the keyboard should appear automatically or not.
In addition I have included another object that informs you if the virtual keyboard is currently visible and its size in case you need to size your layout accordingly.
Since this is quite a common question in several different forums I have decided to create a sample control and a small app to show the features.
In addition I wrote a detailed Readme that explains all crucial points of the implementation.
You will find it here: https://github.com/UweReisewitz/XamarinAndroidEntry
protected override void OnAppearing()
{
base.OnAppearing();
txtLotID.Focus();
}
private void OnLoad()
{
Init();
swScanMode.IsToggled = Global.IsScannable;
txtLotID.EnableKeyboard = !Global.IsScannable;
txtLotID.OnEntryScanned += BtnSearch_Clicked;
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
if (txtLotID.IsReadOnly)
{
txtLotID.Text = "";
**txtLotID.IsReadOnly = false;**
txtLotID.GetFocus();
}
return true;
});
}
I am working on xamarin forms. I am using Master detail page to open left menu. And its working fine. Right now menu hide only when user click on menu icon on top. But I want if menu is open, then if user click anywhere on screen, menu should hide.
How I can do this?
I've managed to do it using a custom renderer.
[assembly: ExportRenderer(typeof(BaseMasterDetailPage), typeof(MyMasterDetailPageRenderer))]
namespace Driver.Droid.Renderers
{
public class MyMasterDetailPageRenderer : MasterDetailPageRenderer
{
public override bool OnTouchEvent(Android.Views.MotionEvent e)
{
if (e.Action == Android.Views.MotionEventActions.Up)
new Task(CloseDrawers).Start(TaskScheduler.FromCurrentSynchronizationContext());
return base.OnTouchEvent(e);
}
}
}
how to reload a page (with a new template) when the orientation of the device changes?
I want a different layout in landscape mode. This is in NativeScript, not Java.
the correct xml file is selected if i arrive at the page in landscape, but if i change orientation, nothing gets reloaded.
Note that this is not a question of refreshing the CSS, it is a different XML file needed.
NativeScript solution:
First you need to tie into the orientation event. You have a couple ways you can tie into that event.
The first and easiest method is to install the nativescript-orientation plugin, it ties into the event globally and then it will just automatically run your exported function on each of the current page called orientation, each time the orientation changes.
To Install:
tns plugin install nativescript-orientation
Open your app.js file and add at the top of the file;
require('nativescript-orientation');
Then by creating:
exports.orientation = function(args) {
if (args.landscaped) { /* do landscape stuff */ }
else { /* do port */
};
on any page you want to be notified that the orientation changed, it will be called on those pages that have that function and you can handle the event how you need to.
However, if you prefer to not use a plugin, you can directly tie into the orientation event yourself by doing:
var application = require('application');
exports.onNavigateTo = function() {
application.on(application.orientationChangedEvent,myOrientationFunction);
}
exports.onNavigateFrom = function() {
application.off(application.orientationChangedEvent, myOrientationFunction);
function myOrientationFunction(args) {
// We have an orientation event
}
However you must ask to be notified of the event when your page first open and you must remove your self from the notification when your page closes. This is a lot of extra code per page that the plugin above just handles for you. Please note when you are doing this yourself you also need to to add the NavigateTo/NavigatedFrom to the <Page> tag in your Declarative UI XML file, otherwise those functions won't be called.
Ok, now that you have the event which ever way you prefer; lets look at how we can make your idea work.
Now, you are asking to switch layouts each time the page changes; this is typically the worst thing to do; but I will answer it first and then give you the alternative method that I use to do complex layouts that work in both Portrait and Landscape modes pretty much automatically.
MyPage-Landscape.xml
<Page><StackLayout><Label text="Landscape"/></StackLayout></Page>
MyPage-Portrait.xml
<Page><StackLayout><Label text="Portrait"/></StackLayout></Page>
MyPage-Landscape.js
var commonPage = require("./MyPage.js");
var frame = require('ui/frame');
exports.orientation = function(args) {
if (args.landscape === false) {
frame.topmost().navigate('MyPage-Portrait');
}
};
// Tie all the common page code into this pages exports
for (var key in commonPage) {
if (commonPage.hasOwnProperty(key)) {
exports[key] = commonPage[key];
}
}
MyPage-Portrait.js
var commonPage = require("./MyPage.js");
var frame = require('ui/frame');
exports.orientation = function(args) {
if (args.landscape === true) {
frame.topmost().navigate('MyPage-Landscape');
}
};
// Tie all the common page code into this pages exports
for (var key in commonPage) {
if (commonPage.hasOwnProperty(key)) {
exports[key] = commonPage[key];
}
}
MyPage.js
exports.TapHandler = function() { /* Someone Tapped */ }
exports.someOtherLogic = function() { /* More logic */ }
exports.etc = function() { /* etc */ }
You put all your common page logic in the MyPage file; but you specifically navigate to the specific landscape or portrait page; and each of them are responsible to navigate to the other version if the page orientation changes.
Notes about the above solution:
You need to navigate to the proper version of the page from any other page; ie. if you are in Landscape mode; when you navigate to another page; you need to make sure you navigate to the Landscaped version of the page.
the NS-Orientation plugin does give you a handle helper function to find out the current orientation to make this easier.
Remember to make the MyPage.js have all the common code; you want to try and eliminate any custom code on a specific page version.
Their is a frame reload command you can use; however it totally clears the history; meaning you can't navigate backwards. i.e. Page1 -> Page2, frame.reloadPage() means that the back button will NOT go back to Page1. If this is acceptable; you can make the above system a lot simpler; rather than create separate xml & js files you just need a myPage.landscape.xml and myPage.portrait.xml and you need to on every orientation change just call the frame.reloadPage();
Now to me the above is some serious overkill for what is probably a simple change that you need done between pages. So I'm going to describe how I do it in my apps; which has some pretty complex screens but they look very nice and completely change functionality on a orientation change.
This is part of the reason the NativeScript-orientation plugin was written. On a page orientation change will automatically add / remove a "landscape" class name to the <Page> element in your XML. What this allows you to do in your CSS is:
.myLabel {
font-size: 12;
background-color: blue;
height: 20;
}
.landscape .myLabel {
font-size: 16;
background-color: green;
height: 40;
}
If you haven't figured out where I am going with this; this allows you to have custom CSS for the page while in landscape mode vs it being in portrait mode. In addition when you use the exports.orientation function in union with it also you can then run custom code depending on the orientation.
So in my case; On a phone my scroll list is a single scroll list of items going up down and is sized perfectly to the screen, and looks very sharp. When you switch to landscape mode; it hides the actionbar, adds a fab button, resizes the entire grid item to fit with the same proportions and switches scrolling modes to right-left. The majority of the entire look change is done in pure css; and the rest is done in the exports.orientation function which handles things like switching scroll direction.
Disclaimer: I am the author of the NativeScript-orientation plugin
In your activity:
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setContentView(R.layout.my_layout);
}
Make sure you have layout XML with same name for both orientations
res/layout/my_layout.xml
res/layout-land/my_layout.xml
With correct resource file names Android system automatically reloads your Activities/Fragments with proper resources.
You can catch and handle events manually by setting android:configChanges (but it is a rare case).
Check some documentation:
Handling Runtime Changes
Providing Resources
Context:
Qt QML 5.6
I could not find related documentation to handle the Android navigation buttons in my QML app (triangle, square and circle, at the bottom of the screen).
Currently, when touching them, it just puts my app in the background. I would like to give them some logic.
Question:
Is it possible to manage those buttons in QML? Or will I have to deal with a c++ event handler? (If so, which code should one look after?)
Thanks
Poor man's solution:
Within the Window or application window scope, use
onClosing: {
do_what_you_need()
close.accepted = false
}
In do_what_you_need(), you may call Qt.quit if it is ok.
It is possible to manage these buttons from the QML. In QML, those key presses are handled exactly like key presses on a keyboard. For example, Qt.Key_Back refers to the back key(triangle) and Qt.Key_Home refers to the home key(square). Here is an example of listening for the home key in QML:
Keys.onPressed: {
if (event.key == Qt.Key_Home) {
console.log("Square button(home) pressed");
}
}
For more on the key enumerations in Qt, see this documentation: http://doc.qt.io/qt-5/qt.html#Key-enum
For completeness, example with ignoring the back button (do not minimize the app)
focus: true
Keys.onPressed: {
if (event.key == Qt.Key_Back) {
event.accepted = true
}
}
So building a new app specifically for the Android TV interface (lollipop leanback) and I'm using the PlaybackOverlayFragment that is provided by the framework which has a PlaybackControlsRow with all the usual controls on it.
The problem is, the default behavior is for the user to have to click the "Play" button to start the video and I want it to start automatically. That part is easy and I have it working but then the Play/Pause icons on the provided control are out of sync (showing play when should be pause) because the item was started outside of the events of clicking on that control.
Documentation is sparse on these framework elements and examining the class I can't find any public method that would allow me to put this control in the proper "mode" or tell it to display the play or pause icon myself.
Anyone with experience with these yet that would know how to do this?
In order to change the state of the button, even after adding your Actions to the Adapter, you'll need to notify the changes to the adapter that has your Action.
mPlayPauseAction.nextIndex(); // next index, if it was pause, it'll be play
notifyChanged(mPlayPauseAction);
// where notifyChanged(Action action) is:
private void notifyChanged(Action action) {
ArrayObjectAdapter adapter = mPrimaryActionsAdapter; // reference to your adapter
if (adapter.indexOf(action) >= 0) {
adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
return;
}
}
Well, I partially answered my own question.
If I know before the PlaybackControlsRow is created that I want to set it to the pause state (actually, playing state but showing pause button) then if I call setIndex(PlaypauseAction.PAUSE) on the PlayPauseAction before adding it to the controlsrow then it works.
It doesn't appear that I can modify it myself after adding it but that may be something else I'm doing wrong.