Unity3D: How to detect when a button is being held down and released [duplicate] - user-interface

This question already has answers here:
How to detect click/touch events on UI and GameObjects
(4 answers)
Closed 3 years ago.
I have a UI button. I want to show a text when the user is pressing the button and hide the text when the user releases the button.
How can I do this?

this anwser is basically fine but there is a huge drawback: You can't have additional fields in the Inspector since Button already has a built-in EditorScript which overwrites the default Inspector - You would need to extend this via a custom Inspector every time.
I would instead implement it as completely additional component implementing IPointerDownHandler and IPointerUpHandler (and maybe also IPointerExitHandler to reset also when exiting the button while holding the mouse/pointer still pressed).
For the doing something while the button stays pressed I'ld use a Coroutine.
In general I'ld use UnityEvents:
[RequireComponent(typeof(Button))]
public class PointerDownUpHandler : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler, IPointerExitHandler
{
public UnityEvent onPointerDown;
public UnityEvent onPointerUp;
// gets invoked every frame while pointer is down
public UnityEvent whilePointerPressed;
private Button _button;
private void Awake()
{
_button = GetComponent<Button>();
}
private IEnumerator WhilePressed()
{
// this looks strange but is okey in a Coroutine
// as long as you yield somewhere
while(true)
{
whilePointerPressed?.Invoke();
yield return null;
}
}
public void OnPointerDown(PointerEventData eventData)
{
// ignore if button not interactable
if(!_button.interactable) return;
// just to be sure kill all current routines
// (although there should be none)
StopAllCoroutines();
StartCoroutine(WhilePressed);
onPointerDown?.Invoke();
}
public void OnPointerUp(PointerEventData eventData)
{
StopAllCoroutines();
onPointerUp?.Invoke();
}
public void OnPointerExit(PointerEventData eventData)
{
StopAllCoroutines();
onPointerUp?.Invoke();
}
// Afaik needed so Pointer exit works .. doing nothing further
public void OnPointerEnter(PointerEventData eventData) { }
}
Than you can reference any callback in onPointerDown, onPointerUp and whilePointerPressed just the way you would do it with the onClick event of the Button.

You have to create your own custom button by extending Button class and override method OnPoiterDown and OnPointerUp.
Attach MyButton component instead of Button to your gameobject
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class MyButton : Button
{
public override void OnPointerDown(PointerEventData eventData)
{
base.OnPointerDown(eventData);
Debug.Log("Down");
//show text
}
public override void OnPointerUp(PointerEventData eventData)
{
base.OnPointerUp(eventData);
Debug.Log("Up");
//hide text
}
}

Related

How to intercept Navigation Bar Back Button Clicked in Xamarin Forms?

I have a xamarin form page where a user can update some data in a form. I need to intercept the Navigation Bar Back Button Clicked to warn the user if some data have not been saved.How to do it?
I'm able to intercept the hardware Bar Back Button Clicked in Android using the Android.MainActivity.OnBackPressed(), but that event is raised only on hardware Bar Back Button Clicked, not on Navigation Bar Back Button Clicked.
I tried also to override Xamarin.Forms.NavigationPageOnBackButtonPressed() but it doesn't work. Why?
Any one have already solved that issue?
I also tried by overriding OnDisappear(), there are two problems:
The page has already visually disappeared so the "Are you sure?" dialog appears over the previous page.
Cannot cancel the back action.
So, is it possible to intercept the navigation bar back button press?
I was able to show a confirmation dialog that could cancel navigation by overriding the following methods in the FormsApplicationActivity.
// navigation back button
public override bool OnOptionsItemSelected(IMenuItem item)
{
if (item.ItemId == 16908332)
{
var currentViewModel = (IViewModel)navigator.CurrentPage.BindingContext;
currentViewModel.CanNavigateFromAsync().ContinueWith(t =>
{
if (t.Result)
{
navigator.PopAsync();
}
}, TaskScheduler.FromCurrentSynchronizationContext());
return false;
}
else
{
return base.OnOptionsItemSelected(item);
}
}
// hardware back button
public async override void OnBackPressed()
{
var currentViewModel = (IViewModel)navigator.CurrentPage.BindingContext;
// could display a confirmation dialog (ex: "Cancel changes?")
var canNavigate = await currentViewModel.CanNavigateFromAsync();
if (canNavigate)
{
base.OnBackPressed();
}
}
The navigator.CurrentPage is a wrapper around the INavigation service. I do not have to cancel navigation from modal pages so I am only handling the NavigationStack.
this.navigation.NavigationStack[this.navigation.NavigationStack.Count - 1];
The easiest, as #JordanMazurke also somewhat mentions, since the event for the back button cannot be handled currently (other than the physical back button for Android), is to either:
NavigationPage.ShowHasBackButton(this, false)
Pushing a Modal instead of a Page
Then afterwards, you can add an ActionbarItem from where you can handle the Event.
I personally spoke to the iOS team from Xamarin concerning this matter, and they basically told me we shouldn't expect support for handling the Event for the BackButtonPressed in the NavigationBar. The reason being, that on iOS, it's bad practice for the users to receive a message when Back is pressed.
Best way I have found is adding my own NavigationRenderer to intercept the navigation methods and a simple Interface
[assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomerMobile.Droid.NavigationPageRenderer))]
public class NavigationPageRenderer : Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer
{
public Activity context;
public NavigationPageRenderer(Context context)
: base(context)
{}
protected override Task<bool> OnPushAsync(Page page, bool animated) {...}
protected override Task<bool> OnPopToRootAsync(Page page, bool animated){...}
protected override Task<bool> OnPopViewAsync(Page page, bool animated)
{
// if the page implements my interface then first check the page
//itself is not already handling a redirection ( Handling Navigation)
//if don't then let the handler to check whether to process
// Navitation or not .
if (page is INavigationHandler handler && !handler.HandlingNavigation
&& handler.HandlePopAsync(page, animated))
return Task.FromResult(false);
return base.OnPopViewAsync(page, animated);
}
}
Then my INavigationHandler interface would look like this
public interface INavigationHandler
{
public bool HandlingNavigation { get; }
public bool HandlePopAsync(Xamarin.Forms.Page view, bool animated);
public bool HandlePopToRootAsync(Xamarin.Forms.Page view, bool animated);
public bool HandlePuchAsync(Xamarin.Forms.Page view, bool animated);
}
Finally in any ContentView, in this example when trying to navigate back I'm just collapsing a menu and preventing a navigation back.
public partial class MenusList : INavigationHandler
{
public bool HandlingNavigation { get; private set; }
public bool HandlePopAsync(Page view, bool animated)
{
HandlingNavigation = true;
try
{
if (Menu.Expanded)
{
Menu.Collapse();
return true;
}
else return false;
}
finally
{
HandlingNavigation = false;
}
}
}
This is an inherently difficult task and the only way I got around it was to remove the back button entirely and then handle the backwards navigation from a 'save' button.
I have done a brief search of the Xamarin.Forms forum and the following has been suggested:
public override bool OnOptionsItemSelected(Android.Views.IMenuItem item)
{
return false;
}
The link for the post is as follows:
https://forums.xamarin.com/discussion/21631/is-there-nay-way-of-cancelling-the-back-button-event-from-the-navigationpage
As has already been said - you cannot do this cross-platform. However, you can handle it natively with arguably not so much effort: https://theconfuzedsourcecode.wordpress.com/2017/03/12/lets-override-navigation-bar-back-button-click-in-xamarin-forms/
The article covers iOS and Android. If you have a UWP project you'll have to hammer your own solution for it.
Edit:
Here is the UWP solution! It actually turned out to be pretty easy – there is just one back button and it’s supported by Forms so you just have to override ContentPage’s OnBackButtonPressed:
protected override bool OnBackButtonPressed()
{
if (Device.RuntimePlatform.Equals(Device.UWP))
{
OnClosePageRequested();
return true;
}
else
{
base.OnBackButtonPressed();
return false;
}
}
async void OnClosePageRequested()
{
var tdvm = (TaskDetailsViewModel)BindingContext;
if (tdvm.CanSaveTask())
{
var result = await DisplayAlert("Wait", "You have unsaved changes! Are you sure you want to go back?", "Discard changes", "Cancel");
if (result)
{
tdvm.DiscardChanges();
await Navigation.PopAsync(true);
}
}
else
{
await Navigation.PopAsync(true);
}
}
Back button appearance and behavior can be redefined by setting the BackButtonBehavior attached property to a BackButtonBehavior object.
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding GoBackCommand}"/></Shell.BackButtonBehavior>
GobackCommand defined in your View Model.
You can follow below link for more details:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/navigation
In Xamarin.Forms, the Page class has an OnBackButtonPressed() method that you can tap into for all platforms

JavaFX blocking native key event handling for treeview

I would like to change handling of key pressed events for example to do something else when user press up/down arrows but when i add eventHandler by setOnKeyPressed/setOnKeyReleased i cant stop native handling of those keys.
Example:
treeView.setOnKeyPressed(new EventHandler<KeyEvent>() {
private KeyCodeCombination prevNodeKeyCombination = new KeyCodeCombination(KeyCode.UP);
private KeyCodeCombination nextNodeKeyCombination = new KeyCodeCombination(KeyCode.DOWN);
public void handle(KeyEvent event)
{
if (prevNodeKeyCombination.match(event))
{
selectPrevSibling();
}
else if (nextNodeKeyCombination.match(event))
{
selectNextSibling();
}
event.consume(); // i try to block anything that goes by
}
});
Any idea how to override native key handling, if necessary i can try extend TreeView if that helps in something?
To override native handling of event use addEventFilter in my case it was:
treeView.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() { ... }

GWT capture event in RichTextArea or in IFrame

I have a RichTextArea
private RichTextArea richTextArea;
and I'm trying to capture a paste event like this:
DOM.sinkEvents((com.google.gwt.user.client.Element) richTextArea.getElement(), com.google.gwt.user.client.Event.ONPASTE);
DOM.setEventListener((com.google.gwt.user.client.Element) richTextArea.getElement(), new EventListener(){
#Override public void onBrowserEvent(Event event) {
switch (event.getTypeInt()) {
case Event.ONPASTE: Window.alert("hey");break;
}
}
});
But it doesn't work, when I paste text on the richTextArea the alert is not triggered.
Any idea how to capture this paste event?
Thanks!
You cannot add the event to the RichTextArea, which actually is an iframe, but to it's body.
Although you could use jsni, I would use gwtquery because its simplicity:
// First attach the widget to the DOM
RootPanel.get().add(richTextArea);
// We only can bind events to the content, once the iframe document has been created,
// and this happens after it has been attached. Note that richtTextArea uses a timeout
// to initialize, so we have to delay the event binding as well
$(richTextArea).delay(1, lazy().contents().find("body").bind(Event.ONPASTE, new Function(){
#Override public boolean f(Event e) {
Window.alert("OnPaste");
return true;
}
}).done());
Have you seen the rendered HTML of RichTextArea ? it's an iframe not an actual textarea input type. it sets the user input under a body element. So that's why you don't get sinked onpaste events. For example if you listen to onpaste on TextArea widget it works fine.
private static class MyTextArea extends TextArea
{
public MyTextArea()
{
sinkEvents(Event.ONPASTE);
}
#Override
public void onBrowserEvent(Event event)
{
if(event.getTypeInt() == Event.ONPASTE)
{
Window.alert("text pasted !");
}
super.onBrowserEvent(event);
}
}
maybe you can bind a handler to that iframe body element using JSNI and get the callback on that event (haven't tried it though )
Just for the sake of completeness, the native (JSNI) solution would be something like:
setPastehandler(richTextArea.getElement());
private native void setPasteHandler(Element e) /*-{
e.contentDocument.onpaste = function (event) {
alert("pasted!");
};
}-*/;

How to dismiss a Alert Dialog in Mono for android correctly?

In my application i have a Custom AlertView, which works quite good so far. I can open it the first time, do, what i want to do, and then close it. If i want to open it again, i'll get
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first
so, here some code:
public Class ReadingTab
{
...
private AlertDialog AD;
...
protected override void OnCreate(Bundle bundle)
{
btnAdd.Click += delegate
{
if (IsNewTask)
{
...
AlertDialog.Builer adb = new AlertDialog.Builer(this);
...
View view = LayoutInflater.Inflate(Resource.Layout.AlertDView15ET15TVvert, null);
adb.setView(view)
}
AD = adb.show();
}
}
}
that would be the rough look of my code.
Inside of btnAdd are two more buttons, and within one of them (btnSafe) i do AD.Dismiss() to close the Alert dialoge, adb.dispose() hasn't done anything.
the first time works fine, but when i call it the secon time, the debugger holds at AD = adb.show(); with the Exception mentioned above.
So what do i have to do, to remove the Dialoge from the parent? i can't find removeView() anywhere.
If you are setting up an AlertView once and then using it in multiple places (especially if you are using the same AlertView across different Activities) then you should consider creating a static AlertDialog class that you can then call from all over the place, passing in the current context as a parameter each time you want to show it. Then when a button is clicked you can simply dismiss the dialog and set the instance to null. Here is a basic example:
internal static class CustomAlertDialog
{
private static AlertDialog _instance;
private const string CANCEL = #"Cancel";
private const string OK = #"OK";
private static EventHandler _handler;
// Static method that creates your dialog instance with the given title, message, and context
public static void Show(string title,
string message,
Context context)
{
if (_instance != null)
{
throw new Exception(#"Cannot have more than one confirmation dialog at once.");
}
var builder = new AlertDialog.Builder(context);
builder.SetTitle(title);
builder.SetMessage(message);
// Set buttons and handle clicks
builder.SetPositiveButton(OK, delegate { /* some action here */ });
builder.SetNegativeButton(CANCEL, delegate { /* some action here */});
// Create a dialog from the builder and show it
_instance = builder.Create();
_instance.SetCancelable(false);
_instance.Show();
}
}
And from your Activity you would call your CustomAlertDialog like this:
CustomAlertDialog.Show(#"This is my title", #"This is my message", this);

How do I mask the current page behind a modal dialog box in vanilla GWT?

I've built a log-in composite that I am displaying in my application entry-point to the user. Upon entry of the username and password, I am sending the username and password to the server via a RemoteService and will receive back an object containing the ClientSession. If the ClientSession is a valid object (recognised username and password), I wish to display the main application panel otherwise I want to display the login dialog again (with an error message).
My question is, that during the async call to the server, how to I mask the screen so that the user cannot click anything whilst the Session is obtained from the server?
I know that the login should be fast, but the Session object contains a lot of Client Side cached values for the current user that is used to generate the main panel. This may take a fraction of a second or up to 5 seconds (I can't control the speed of the underlying infrastructure unfortunately) so I want to mask the screen until a timeout is reached then allow the user to try again.
I have done this exact operation before using GWT Ext, but vanilla GWT seems to have a lot less samples unfortunately.
Thanks
Chris
The GWT class PopupPanel has an optional "glass panel" that blocks interaction with the page underneath.
final PopupPanel popup = new PopupPanel(false, true); // Create a modal dialog box that will not auto-hide
popup.add(new Label("Please wait"));
popup.setGlassEnabled(true); // Enable the glass panel
popup.center(); // Center the popup and make it visible
You might want to check out GlassPanel from the GWT Incubator project. AFAICT it's not perfect, but should be of some help nevertheless ;)
You can also use a dialog box for this purpose.
Here is the code how to use it.
public class NTMaskAlert extends DialogBox {
private String displayText;
private String message;
private static NTMaskAlert alert;
Label lable;
private NTMaskAlert(String text) {
setText(text);
setWidget(new Image(GWT.getModuleBaseURL()
+ "/images/ajax-loader_1.gif"));
setGlassEnabled(true);
setAnimationEnabled(true);
super.show();
super.center();
WorkFlowSessionFactory.putValue(WorkFlowSesisonKey.MASKING_PANEL, this);
}
public static void mask(String text) {
if (text != null)
new NTMaskAlert(text);
else
new NTMaskAlert("Processing");
}
public static void unMask() {
NTMaskAlert alert = (NTMaskAlert) WorkFlowSessionFactory
.getValue(WorkFlowSesisonKey.MASKING_PANEL);
if (alert != null) {
alert.hide();
alert = null;
}
}
public void setDisplayText(String displayText) {
this.displayText = displayText;
alert.setText(displayText);
}
public String getDisplayText() {
return displayText;
}
public void setMessage(String message) {
this.message = message;
lable.setText(message);
}
public String getMessage() {
return message;
}
}
Use static mask and unmask method for operations.
This is my solution:
public class CustomPopupPanel extends PopupPanel {
private Label label = new Label();
public CustomPopupPanel() {
super(false, true); // Create a modal dialog box that will not auto-hide
super.setGlassEnabled(true); // Enable the glass panel
super.add(label); // Add the widget label into the panel
}
public CustomPopupPanel(String text) {
this();
this.mask(text);
}
public final void mask(String text) {
label.setText(text);
super.center(); // Center the popup and make it visible
}
public void unmask() {
super.hide(); // Hide the popup
}
}

Resources