Long Touch Gestures in Xamarin Android Renderer - c#

I am trying to get long touch gestures into my xamarin app. I have a view where a tap brings you to an edit screen & a long touch reveals an options menu. I followed this guide on SO about implementing such a thing. The item I want to be long touchable is a Frame, so I wrote an extension for Frame. Here is this class:
public class FrameWithLongTouchGesture : Frame
{
public FrameWithLongTouchGesture()
{
}
public EventHandler LongPressActivated;
public void HandleLongPress(object sender, EventArgs e)
{
//Handle LongPressActivated Event
EventHandler eventHandler = this.LongPressActivated;
eventHandler?.Invoke((object)this, EventArgs.Empty);
}
}
As you can see I have added an event handler to this object. Now I then went about implementing a custom renderer for each platform, I started with iOS (since I am an iOS dev). Worked absolutely no problem, took 5 minutes to get working. So now I've come round to android, this should be even easier since the post I linked earlier shows you how to implement the renderer in android... great!....
Not so great :( No long touch event is handled AT ALL with the exact code in the post. I have set breakpoints, tried to write to the console but the gesture event handler is never fired. I can even see that the phone receives a touch down event because it prints to the console when I run it on my test device. I have absolutely no idea why android isn't letting me recognise this gesture, I have also played around with androids GestureDetector but that never fired either. Here is my android renderer:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Diagnostics;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Content;
using Android.Views;
using Android.Widget;
using LongTouchGestureDemo;
using LongTouchGestureDemo.Droid;
[assembly: ExportRenderer(typeof(FrameWithLongTouchGesture), typeof(FrameWithLongTouchGestureRenderer))]
namespace LongTouchGestureDemo.Droid
{
public class FrameWithLongTouchGestureRenderer : FrameRenderer
{
FrameWithLongTouchGesture view;
//GestureDetector gesture;
public FrameWithLongTouchGestureRenderer(Context context) : base(context)
{
//gesture = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener());
this.LongClick += (object sender, LongClickEventArgs e) => {
view.HandleLongPress(sender, e);
};
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
view = e.NewElement as FrameWithLongTouchGesture;
}
}
}
}
This is really frustrating because I cannot seem to implement core functionality into the android app. It doesn't help that I have no experience developing android, it doesnt seem as easy to implement gestures in droid as it does in iOS unfortunately :/
All help and suggestions are welcomed!
Thanks

You need a custom Gesture Listener to manage the long press. Here is the basic structure:
public class FrameWithLongTouchGestureRenderer : FrameRenderer
{
FrameWithLongTouchGesture view;
readonly MyGestureListener _listener;
readonly Android.Views.GestureDetector _detector;
public FrameWithLongTouchGestureRenderer()
{
_listener = new MyGestureListener();
_detector = new GestureDetector(_listener);
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
view = e.NewElement as FrameWithLongTouchGesture;
UpdateEventHandlers();
}
}
private void UpdateEventHandlers()
{
_listener.MyFrame = view;
GenericMotion += (s, a) => _detector.OnTouchEvent(a.Event);
Touch += (s, a) => _detector.OnTouchEvent(a.Event);
}
}
And then your Gesture Listener:
internal class MyGestureListener : GestureDetector.SimpleOnGestureListener
{
public FrameWithLongTouchGesture MyFrame { private get; set; }
public override void OnLongPress(MotionEvent e)
{
base.OnLongPress(e);
if (MyFrame != null)
{
MyFrame.HandleLongPress(this, new System.EventArgs());
}
}
}

Related

Unity - How can I popup a GUI button when a target is detected

I am new to Unity and Vuforia. I am trying to create an augmented reality experience where the user can scan across a target marker and it will reveal a button that they can click on. I have found couple of programs but it throws an error related to ITrackableEventHandler.
Error: Assets\scripts\ButtonPopup.cs(5,43): error CS0246: The type or namespace name 'ITrackableEventHandler' could not be found (are you missing a using directive or an assembly reference?)
using UnityEngine;
using System.Collections;
public class ButtonPopup : MonoBehaviour, ITrackableEventHandler {
private TrackableBehaviour mTrackableBehaviour;
private bool mShowGUIButton = false;
private Rect mButtonRect = new Rect(50,50,120,60);
void Start () {
mTrackableBehaviour = GetComponent<TrackableBehaviour>();
if (mTrackableBehaviour)
{
mTrackableBehaviour.RegisterTrackableEventHandler(this);
}
}
public void OnTrackableStateChanged(
TrackableBehaviour.Status previousStatus,
TrackableBehaviour.Status newStatus)
{
if (newStatus == TrackableBehaviour.Status.DETECTED ||
newStatus == TrackableBehaviour.Status.TRACKED)
{
mShowGUIButton = true;
}
else
{
mShowGUIButton = false;
}
}
void OnGUI() {
if (mShowGUIButton) {
// draw the GUI button
if (GUI.Button(mButtonRect, "Hello")) {
// do something on button click
}
}
}
}
I think you might be missing a namespace like
using Vuforia;
afaik the RegisterTrackableEventHandler is deprecated and now it is RegisterOnTrackableStatusChanged without the need for the interface maybe it's deprecated as well)
and OnGUI (also IMGUI) is pretty much obsolete/legacy and mainly only used for editor scripts and very very quick and dirty prototyping .. rather use the "new" UI System (uGUI) using the UI package and then just use SetActive
So first of all you would need to set up a Button within a Canvas somewhere in your Scene. If you go via Hierarchy View → right click → Create → UI → Button this automatically creates a new Canvas if you don't have any so far and adds a Button to it.
And the code would be somewhat like
using UnityEngine;
using System.Collections;
using Vuforia;
using UnityEngine.UI;
public class ButtonPopup : MonoBehaviour
{
[SerializeField] private TrackableBehaviour _trackableBehaviour;
// Reference your Button via the Inspector
[SerializeField] private Button _button;
private void Start ()
{
if(!_trackableBehaviour)
{
if(!TryGetComponent<TrackableBehaviour>(out _trackableBehaviour))
{
Debug.LogError("No TrackableBehavior!", this);
return;
}
_trackableBehaviour.RegisterOnTrackableStatusChanged(OnTrackableStateChanged);
}
private void OnTrackableStateChanged(TrackableBehaviour.Status previousStatus, TrackableBehaviour.Status newStatus)
{
_button.gameObject.SetActive(newStatus == TrackableBehaviour.Status.DETECTED || newStatus == TrackableBehaviour.Status.TRACKED);
}
}
Of course if you really have your reasons you can stick to the OnGUI .. it's just really not the state of the art ;)

Refresh map loading for Android

I am working on an app where the user has to discover places. I am using this to show areas with undiscovered places on a map:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/map/circle-map-overlay
But later while app grew in functionality, it no longer runs well. Here is the link to my github.
https://github.com/jakuboles/TDKInz2020/tree/master/TDK
After the user login TDK.Android.CustomMapRenderer.OnElementChanged function is called way faster before TDK.MainPage.xaml.cs.DisplayInMap where I set which places should be shown as discovered, and which as undiscovered. In result now app always on TDK.Android.CustomMapRenderer.OnElementChanged line 36 list of places always will have null and on calling OnMapReady to draw circles list is null.
Is there some way to call OnElementChanged once more when DisplayInMap will assign places to list? Or to make OnElementChanged run later, after DisplayInMap will set list.
Sorry if it's a complicated description; didn't know exactly how to properly describe.
Here's my Android renderer:
using Android.Content;
using Android.Gms.Maps.Model;
using Java.Lang;
using MapOverlay;
using MapOverlay.Droid;
using System.Collections.Generic;
using TDK.MapsCustoms;
using Xamarin.Forms;
using Xamarin.Forms.Maps.Android;
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace MapOverlay.Droid
{
public class CustomMapRenderer : MapRenderer
{
List<CustomCircle> circles;
public CustomMapRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Xamarin.Forms.Maps.Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
circles = formsMap.CircleList;
}
}
protected override void OnMapReady(Android.Gms.Maps.GoogleMap map)
{
base.OnMapReady(map);
foreach (var circle in circles)
{
var circleOptions = new CircleOptions();
circleOptions.InvokeCenter(new LatLng(circle.Position.Latitude, circle.Position.Longitude));
circleOptions.InvokeRadius(circle.Radius);
circleOptions.InvokeFillColor(0X66FF0000);
circleOptions.InvokeStrokeColor(0X66FF0000);
circleOptions.InvokeStrokeWidth(0);
NativeMap.AddCircle(circleOptions);
}
}
}
}
Move the line
circles = formsMap.CircleList;
from OnElementChanged to OnMapReady

Change starting page in app created with Windows Template Studio

I created an app with Windows Template Studio on Visual Studio 2017.
The app is mainly a NavigationDrawer with different pages.
Everything was ok, until I wanted to add a login page.
So I created the XAML of the login page, etc. But now I want it to show before the NavigationDrawer page on app startup.
I seeked some documentation about the App.xaml.cs to know what to change to do that but, because of the use of Windows Template Studio, the code is not really vanilla anymore.
I tried a few things and the only thing I'm able to do right now is to change the shell page of the NavigationDrawer to my Login page.
That's not exactly what I want because my first intention was to make the app unavailable until you log in, and because the NavigationDrawer is still usable the user can still do what he wants to.
My app.xaml.cs looks like this :
using System;
using BasePosteMobilite.Services;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
namespace BasePosteMobilite
{
public sealed partial class App : Application
{
private Lazy<ActivationService> _activationService;
private ActivationService ActivationService
{
get { return _activationService.Value; }
}
public App()
{
InitializeComponent();
// Deferred execution until used. Check https://msdn.microsoft.com/library/dd642331(v=vs.110).aspx for further info on Lazy<T> class.
_activationService = new Lazy<ActivationService>(CreateActivationService);
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
if (!args.PrelaunchActivated)
{
await ActivationService.ActivateAsync(args);
}
}
protected override async void OnActivated(IActivatedEventArgs args)
{
await ActivationService.ActivateAsync(args);
}
private ActivationService CreateActivationService()
{
return new ActivationService(this, typeof(ViewModels.LoginViewModel), new Lazy<UIElement>(CreateShell));
}
private UIElement CreateShell()
{
return new Views.ShellPage();
}
}
}
ShellPage.xaml.cs :
using System;
using BasePosteMobilite.ViewModels;
using Windows.UI.Xaml.Controls;
namespace BasePosteMobilite.Views
{
// TODO WTS: Change the icons and titles for all NavigationViewItems in ShellPage.xaml.
public sealed partial class ShellPage : Page
{
private ShellViewModel ViewModel
{
get { return ViewModelLocator.Current.ShellViewModel; }
}
public ShellPage()
{
InitializeComponent();
DataContext = ViewModel;
ViewModel.Initialize(shellFrame, navigationView, KeyboardAccelerators);
}
}
}
ViewModel.Initialize :
public void Initialize(Frame frame, WinUI.NavigationView navigationView, IList<KeyboardAccelerator> keyboardAccelerators)
{
_navigationView = navigationView;
_keyboardAccelerators = keyboardAccelerators;
NavigationService.Frame = frame;
NavigationService.NavigationFailed += Frame_NavigationFailed;
NavigationService.Navigated += Frame_Navigated;
_navigationView.BackRequested += OnBackRequested;
}
You can create a project with login required feature and you will see the following code from ActivateAsync method:
var silentLoginSuccess = await IdentityService.AcquireTokenSilentAsync();
if (!silentLoginSuccess || !IdentityService.IsAuthorized())
{
await RedirectLoginPageAsync();
}
That's it. If you want to redirect to your own page, write the detectation code under ActivationService.ActivateAsync(args) method. If you see the customer is not logged in. Call redirect login method. Here is the code from template studio about redirectlogin:
public async Task RedirectLoginPageAsync()
{
var frame = new Frame();
NavigationService.Frame = frame;
Window.Current.Content = frame;
await ThemeSelectorService.SetRequestedThemeAsync();
NavigationService.Navigate<Views.LogInPage>();
}

Raise event c# gives error: No overload matches delegate EventHandler

I am trying to make an event that gets raised when something happens. So other classes that contain a reference to the class that raised the event get notified.
This is what i have got so far:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DropArea : MonoBehaviour {
public event EventHandler ObjectChange;
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnTriggerEnter(Collider other)
{
var correctType = FindParentWithComponent(other.gameObject);
if (correctType != null)
{
var cc = correctType.GetComponent<Object_properties>();
if(cc.VariableTypeSelector == AcceptedVariableType)
{
DetectedObjectChange(null); // here i want to raise the event
}
}
}
protected virtual void DetectedObjectChange(EventArgs e)
{
EventHandler handler = ObjectChange;
if (handler != null)
{
handler(this, e );
}
}
}
this is the class that should get notified by the raised event:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IfStatement : MonoBehaviour {
public DropArea Base, Check, Increment;
void Start()
{
Base.ObjectChange += OnBaseObjectChange; //this line gives me an error so i cant compile.
}
// Update is called once per frame
void Update () {
}
void OnBaseObjectChange(System.Object sender)
{
Debug.Log("event is raised by element");
}
}
this is the error i get :
No overload for 'OnBaseObjectChange' matches delegate 'EventHandler'
I have never worked with events before but i cant really understand how to fix this. Sadly i also dont really understand the microsoft docs about events :(
If extra clarification is needed pls let me know!
All help is really appreaciated!
Just change this method. Because your event also requires EventArgs
void OnBaseObjectChange(System.Object sender, EventArgs e)
{
Debug.Log("event is raised by element");
}
Delegates hold signatures of methods. As you can see your event has a method type with two parameters. Trying to use it with a single parameter causes the error.
handler(this, e);
Alternatively, you can change the type of your event to something else. For example an Action<System.Object> to keep the current signature of your handler method, if you don't want to have an EventArgs in your event:
public event System.Action<System.Object> ObjectChange;
void OnBaseObjectChange(System.Object sender)
{
Debug.Log("event is raised by element");
}
Where you can call it like:
handler(this);

Awesomium not loading page or triggering any events

I want to do something a simple as loading a webpage. For some reason Awesomium is not updating properties such as IsLoading, or triggering events such as DocumentReady or LoadingFrameComplete and I have no idea why, can anyone help me out?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Awesomium.Core;
namespace DownloaderTest
{
class ParsingHelper
{
WebView wv;
public ParsingHelper(WebView web)
{
this.wv = web;
}
public void ParsingInitiation(string link)
{
wv.LoadingFrameComplete +=wv_LoadingFrameComplete;
wv.Source = new Uri(link);
}
void wv_LoadingFrameComplete(object sender, FrameEventArgs e)
{
if(e.IsMainFrame)
{
//BeginParsing
((WebView)sender).LoadingFrameComplete -= wv_LoadingFrameComplete;
}
}
}
class Teste
{
WebView MainWeb = WebCore.CreateWebView(1024,768);
public object[] ObtainInformation(int id)
{
ParsingHelper ph = new ParsingHelper(MainWeb);
ph.ParsingInitiation("http://www.google.com");
//More code
return new object[] {};
}
}
}
If I run something like...
Teste t = new Teste();
t.ObtainInformation(1);
wv_LoadingFrameComplete is never triggered and I have no idea why.
try this code to detect page loaded completely
loadingFrameCompete event + IsLoading property
private void Awesomium_Windows_Forms_WebControl_LoadingFrameComplete(object sender, Awesomium.Core.FrameEventArgs e)
{
if (!webControl1.IsLoading)
MessageBox.Show("Page Loaded Completely");
}
Answered here: http://answers.awesomium.com/questions/2260/awesomium-not-loading-page-or-triggering-any-event.html
You are either using Awesomium in non UI environment (not WPF/WinForms control) and must call WebCore.Update() implicitly or you just blocking the same thread so it can't fire events.

Categories

Resources