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
Related
I'm working on a game engine. Now assume we have a Game class:
using System;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using System.Drawing;
using static OpenTK.Graphics.OpenGL.GL;
namespace Test2
{
class Game : GameWindow
{
public Game(int width, int height)
:base(width, height)
{
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
}
}
}
And we have a medium in which to call a method for every frame.
Now assume the person creating a game, creates a file called ball_behaviour script:
using System;
using System.Drawing;
namespace Test2.Project.Behaviour
{
class Ball
{
public void Update()
{
}
public void init()
{
A2D.Background = Color.CornflowerBlue;
}
public void preInit()
{
}
}
}
And assume there are other files like it, which have the methods Update, and init. How can I call these methods from protected override void OnRenderFrame for example, in every class that's registered under Test2.Project.Behaiviour namespace? Thank you so much in advanced.
EDIT:The GameWindow class comes from OpenTK, which is the API I'm using to interface c# with OpenGL.
EDIT #2: Kind of like the way Unity does it.
you could use a virtual void that you than override which would be easier and faster to implement,have done this thing yesterday
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());
}
}
}
I am looking to get platform specific location details using Xamarin's dependency injection but running into issues. More than likely from doing it wrong.
Here is my current setup:
nuMaps/Interfaces/ILocationService.cs
using Xamarin.Forms.Maps;
namespace nuMaps
{
public interface ILocationService
{
void initLocationService();
Position getCurrentPosition();
}
}
nuMaps/nuMaps.Droid/Interfaces/LocationService.cs
using System;
using nuMaps;
using nuMaps.Droid;
using Xamarin.Forms.Maps;
using Android.App;
using Android.Gms.Common;
using Android.Gms.Common.Apis;
using Android.Gms.Location;
using Android.Locations;
using Android.Widget;
[assembly: Xamarin.Forms.Dependency (typeof (LocationService))]
namespace nuMaps.Droid
{
public class LocationService : Java.Lang.Object, ILocationService, IGoogleApiClientConnectionCallbacks, IGoogleApiClientOnConnectionFailedListener, Android.Gms.Location.ILocationListener
{
readonly IGoogleApiClient _googleApiClient;
readonly LocationRequest _locRequest;
Position _currentPosition;
Location _currentLocation;
public LocationService()
{
Console.WriteLine ("LocationService constructor");
_currentPosition = new Position (0, 0);
_googleApiClient = new GoogleApiClientBuilder (Xamarin.Forms.Forms.Context)
.AddApi (LocationServices.Api)
.AddConnectionCallbacks (this)
.AddOnConnectionFailedListener (this)
.Build ();
_locRequest = new LocationRequest ();
}
#region ILocationService implementation
public void initLocationService()
{
_googleApiClient.Connect ();
}
public Position getCurrentPosition ()
{
if (_googleApiClient.IsConnected) {
_currentLocation = LocationServices.FusedLocationApi.GetLastLocation (_googleApiClient);
_currentPosition = new Position (_currentLocation.Latitude, _currentLocation.Longitude);
}
_googleApiClient.Disconnect ();
return new Position (_currentLocation.Latitude, _currentLocation.Longitude);
}
#endregion
public void OnLocationChanged(Location l)
{
Console.WriteLine ("OnLocationChanged");
_currentLocation = l;
}
public void OnConnectionFailed (ConnectionResult result)
{
Console.WriteLine ("ConnectionFailed");
}
#region IGoogleApiClientConnectionCallbacks implementation
public void OnConnected (Android.OS.Bundle connectionHint)
{
Console.WriteLine ("OnConnected");
if (!_googleApiClient.IsConnected)
LocationServices.FusedLocationApi.RequestLocationUpdates (_googleApiClient, _locRequest, this);
_currentLocation = LocationServices.FusedLocationApi.GetLastLocation (_googleApiClient);
}
public void OnConnectionSuspended (int cause)
{
Console.WriteLine ("OnConnectionSuspended");
}
#endregion
}
}
Usage in nuMaps/Views/MapPage.xaml.cs
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using System;
using System.Diagnostics;
namespace nuMaps
{
public partial class MapPage : ContentPage
{
public MapPage ()
{
InitializeComponent ();
Position p = DependencyService.Get<ILocationService>().getCurrentPosition();
MyMap.MoveToRegion (
MapSpan.FromCenterAndRadius (
p, Distance.FromMiles (1)
)
);
}
}
}
nuMaps/Views/Loginpage.xaml.cs
using System;
using Xamarin.Forms;
using nuMaps;
namespace nuMaps
{
public partial class LoginPage : ContentPage
{
public LoginPage ()
{
InitializeComponent ();
/*
* Init platform specific location service.
* TODO: This *shouldn't* need to happen, but we don't get location information until
* the OnConnected callback happens which is too slow to put in getCurrentLocation method.
*/
DependencyService.Get<ILocationService>().initLocationService();
}
}
}
I believe the problem is in your implementation of ILocationService.
I would remove implementing activity (why do you want to use OnCreate?) and take a look at http://developer.xamarin.com/guides/android/platform_features/maps_and_location/location/.
I'd recommend on Android getting the GPS location via the google play apis, which will require implementing ILocationListener, IGoogleApiClientConnectionCallbacks, and IGoogleApiClientOnConnectionFailedListener. Hope that helps!
Edit for comments:
If the LocationService in the question is up to date, I don't see that you're implementing IGoogleApiClientConnectionCallbacks or ILocationListener. It may be that because the mappage is using gps, GetLastKnownLocation works, because a location has recently been obtained.
GPS location requesting is an async operation - one of the methods with IGoogleApiClientConnectionCallbacks is OnConnected, where you should call something like:
LocationServices.FusedLocationApi.RequestLocationUpdates(googleApiClient, locationRequest, this);
This will actively request location updates, and then fire OnLocationChanged(Location location) when an location update is returned. This is all async, so you'll likely need to expose these events in your LocationService and subscribe to them.
You could give TinyIoC a try instead of the Xamarin Dependency Service. It works well for platform-specific implementations, like location services.
Maybe the Xamarin Dependency Service can be used for these types of things, but I've only used it for custom Renderers so far. For more service-based stuff, I use TinyIoC.
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.
I'm creating a custom C# control (form's title bar). One form can have only one title bar, and that's why i'm wondering something: When user (programmer) adds my title bar to his form, is there ANY way i can check if ParentForm already contains my title bar, and if so can i cancel adding another instance of my control?
I know how to perform check to see types of controls ParentForm contains, but what event is raised when my control is dropped from toolbox to form, and how to "cancel" layout of my control if necessary?
You should read in-depth about the designer technologies available in .NET, because these are not what I would call production-ready examples. However, this gives you a solid start and both code snippets do what you are asking.
For design time, you can override the designer site in your control and do the following:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel.Design;
using System.Diagnostics;
namespace WindowsFormsControlLibrary1 {
public partial class DebugControl : UserControl {
public DebugControl() {
InitializeComponent();
}
public override ISite Site
{
get
{
return base.Site;
}
set
{
base.Site = value;
IComponentChangeService service = (IComponentChangeService)GetService(typeof(IComponentChangeService));
service.ComponentAdding += (sender, e) => {
IDesignerHost host = (IDesignerHost)value.Container;
Component component = (Component)host.RootComponent;
if (component as Form != null)
{
Form form = (Form)component;
foreach (Control c in form.Controls)
{
if (c.GetType() == this.GetType())
{
throw new System.ArgumentException("You cannot add two of these controls to a form!");
}
}
}
};
}
}
}
}
For runtime in your form you can override OnControlAdded and do the following:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using WindowsFormsControlLibrary1;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
protected override void OnShown(EventArgs e)
{
Controls.Add(new DebugControl());
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (e.Control.GetType() == typeof(DebugControl))
{
int count = 0;
foreach (Control c in Controls)
{
if (c is DebugControl)
{
count++;
}
}
if (count > 1)
{
Controls.Remove(e.Control);
Debug.Assert(false, "Cannot add two of these controls!");
}
}
}
}
}
There are more than one way to do this, but these are rough examples, mind you. Read up on design-time support that the .NET framework, it is very rich and there are extensive documentation on it. Another method is to implement custom designers and implement CanBeParentedTo and CanParent, but mind you CanBeParentedTo is not called when your control is drug from the ToolBox to your form.
You may use Controls collection of the active form.
Handle the ParentChanged event, check the parent's Controls, and throw an exception if necessary.