Create a simple GUI from scratch - algorithm

On a platform that has no real libraries available, and bare-minimum graphics other than a "display object of dimension(x,y,xx,yy) at coordinates (x,y), I'm trying to create a simple gui.
Can someone point me to a reference where I can understand the logistical principles involved in displaying a set of objects on the screen, and highlighting the selected object, allowing users to navigate between objects and move highlighting to each object. It seems like it should be simple to do, but I would like to understand how people think about this.
How would one create an object with a method like obj.highlight() where obj.highlight would turn off highlighting in all other objects? Would one simply do a for next loop through an array of objects, skipping the current object, turning off highlighting and then set the current object to true? Highlighting would be accomplished by drawing another object on top of the selected object with a transparent center.
This is a single threaded system, (but allows a small amount of async processing).
I'm looking more for conceptual ideas but code in VB that doesn't make use of proprietary graphics calls might be useful.

I've coded a small sample app that does its own control framework by painting over a form using .Net C#. Just something simple with this result:
I've done the IsSelected by recursively disabling all controls and toggling the clicked one.
See the part with window.MouseUp += (sender, arg) =>.
Selection can go through mouse or the Tab key.
The code approach should be portable to other languages, and online-translatable to VB.Net.
Relevant snippet of code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
namespace CustomGUI
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form window = new Form1();
window.BackColor = Color.Gray;
Graphics api = window.CreateGraphics();
GUIControl form = new GUIControl();
form.Location = new Point(30,30);
form.Size = new Size(200, 300);
GUIControl control1 = new GUIControl();
control1.Location = new Point(0, 0);
control1.Size = new Size(200, 130);
control1.Background = Color.Blue;
GUIControl control11 = new GUIControl();
control11.Location = new Point(140, 30);
control11.Size = new Size(30, 30);
control11.Background = Color.Red;
GUIControl control12 = new GUIControl();
control12.Location = new Point(30, 30);
control12.Size = new Size(30, 30);
control12.Background = Color.Red;
control12.BorderColor = Color.Green;
control12.BorderWidth = 5;
GuiLabel control2 = new GuiLabel();
control2.Location = new Point(10, 200);
control2.Size = new Size(180, 30);
control2.Background = Color.Green;
control2.Text = "Hello World!";
control1.AddChild(control11);
control1.AddChild(control12);
form.AddChild(control1);
form.AddChild(control2);
window.MouseUp += (sender, arg) =>
{
// hit test the control where the mouse has landed
IGUIContainer control = form.HitTest(arg.Location);
if (control != null)
{
// recursive on all controls
foreach (var ct in (new IGUIContainer[] { form }).Traverse(c => c.Controls))
{
//deselecting all others
if (ct != control) ct.IsSelected = false;
}
control.IsSelected = !control.IsSelected;
}
window.Invalidate(); // force paint
};
window.KeyUp += (sender, key) =>
{
if (key.KeyCode == Keys.Tab && key.Modifiers == Keys.None)
{
var selected = (new IGUIContainer[] { form }).Traverse(c => c.Controls).FirstOrDefault(c => c.IsSelected);
IGUIContainer parent;
if (selected == null)
{
parent = form;
}
else
{
parent = selected;
}
IGUIContainer control;
if (parent.Controls.Count > 0)
{
control = parent.Controls[0];
}
else
{
control = GUIControl.Next(parent);
}
if (control == null) control = form;
foreach (var ct in (new IGUIContainer[] { form }).Traverse(c => c.Controls))
{
if (ct != control) ct.IsSelected = false;
}
control.IsSelected = true;
window.Invalidate();
}
};
window.Paint += (sender, args) =>
{
form.Draw(api, new Point(0,0));
};
Application.Run(window);
}
}
}
All the needed classes and interfaces:
IDrawable:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace CustomGUI
{
public interface IDrawable
{
Point Location { get; set; }
Size Size { get; set; }
Rectangle GetRealRect(Point origin);
void Draw(Graphics gfxApi, Point origin);
}
}
IGUIContainer:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace CustomGUI
{
delegate void SelectionChangedHandler(object sender, bool newIsSelected);
interface IGUIContainer : IUIElement
{
IGUIContainer Parent { get; set; }
List<IGUIContainer> Controls { get; }
void AddChild(IGUIContainer child);
bool IsSelected { get; set; }
event SelectionChangedHandler SelectionChanged;
IGUIContainer HitTest(Point mouseCoord);
}
}
UIElement:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Diagnostics;
namespace CustomGUI
{
abstract class UIElement : IUIElement
{
private Point _location;
private Size _size;
private Color _background;
private Color _foreground;
private Color _borderColor;
private int _borderWidth;
public UIElement()
{
_foreground = Color.Black;
_background = Color.White;
_borderColor = Color.Transparent;
}
public Point Location
{
get
{
return _location;
}
set
{
_location = value;
}
}
public Size Size
{
get
{
return _size;
}
set
{
_size = value;
}
}
public virtual void Draw(Graphics drawingApi, Point origin)
{
Rectangle inside = GetRealRect(origin);
Pen borderPen = new Pen(new SolidBrush(_borderColor), _borderWidth);
drawingApi.FillRectangle(new SolidBrush(_background), inside);
drawingApi.DrawRectangle(borderPen, inside);
}
public Rectangle ClientRect
{
get
{
return new Rectangle(_location, _size);
}
}
public Color Background
{
get
{
return _background;
}
set
{
_background = value;
}
}
public Color Foreground
{
get
{
return _foreground;
}
set
{
_foreground = value;
}
}
public Rectangle GetRealRect(Point origin)
{
int left = ClientRect.Left + origin.X;
int top = ClientRect.Top + origin.Y;
int width = ClientRect.Width;
int height = ClientRect.Height;
Debug.WriteLine("GetRealRect " + left + ", " + top + ", " + width + ", " + height);
return new Rectangle(left, top, width, height);
}
public int BorderWidth
{
get
{
return _borderWidth;
}
set
{
_borderWidth = value;
}
}
public Color BorderColor
{
get
{
return _borderColor;
}
set
{
_borderColor = value;
}
}
}
}
GUIControl:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace CustomGUI
{
class GUIControl : UIElement, IGUIContainer
{
private IGUIContainer _parent;
private List<IGUIContainer> _controls = new List<IGUIContainer>();
private bool _isSelected;
public List<IGUIContainer> Controls
{
get
{
return _controls;
}
}
public override void Draw(Graphics api, Point origin)
{
Point original = origin;
base.Draw(api, origin);
origin.Offset(this.Location);
foreach (var ctrl in Controls)
{
ctrl.Draw(api, origin);
}
if (IsSelected)
{
Pen selection = new Pen(Color.Yellow, 3);
selection.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
api.DrawRectangle(selection, GetRealRect(original));
}
}
public IGUIContainer HitTest(Point coord)
{
Point newOrigin = coord;
newOrigin.Offset(-this.Location.X, -this.Location.Y);
foreach (var ctrl in Controls)
{
IGUIContainer hit = ctrl.HitTest(newOrigin);
if (hit != null)
{
return hit;
}
}
return ClientRect.Contains(coord) ? this : null;
}
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
if (SelectionChanged != null)
{
SelectionChanged(this, _isSelected);
}
}
}
public event SelectionChangedHandler SelectionChanged;
public void AddChild(IGUIContainer child)
{
// if you need to implement event propagation this is the place to attach them to children
child.Parent = this;
Controls.Add(child);
}
public IGUIContainer Parent
{
get
{
return _parent;
}
set
{
_parent = value;
}
}
public static IGUIContainer Next(IGUIContainer self)
{
if (self.Parent != null &&
self.Parent.Controls.Count - 1 > self.Parent.Controls.IndexOf(self))
{
return self.Parent.Controls[self.Parent.Controls.IndexOf(self) + 1];
}
else if (self.Parent != null)
{
return Next(self.Parent);
}
else
{
return null;
}
}
}
}
GUILabel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace CustomGUI
{
class GuiLabel : GUIControl
{
public string Text { get; set; }
public Font Font { get; set; }
public GuiLabel()
{
Font = new Font(new FontFamily("Tahoma"), 12, FontStyle.Regular);
}
public override void Draw(System.Drawing.Graphics api, System.Drawing.Point origin)
{
base.Draw(api, origin);
Rectangle controlRect = GetRealRect(origin);
SizeF size = api.MeasureString(Text, Font);
Point textPosition = new Point(controlRect.Location.X + (int)(controlRect.Width - size.Width) / 2,
controlRect.Location.Y + (int)(controlRect.Height - size.Height) / 2);
api.DrawString(Text, Font, new SolidBrush(Foreground), textPosition);
}
}
}
Extension (for Traverse method to flatten recursion):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CustomGUI
{
static class Extensions
{
public static IEnumerable<T> Traverse<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> fnRecurse)
{
foreach (T item in source)
{
yield return item;
IEnumerable<T> seqRecurse = fnRecurse(item);
if (seqRecurse != null)
{
foreach (T itemRecurse in Traverse(seqRecurse, fnRecurse))
{
yield return itemRecurse;
}
}
}
}
}
}

Well that is one question that can be answered in a million ways... :)
But as long as you can draw pixels (or anything remotely like it), you can draw a GUI. If you have an object oriented language at hand, I would not choose to highlight and unhighlight the current object. I would give if focus and remove focus from it, and let the object itself decide whether it should be redrawn and how that should be done.
You can automatically unfocus the previous object if all object are placed in some sort of container. When you press a navigational key (like Tab) or press a mouse button, that container can process that message and focus the next object and unfocus the last object.
It requires some programming, but the concept is quite easy. It becomes harder when you want it to perform well, look slick, have all kinds of anumations and transitions... But as I said, the concept is simple and you won't even need OO to do it, although it will probably give you a much cleaner result. I think I can program an ASCII based GUI in DOS Batch on a rainy afternoon if I needed to.

Related

WebCam texture doesn't work on Build Windows - Unity 2022.1.17f1

WebCamTexture works fine on editor, but doesn't work on Build. I have tried: turn off antivirus and firewall, check authorization, put the webcamtexture on UI_RawImage and a plane gameObject, nothing works on build. There is no any real conclusion about the same bug on the forums (many are about Vuforia, and I just want to take a photo by a simple webcam on Windows). Any clues? Below the code. Tks in advance.
using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.UI;
using System.Collections.Generic;
// https://stackoverflow.com/questions/24496438/can-i-take-a-photo-in-unity-using-the-devices-camera
public class GetCam2 : MonoBehaviour
{
WebCamTexture webCam;
//string your_path = "D:\\Lixo\\FotosCam";// any path you want to save your image
[SerializeField] string your_path = "";
public RawImage display;
public AspectRatioFitter fit;
public int contadorFotos;
[SerializeField] GameObject planoWeb;
IEnumerator Start()
{
your_path = "" + Application.dataPath;
yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);
if (Application.HasUserAuthorization(UserAuthorization.WebCam)) {
LigaCamComeco1();
}
}
void LigaCamComeco1() {
if (WebCamTexture.devices.Length == 0)
{
Debug.LogError("can not found any camera!");
return;
}
int index = -1;
for (int i = 0; i < WebCamTexture.devices.Length; i++)
{
if (WebCamTexture.devices[i].name.ToLower().Contains("pc"))
{
Debug.Log("WebCam Name:" + WebCamTexture.devices[i].name + " Webcam Index:" + i);
index = i;
}
}
if (index == -1)
{
Debug.LogError("can not found your camera name!");
return;
}
WebCamDevice device = WebCamTexture.devices[index];
webCam = new WebCamTexture(device.name);
webCam.Play();
display.texture = webCam;
planoWeb.GetComponent<Renderer>().materials[0].mainTexture = webCam;
}
public void Update()
{
if (Input.GetKeyDown(KeyCode.PageUp)) {
Ratio();
callTakePhoto();
}
}
void Ratio() {
float ratio = (float)webCam.width / (float)webCam.height;
fit.aspectRatio = ratio;
float ScaleY = webCam.videoVerticallyMirrored ? -1f : 1f;
display.rectTransform.localScale = new Vector3(1f, ScaleY, 1f);
int orient = -webCam.videoRotationAngle;
display.rectTransform.localEulerAngles = new Vector3(0, 0, orient);
}
public void callTakePhoto() // call this function in button click event
{
StartCoroutine(TakePhoto());
}
IEnumerator TakePhoto() // Start this Coroutine on some button click
{
yield return new WaitForEndOfFrame();
Texture2D photo = new Texture2D(webCam.width, webCam.height);
photo.SetPixels(webCam.GetPixels());
photo.Apply();
byte[] bytes = photo.EncodeToJPG();
File.WriteAllBytes(your_path + "\\ZaxisCam" + contadorFotos + ".jpg", bytes);
contadorFotos++;
}
}
I have tried: turn off antivirus and firewall, check authorization, put the webcamtexture on UI_RawImage and a plane gameObject, nothing works on build.

How to Set auto sliders in pageviewers in xamarin.android

I am trying to put auto sliders in my code for page viewers in xamarin.android and tried may ways to do that but it is not working correctly can you please help me to set up the auto slider to the page viewer i am posting my last tried code which is auto sliding one page and jumping off right away to last page.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Support.V4.View;
using Android.Support.V4.App;
using MyApplication.Droid.Library;
using System.Timers;
namespace MyApplication.Droid.Circles
{
[Activity(Label = "SampleCirclesSnap")]
public class SampleCirclesSnap : FragmentActivity
{
public TestFragmentAdapter mAdapter;
public ViewPager mPager;
public PageIndicator mIndicator;
public Timer time;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.simple_circles);
mAdapter = new TestFragmentAdapter(SupportFragmentManager);
mPager = FindViewById<ViewPager>(Resource.Id.pager);
mPager.Adapter = mAdapter;
var indicator = FindViewById<CirclePageIndicator>(Resource.Id.indicator);
mIndicator = indicator;
indicator.SetViewPager(mPager);
indicator.SetSnap(true);
time = new System.Timers.Timer();
time.Elapsed += (sender, args) => viewPager.SetCurrentItem(CurrentItem++, true);
time.Interval = 1000;
time.Enabled = true;
}
}
}
My fragments related code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Support.V4.App;
using Fragment = Android.Support.V4.App.Fragment;
using FragmentManager = Android.Support.V4.App.FragmentManager;
namespace MyApplication.Droid
{
public class TestFragmentAdapter : FragmentPagerAdapter
{
// public static string[] CONTENT = new string[] { "This", "Is", "A", "Test", };
public static int[] CONTENT = new int[] { Resource.Drawable.Visa, Resource.Drawable.home_s, Resource.Drawable.Set_s, Resource.Drawable.Icon, Resource.Drawable.home_s };
int mCount;
public TestFragmentAdapter(FragmentManager fm) : base(fm)
{
mCount = CONTENT.Count();
}
public override Fragment GetItem(int position)
{
return new TestFragment(CONTENT[position % CONTENT.Count()]);
}
public override int Count
{
get
{
return mCount;
}
}
public void SetCount(int count)
{
Console.WriteLine("Setting count to " + count);
if (count > 0 && count <= 10)
{
mCount = count;
NotifyDataSetChanged();
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Fragment = Android.Support.V4.App.Fragment;
namespace MyApplication.Droid
{
class TestFragment : Fragment
{
private const string KEY_CONTENT = "TestFragment:Content";
string mContent = "???";
private int v;
public TestFragment()
{
}
public TestFragment(int v)
{
this.v = v;
}
//public TestFragment(string content)
//{
// var builder = new StringBuilder();
// for (int i = 0; i < 20; i++)
// {
// if (i != 19)
// builder.Append(content).Append(" ");
// else
// builder.Append(content);
// }
// mContent = builder.ToString();
//}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//if ((savedInstanceState != null) && savedInstanceState.ContainsKey(KEY_CONTENT))
//{
// mContent = savedInstanceState.GetString(KEY_CONTENT);
//}
ImageView image = new ImageView(Activity);
image.SetImageResource(v);
//TextView text = new TextView(Activity);
//text.Gravity = GravityFlags.Center;
//text.Text = mContent;
//text.TextSize = (20 * Resources.DisplayMetrics.Density);
//text.SetPadding(20, 20, 20, 20);
LinearLayout layout = new LinearLayout(Activity);
layout.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.FillParent);
layout.SetGravity(GravityFlags.Center);
layout.AddView(image);
return layout;
}
public override void OnSaveInstanceState(Bundle outState)
{
base.OnSaveInstanceState(outState);
outState.PutString(KEY_CONTENT, mContent);
}
}
}
anyone Please help me with the code to auto slide pageviewers on set time in xamarin android
I think you will need to invoke your method of changing current item to the RunOnUiThread method to ensure this it will be executed in UI thread, for example:
var timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.Enabled = true;
int page = 0;
timer.Elapsed += (sender, args) =>
{
RunOnUiThread(() =>
{
if (page <= viewPager.Adapter.Count)
{
page++;
}
else
{
page = 0;
}
viewPager.SetCurrentItem(page, true);
Log.WriteLine(LogPriority.Debug, "CurrentItem:", viewPager.CurrentItem.ToString());
});
};
Tested on Android 6.0 emulator:

Loading Image while service gets data using Xamarin Forms

In my application in login page i want loading image with message "Loading" in xamarin forms before the service gets data.
For that i used Activity indicator like this:
ActivityIndicator indicator = new ActivityIndicator { Color = Color.Blue, };
indicator.IsRunning = false;
indicator.IsVisible = false;
indicator.HorizontalOptions = LayoutOptions.CenterAndExpand;
indicator.VerticalOptions = LayoutOptions.CenterAndExpand;
and after click on Login button,I added code like this
indicator.IsRunning = true;
indicator.IsVisible = true;
now i am getting loading image only and in the same screen.But i want like the above image.can anyone suggest me to solve this.
Thanks in advance
You can use following library for the Dialogs
It supports Xamarin.Forms aswell
https://www.nuget.org/packages/Acr.UserDialogs/
EDIT 1
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
**using Acr.UserDialogs;**
using Xamarin.Forms;
using Splat;
namespace ProjectScreen
{
public partial class MainPage : ContentPage
{
public ObservableCollection<MainPageViewModel> projects { get; set; }
public SQLite.Net.SQLiteConnection _connection;
public bool IsLoading;
public MainPage()
{
projects = new ObservableCollection<MainPageViewModel>();
InitializeComponent();
this.getConnection();
foreach (Project Pname in this.GetProjects())
{
projects.Add(new MainPageViewModel { Name = Pname.name });
lstView.ItemsSource = projects;
}
AddNewItem.Clicked += (o, s) =>
{
this.PromptCommand();
};
lstView.ItemSelected += LstView_ItemSelected;
}
private void LstView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
**UserDialogs.Instance.ShowLoading("Loading data");**
Task.Run(() => { waitForMinute(); });
}
private async void waitForMinute()
{
await Task.Delay(10000);
**UserDialogs.Instance.Loading().Hide();**
Device.BeginInvokeOnMainThread(() =>
{
Navigation.PushAsync(new NavigationPage(new ProjectDetails()));
});
}
}
}
}
Lines indicated with ** are for AcrDialog You can use them in any file as per your requirement
make Sure you run them on main thread

Xamarin Forms Maps - how to refresh/update the map - CustomMap Renderer

If you are searching for a full polylines, pins, tiles, UIOptions (and 3D effects soon) renderings/implementations, you should take a loot at the public github I made at XamarinByEmixam23/..../Map.
I search a lot but I still have the same problem:
How can I update, refresh or reload the Xamarin.Forms.Maps?
In the class definition (class CustomMap : Map), there is no method to update the maps.. Maybe a MVVM logic can solves the problem, but I can't find it on the Web..
I followed this tutorial for the maps : Working with maps
To customise it, I followed this tutorial : Highlight a Route on a Map
So, after these tutorials (I made the same things, no changes), I tried with 2 RouteCoordinates which gave me a straight line... I then made an algorithm which works perfectly.
DirectionMap
public class DirectionMap
{
public Distance distance { get; set; }
public Duration duration { get; set; }
public Address address_start { get; set; }
public Address address_end { get; set; }
public List<Step> steps { get; set; }
public class Distance
{
public string text { get; set; }
public int value { get; set; }
}
public class Duration
{
public string text { get; set; }
public int value { get; set; }
}
public class Address
{
public string text { get; set; }
public Position position { get; set; }
}
public class Step
{
public Position start { get; set; }
public Position end { get; set; }
}
}
ResponseHttpParser
public static void parseDirectionGoogleMapsResponse(HttpStatusCode httpStatusCode, JObject json, Action<DirectionMap, string> callback)
{
switch (httpStatusCode)
{
case HttpStatusCode.OK:
DirectionMap directionMap = null;
string strException = null;
try
{
directionMap = new DirectionMap()
{
distance = new DirectionMap.Distance()
{
text = (json["routes"][0]["legs"][0]["distance"]["text"]).ToString(),
value = Int32.Parse((json["routes"][0]["legs"][0]["distance"]["value"]).ToString())
},
duration = new DirectionMap.Duration()
{
text = (json["routes"][0]["legs"][0]["duration"]["text"]).ToString(),
value = Int32.Parse((json["routes"][0]["legs"][0]["duration"]["value"]).ToString())
},
address_start = new DirectionMap.Address()
{
text = (json["routes"][0]["legs"][0]["start_address"]).ToString(),
position = new Position(Double.Parse((json["routes"][0]["legs"][0]["start_location"]["lat"]).ToString()), Double.Parse((json["routes"][0]["legs"][0]["start_location"]["lng"]).ToString()))
},
address_end = new DirectionMap.Address()
{
text = (json["routes"][0]["legs"][0]["end_address"]).ToString(),
position = new Position(Double.Parse((json["routes"][0]["legs"][0]["end_location"]["lat"]).ToString()), Double.Parse((json["routes"][0]["legs"][0]["end_location"]["lng"]).ToString()))
}
};
bool finished = false;
directionMap.steps = new List<Step>();
int index = 0;
while (!finished)
{
try
{
Step step = new Step()
{
start = new Position(Double.Parse((json["routes"][0]["legs"][0]["steps"][index]["start_location"]["lat"]).ToString()), Double.Parse((json["routes"][0]["legs"][0]["steps"][index]["start_location"]["lng"]).ToString())),
end = new Position(Double.Parse((json["routes"][0]["legs"][0]["steps"][index]["end_location"]["lat"]).ToString()), Double.Parse((json["routes"][0]["legs"][0]["steps"][index]["end_location"]["lng"]).ToString()))
};
directionMap.steps.Add(step);
index++;
}
catch (Exception e)
{
finished = true;
}
}
}
catch (Exception e)
{
directionMap = null;
strException = e.ToString();
}
finally
{
callback(directionMap, strException);
}
break;
default:
switch (httpStatusCode)
{
}
callback(null, json.ToString());
break;
}
}
I just get the distance and duration for some private calculs and get each step that I put into a List<>;
When everything is finished, I use my callback which bring us back to the controller (MapPage.xaml.cs the XAML Form Page (Xamarin Portable))
Now, everything becomes weird. It's like the map doesn't get that changes are made
public partial class MapPage : ContentPage
{
public MapPage()
{
InitializeComponent();
setupMap();
setupMapCustom();
}
public void setupMapCustom()
{
customMap.RouteCoordinates.Add(new Position(37.785559, -122.396728));
customMap.RouteCoordinates.Add(new Position(37.780624, -122.390541));
customMap.RouteCoordinates.Add(new Position(37.777113, -122.394983));
customMap.RouteCoordinates.Add(new Position(37.776831, -122.394627));
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(37.79752, -122.40183), Xamarin.Forms.Maps.Distance.FromMiles(1.0)));
}
public async void setupMap()
{
customMap.MapType = MapType.Satellite;
string origin = "72100 Le Mans";
string destination = "75000 Paris";
HttpRequest.getDirections(origin, destination, callbackDirections);
customMap.RouteCoordinates.Add(await MapUtilities.GetMapPointOfStreetAddress(origin));
Position position = await MapUtilities.GetMapPointOfStreetAddress(destination);
//customMap.RouteCoordinates.Add(position);
var pin = new Pin
{
Type = PinType.Place,
Position = position,
Label = "Destination !!",
};
customMap.Pins.Add(pin);
}
private async void callbackDirections(Object obj, string str)
{
if (obj != null)
{
DirectionMap directionMap = obj as DirectionMap;
foreach (Step step in directionMap.steps)
{
customMap.RouteCoordinates.Add(step.start);
System.Diagnostics.Debug.WriteLine("add step");
}
customMap.RouteCoordinates.Add(directionMap.address_end.position);
System.Diagnostics.Debug.WriteLine("add last step");
}
else
{
System.Diagnostics.Debug.WriteLine(str);
}
}
}
I run my app, everything works until it's something fast, because of the time spent by my algorithm etc, the callback is coming too late and then I need to refresh, reload or update my map... Anyway, I need to update my map in the future, so... If anyone can help, this one is welcome !
EDIT 1
I took a look at your answer ( thank a lot ! ;) ) but it doesn't works :/
I updated CustomMap as you did
public class CustomMap : Map
{
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create<CustomMap, List<Position>>(p => p.RouteCoordinates, new List<Position>());
public List<Position> RouteCoordinates
{
get { return (List<Position>)GetValue(RouteCoordinatesProperty); }
set { SetValue(RouteCoordinatesProperty, value); }
}
public CustomMap()
{
RouteCoordinates = new List<Position>();
}
}
Same for CustomMapRenderer (Droid)
public class CustomMapRenderer : MapRenderer, IOnMapReadyCallback
{
GoogleMap map;
Polyline polyline;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
((MapView)Control).GetMapAsync(this);
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Element == null || this.Control == null)
return;
if (e.PropertyName == CustomMap.RouteCoordinatesProperty.PropertyName)
{
UpdatePolyLine();
}
}
private void UpdatePolyLine()
{
if (polyline != null)
{
polyline.Remove();
polyline.Dispose();
}
var polylineOptions = new PolylineOptions();
polylineOptions.InvokeColor(0x66FF0000);
foreach (var position in ((CustomMap)this.Element).RouteCoordinates)
{
polylineOptions.Add(new LatLng(position.Latitude, position.Longitude));
}
polyline = map.AddPolyline(polylineOptions);
}
public void OnMapReady(GoogleMap googleMap)
{
map = googleMap;
UpdatePolyLine();
}
}
So, for the last change, in my MapPage.xaml.cs I made changes in the callbackDirections as you explained (I hope I did good)
private async void callbackDirections(Object obj, string str)
{
if (obj != null)
{
Device.BeginInvokeOnMainThread(() =>
{
DirectionMap directionMap = obj as DirectionMap;
var list = new List<Position>(customMap.RouteCoordinates);
foreach (Step step in directionMap.steps)
{
list.Add(directionMap.address_end.position);
System.Diagnostics.Debug.WriteLine("add step");
}
System.Diagnostics.Debug.WriteLine("last step");
customMap.RouteCoordinates = list;
System.Diagnostics.Debug.WriteLine("finished?");
});
}
else
{
System.Diagnostics.Debug.WriteLine(str);
}
}
The map is still doesn't display the polyline :/ I only made these changes, I didn't change anything else from my previous code.
I didn't tell you, but I'm not an expert in MVVM binding, so if I forget something, I'm sorry :/
EDIT 2
So after your answer and some read, read and re-read of your answer, there is my "test code" in MapPage.xaml.cs
public MapPage()
{
InitializeComponent();
//HttpRequest.getDirections(origin, destination, callbackDirections);
Device.BeginInvokeOnMainThread(() =>
{
customMap.RouteCoordinates = new List<Position>
{
new Position (37.797534, -122.401827),
new Position (37.776831, -122.394627)
};
});
//setupMap();
//setupMapCustom();
}
Because it doesn't works (for me), I took a look at my code and then, I saw that public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create<CustomMap, List<Position>>(
p => p.RouteCoordinates, new List<Position>()); was deprecated..
So I red on this post a different way to implement this binding, but it also said that this way is deprecated SEE HERE... I also saw some tutorials about binding which says that they put some code into their xaml, let me remember you mine
<?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:NAMESPACE;assembly=NAMESPACE"
x:Class="NAMESPACE.Controlers.MapPage">
<ContentPage.Content>
<local:CustomMap x:Name="customMap"/>
</ContentPage.Content>
</ContentPage>
I'm not using something as ItemSource="{PolylineBindable}"
The custom renderer from the example is not made for dynamic updating the path. It is just implemented for the case, where all points of the paths are known before initializing the map / drawing the path the first time. So you have this race condition, you ran into, because you are loading the directions from a web service.
So you have to do some changes:
RouteCoordinates must be a BindableProperty
public class CustomMap : Map
{
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create<CustomMap, List<Position>>(p => p.RouteCoordinates, new List<Position>());
public List<Position> RouteCoordinates
{
get { return (List<Position>)GetValue(RouteCoordinatesProperty); }
set { SetValue(RouteCoordinatesProperty, value); }
}
public CustomMap ()
{
RouteCoordinates = new List<Position>();
}
}
Update the Polyline whenever the coordinates change
Move the creation of the polyline from OnMapReady to UpdatePolyLine
call UpdatePolyLine from OnMapReady and OnElementPropertyChanged
public class CustomMapRenderer : MapRenderer, IOnMapReadyCallback
{
GoogleMap map;
Polyline polyline;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
((MapView)Control).GetMapAsync(this);
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Element == null || this.Control == null)
return;
if (e.PropertyName == CustomMap.RouteCoordinatesProperty.PropertyName)
{
UpdatePolyLine();
}
}
private void UpdatePolyLine()
{
if (polyline != null)
{
polyline.Remove();
polyline.Dispose();
}
var polylineOptions = new PolylineOptions();
polylineOptions.InvokeColor(0x66FF0000);
foreach (var position in ((CustomMap)this.Element).RouteCoordinates)
{
polylineOptions.Add(new LatLng(position.Latitude, position.Longitude));
}
polyline = map.AddPolyline(polylineOptions);
}
public void OnMapReady(GoogleMap googleMap)
{
map = googleMap;
UpdatePolyLine();
}
}
Setting the data
Updating the positions changes a bit. Instead of adding the positions to the existing list, you have to (create a new list) and set it to RouteCoordinates. You can use Device.BeginInvokeOnMainThread to ensure, that the operation is performed on the UI thread. Else the polyline will not update.
Device.BeginInvokeOnMainThread(() =>
{
customMap.RouteCoordinates = new List<Position>
{
new Position (37.797534, -122.401827),
new Position (37.776831, -122.394627)
};
})
In your case it's something like
var list = new List<Position>(customMap.RouteCoordinates);
list.Add(directionMap.address_end.position);
customMap.RouteCoordinates = list;
Todo
On iOS you have now to implement a similar behavior (like UpdatePolyLine)
Note
That might not the most performant implementation, because you redraw everything instead of adding one point. But it's fine as long as you have no performance issues :)
I followed the tutorial available on Xamarin Docs and it worked for me with some changes based on #Sven-Michael Stübe answer
I load the coordinates from a WebService and then I create a separate List, and after this, I set the new list to the RouteCoordinates property on Custom Map.
Some changes are made on Android Renderer
I'm using MVVM.
CustomMap Class:
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create(nameof(RouteCoordinates), typeof(List<Position>), typeof(CustomMap), new List<Position>(), BindingMode.TwoWay);
public List<Position> RouteCoordinates
{
get { return (List<Position>)GetValue(RouteCoordinatesProperty); }
set { SetValue(RouteCoordinatesProperty, value); }
}
public CustomMap()
{
RouteCoordinates = new List<Position>();
}
ViewModel (Codebehind, in your case):
private async void LoadCoordinates(string oidAula, CustomMap mapa)
{
IsBusy = true;
var percurso = await ComunicacaoServidor.GetPercurso(oidAula); // Get coordinates from WebService
var pontos = percurso.Select(p => new Position(p.Latitude, p.Longitude)).ToList(); // Create coordinates list from webservice result
var latitudeMedia = percurso[percurso.Count / 2].Latitude;
var longitudeMedia = percurso[percurso.Count / 2].Longitude;
mapa.RouteCoordinates = pontos;
mapa.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(latitudeMedia, longitudeMedia), Distance.FromMiles(1.0)));
IsBusy = false;
}
XAML:
<maps:CustomMap
AbsoluteLayout.LayoutFlags = "All"
AbsoluteLayout.LayoutBounds = "0, 0, 1, 1"
VerticalOptions = "FillAndExpand"
HorizontalOptions = "FillAndExpand"
x:Name = "PercursoMapa" />
Android Renderer:
public class CustomMapRenderer : MapRenderer
{
bool isDrawn;
protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
Control.GetMapAsync(this);
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if ((e.PropertyName == "RouteCoordinates" || e.PropertyName == "VisibleRegion") && !isDrawn)
{
var polylineOptions = new PolylineOptions();
polylineOptions.InvokeColor(0x66FF0000);
var coordinates = ((CustomMap)Element).RouteCoordinates;
foreach (var position in coordinates)
polylineOptions.Add(new LatLng(position.Latitude, position.Longitude));
NativeMap.AddPolyline(polylineOptions);
isDrawn = coordinates.Count > 0;
}
}
}
This example have more than 3600 points of location and the polyline shows correctly on device:
Screenshot
Building on these answers, here is what I did to get it to work on iOS. This allows changing the route even after the map is loaded, unlike the Xamarin sample.
Firstly, custom map class as per #Sven-Michael Stübe with the update from #Emixam23:
public class CustomMap : Map
{
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create(nameof(RouteCoordinates), typeof(List<Position>), typeof(CustomMap), new List<Position>(), BindingMode.TwoWay);
public List<Position> RouteCoordinates
{
get { return (List<Position>)GetValue(RouteCoordinatesProperty); }
set { SetValue(RouteCoordinatesProperty, value); }
}
public CustomMap()
{
RouteCoordinates = new List<Position>();
}
}
Next, the iOS custom renderer:
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace KZNTR.iOS
{
public class CustomMapRenderer : MapRenderer
{
MKPolylineRenderer polylineRenderer;
CustomMap map;
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if ((this.Element == null) || (this.Control == null))
return;
if (e.PropertyName == CustomMap.RouteCoordinatesProperty.PropertyName)
{
map = (CustomMap)sender;
UpdatePolyLine();
}
}
[Foundation.Export("mapView:rendererForOverlay:")]
MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlay)
{
if (polylineRenderer == null)
{
var o = ObjCRuntime.Runtime.GetNSObject(overlay.Handle) as MKPolyline;
polylineRenderer = new MKPolylineRenderer(o);
//polylineRenderer = new MKPolylineRenderer(overlay as MKPolyline);
polylineRenderer.FillColor = UIColor.Blue;
polylineRenderer.StrokeColor = UIColor.Red;
polylineRenderer.LineWidth = 3;
polylineRenderer.Alpha = 0.4f;
}
return polylineRenderer;
}
private void UpdatePolyLine()
{
var nativeMap = Control as MKMapView;
nativeMap.OverlayRenderer = GetOverlayRenderer;
CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[map.RouteCoordinates.Count];
int index = 0;
foreach (var position in map.RouteCoordinates)
{
coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);
index++;
}
var routeOverlay = MKPolyline.FromCoordinates(coords);
nativeMap.AddOverlay(routeOverlay);
}
}
}
And finally, adding a polyline to the map:
Device.BeginInvokeOnMainThread(() =>
{
customMap.RouteCoordinates.Clear();
var plist = new List<Position>(customMap.RouteCoordinates);
foreach (var point in track.TrackPoints)
{
plist.Add(new Position(double.Parse(point.Latitude, CultureInfo.InvariantCulture), double.Parse(point.Longitude, CultureInfo.InvariantCulture)));
}
customMap.RouteCoordinates = plist;
var firstpoint = (from pt in track.TrackPoints select pt).FirstOrDefault();
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(double.Parse(firstpoint.Latitude, CultureInfo.InvariantCulture), double.Parse(firstpoint.Longitude, CultureInfo.InvariantCulture)), Distance.FromMiles(3.0)));
});
Not sure if this is the best way to do it, or the most efficient, I don't know much about renderers, but it does seem to work.
So after lot of searches and, of course, the answer of #Sven-Michael Stübe, you can have your proper maps which works on each platform "Android, iOS, WinPhone". Follow my code, then edit it following the #Sven-Michael Stübe's answer.
Once you finished everything, it could works (like for #Sven-Michael Stübe), but it also couldn't work (like for me). If it doesn't works, try to change the following code:
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create<CustomMap, List<Position>>(
p => p.RouteCoordinates, new List<Position>());
by
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create(nameof(RouteCoordinates), typeof(List<Position>), typeof(CustomMap), new List<Position>(), BindingMode.TwoWay);
See the documentation for more information about it. (Deprecated implementation)
Then the code works !
PS: You can have some troubles with the polyline at the end, which not following the road right, I'm working on it.
PS2: I'll also make a video to explain how to code your customMap to don't have to install a NuGet package, to be able to edit everything at the end ! (The first one will be in French, the second in English, this post will be edited when the video will be made)
Thank angain to #Sven-Michael Stübe !! Thank to up his answer as well :)

how to combine query results of several times function call with entity framework5?

i want to use a function to query db with linq and combine their results,i write the code as follows but cannot work , any one can help me? thanks!
the error:(The operation cannot be completed because the DbContext has been disposed)
the part code:
public static IEnumerable<UserLocation> loadedUserList;
public static IEnumerable<UserLocation> combinedUserList;
public static void loadDataInStaticClass()
{
using (var context = new SptialTestEntities())
{
var loadedUserList = from newRcords in context.UserLocations
where newRcords.ID > lastLoadedID
select newRcords;
if (loadedUserList.Any())
{
foreach (var user in loadedUserList)
{
Console.WriteLine(user.UserName);
}
if (combinedUserList != null)
{
combinedUserList = loadedUserList.Union(combinedUserList);
foreach (var cc in combinedUserList)
{
Console.WriteLine("\n after combined:" + cc.UserName);
}
}
else
{
combinedUserList = loadedUserList;
Console.WriteLine("\nfirst run :" + combinedUserList.Count());
foreach (var cc in combinedUserList)
{
Console.WriteLine("\n user:" + cc.UserName);
}
}
}
}
}
the problem is: the first call is ok, but the second report error: The operation cannot be completed because the DbContext has been disposed ,and how?
thanks!
i paste the whole code and some one can run and check the mistake and thank u:
userLocation a table contain userid,username,userlocation(geography type) ,and
i user database first mode in visual studio 2012 and map the userLocation to a entity of SptialTestEntities.
Program.cs
static void Main(string[] args)
{
for (int i = 1; i < 3; i++)
{
Console.WriteLine("\nrun{0}times, i);
Test.LoadUsersFromDB();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Data.Spatial;
using System.Data.Entity;
using System.Xml.Linq;
using System.IO;
using System.Configuration;
using System.Web;
using System.Web.Script.Serialization;
using System.Collections;
using System.Globalization;
namespace SptialMatch
{
class Test
{
public static int lastedLoadedRecordID = 14;
public static IEnumerable<UserLocation> loadedUserList;
public static IEnumerable<UserLocation> combinedUserList;
public static void LoadUsersFromDB()
{
try
{
Console.WriteLine("\n------------------------load data begin----------------------------------------------------------");
//var context = new SptialTestEntities();
using (var context = new SptialTestEntities())
{
System.Diagnostics.Stopwatch loadStopwatch = new System.Diagnostics.Stopwatch();
loadStopwatch.Start();
loadedUserList = from newRcords in context.UserLocations
where newRcords.ID > lastedLoadedRecordID
select newRcords;
if (loadedUserList.Any())
{
foreach (var loadUser in loadedUserList)
{
Console.WriteLine("\n loaded element:" + loadUser.UserName);
}
if (combinedUserList != null)
{
Console.WriteLine("\nnot first run:" );
foreach (var cc in combinedUserList)
{
Console.WriteLine("\n before union:" + cc.UserName);
}
IEnumerable<UserLocation> tmp = loadedUserList.AsEnumerable();
combinedUserList = tmp.Union<UserLocation>(combinedUserList.AsEnumerable(), new UserComparer2()).ToList();
Console.WriteLine("\nnot first run after union:" );
foreach (var cc in combinedUserList)
{
Console.WriteLine("\n after union the user name is:" + cc.UserName);
}
}
else
{
combinedUserList = loadedUserList;
Console.WriteLine("\nfirst run the count is:" + combinedUserList.Count());
foreach (var cc in combinedUserList)
{
Console.WriteLine("\n the combined list:" + cc.UserName);
}
}
var maxID = loadedUserList.Max(myMaxID => myMaxID.ID);
lastedLoadedRecordID = lastedLoadedRecordID + 1;
}
else
{
Console.WriteLine("\n no new data!");
Console.WriteLine("\n-----------------load end,no new data yet------------------------------------------------");
Thread.Sleep(3000);
}
loadStopwatch.Stop();
Console.WriteLine("\nload time cost{0} seconds。", loadStopwatch.Elapsed);
Console.WriteLine("\n---------------------load end ----------------------------------------------------------");
}
}
catch (Exception ex)
{
Console.WriteLine("\n exception message:" + ex.Message);
}
}
}
class UserComparer2 : IEqualityComparer<UserLocation>
{
public bool Equals(UserLocation x, UserLocation y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.ID == y.ID && x.UserName == y.UserName;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(UserLocation user)
{
//Check whether the object is null
if (Object.ReferenceEquals(user, null)) return 0;
//Get hash code for the Name field if it is not null.
int hashUserName = user.UserName == null ? 0 : user.UserName.GetHashCode();
//Get hash code for the Code field.
int hashUserCode = user.ID.GetHashCode();
//Calculate the hash code for the product.
return hashUserName ^ hashUserCode;
}
}
}

Resources