i'm using Gmap.NET with C# WPF, and i'd like to add a large amount of markers (~6k) on the map.
But i still can't make add them asynchronously, Map is always freezing and not responding at all, until all markers will not be added...
Here is my code sample:
private void MainMap_Loaded(object sender, RoutedEventArgs e)
{
MainMap.Zoom = 12;
LoadMarkers();
}
private async void LoadMarkers()
{
await Task.Run(new Action(() =>
{
for (int i = 0; i <= 6000; i++)
{
Dispatcher.InvokeAsync(
new Action(
delegate()
{
PointLatLng point = new PointLatLng(GetRandomNumber(55.0000, 55.7510),
GetRandomNumber(36.0000, 38.9999));
var currentMarker = new GMap.NET.WindowsPresentation.GMapMarker(point);
{
currentMarker.Shape = new MarkerTemplate(this, currentMarker,
string.Empty);
currentMarker.Offset = new Point(-16, -32);
currentMarker.ZIndex = int.MaxValue;
MainMap.Markers.Add(currentMarker);
}
}
));
}
}));
}
You probably need to design a cluster marker solution for GMap. Use the Map_OnMapZoomChanged event to hide/show markers accordingly.
With a bit of a work, you might be able to achieve a cluster like the Google Maps one:
Good luck! Don't forget to open-source it when you're done :)
Related
I am currently using liveChart to plot a real time graph of 3 values: a position, a load and a deformation. The program is based on the Doli.DoPE library (a proprietary dll)
In MainForm.cs, there is an event that is triggered everytime the sensor records a new value (every millisecond or so).
public void Initialisation()
{
//...
MyEdc.Eh.OnDataHdlr += new DoPE.OnDataHdlr(OnData)
//...
}
with
private int OnData(ref DoPE.OnData Data, object Parameter)
{
DoPE.Data Sample = Data.Data;
if (Data.DoPError == DoPE.ERR.NOERROR)
{
Int32 Time = Environment.TickCount;
if ((Time - LastTime) >= 250 /*ms*/)
{
// Send the data from the ondata handler inside of a global list
ListData.time.Add(Sample.Time);
ListData.position.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_S]);
ListData.load.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_F]);
ListData.extend.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_E]);
Thread ThForUpdateChart = new Thread(() =>
{
if (NewINstanceOfChart != null)
{ NewINstanceOfChart.UpdateValues(ListData.time.Last(), ListData.position.Last(),ListData.load.Last(), ListData.extend.Last()); }
});
ThForUpdateChart.Start();
LastTime = Time;
}
}
return 0;
}
The function UpdateValues is part of a second form RealTimeChart.cs called in the MainForm through a button click event:
private void btnGraph_Click(object sender, EventArgs e)
{
var thread = new Thread(() =>
{
NewINstanceOfChart = new RealTimeChart(ListData);
NewINstanceOfChart.Show();
});
thread.Start();
}
the form RealTimeCharts.cs is initalised this way:
public RealTimeChart(Globals ListData)
{
InitializeComponent();
//measures = ListData;
ListPosition = new ChartValues<ObservablePoint>();
for (int i = 0; i < measures.load.Count(); i++)
{
ListPosition.Add(new ObservablePoint
{
X = measures.time[i],
Y = measures.position[i]
});
}
ListLoad = new ChartValues<ObservablePoint>();
for (int i = 0; i < measures.load.Count(); i++)
{
ListLoad.Add(new ObservablePoint
{
X = measures.time[i],
Y = measures.load[i]
});
}
ListExtend = new ChartValues<ObservablePoint>();
for (int i = 0; i < measures.load.Count(); i++)
{
ListExtend.Add(new ObservablePoint
{
X = measures.time[i],
Y = measures.extend[i]
});
}
resultChart.Series.Add(new LineSeries
{
LineSmoothness = 0,
Values = ListPosition,
PointGeometrySize = 2,
StrokeThickness = 4
});
SetXAxisLimits();
}
And the UpdateValues function is defined as followed:
public void UpdateValues(double time, double position, double load, double extend)
{
measures.time.Add(time-measures.TareTime);
measures.position.Add(position);
measures.load.Add(load);
measures.extend.Add(extend);
UpdateEnabledSequencialPartToTrue();
}
public void UpdateEnabledSequencialPartToTrue()
{
if (this.InvokeRequired)
BeginInvoke(new System.Action(() => this.InternalUpdateEnabledSequencialPartToTrue()));
else
InternalUpdateEnabledSequencialPartToTrue();
}
private void InternalUpdateEnabledSequencialPartToTrue()
{
try
{
ListPosition.Add(new ObservablePoint
{
X = measures.time.Last(),
Y = measures.position.Last()
});
ListLoad.Add(new ObservablePoint
{
X = measures.time.Last(),
Y = measures.load.Last()
});
ListExtend.Add(new ObservablePoint
{
X = measures.time.Last(),
Y = measures.extend.Last()
});
//LineSeries plot = new LineSeries();
SetXAxisLimits();
// lets only use the last 14400 values (1h long recording, 14400 values at frequency of 1 record very 250ms, see OnData function MainForm
if (measures.time.Count > 14400)
{
ListPosition.RemoveAt(0);
ListLoad.RemoveAt(0);
ListExtend.RemoveAt(0);
}
}
catch (NullReferenceException) { }
}
After a minute, the programme starts to be really laggy. I tried putting the second winform (RealTimeCharts) on another thread so the MainForm does not lag (it is piloting a machine, it has to be responsive), but no success.
I would like to know if the whole thing is laggy because the code is way too bad, or if it is liveChart that reached its (free) limits. Would you advice another way to plot real time data ?
In MainForm.cs, there is an event that is triggered everytime the sensor records a new value (every millisecond or so).
That is natturally way higher then what Winforms Drawing can take. See, drawing a GUI is expensive. If you only do it once per user-triggered event, you will never notice that. But do it from a loop - including sampling a sensor every MS - and you can quickly overlord the UI. My first Multithreading tests actually appeared to have failed on big numbers, becaus I ended up sending so many updates I plain overloaded the GUI thread. Since then I know not to go past progress bars.
You can add data to a background collection as quickly as you can sample them, but you can not draw that quickly. And honestly drawing more often then 30-60 times/second (every ~17 ms) is not really going to help anyone anyway. Usually you can not use a timer, as the Tick might happen more often then it can be processed - again, a GUI Thread with a overflowing Event Queue.
I do not have any rate limiting code for WindowsForms. But I would guess an Event that re-queues itself at the end of the EventQueue after finishing the work would work.
I am trying to use Dynamic Data Display for my senior design project, but I'm having difficulty.
I can create the graph just fine, but I can't the line to update at all unless I am zooming in and out.
In most examples, all they had to do was create the graph, then when they manipulate the data, the graph updates itself.
So here are the main functions I am using with regards to the charts:
private List<WindDAQ.WindDataPoint> Chart1Data;
private EnumerableDataSource<WindDAQ.WindDataPoint> Chart1DataSource;
private void InitializeCharts()
{
Chart1Data = new List<WindDAQ.WindDataPoint>();
Chart1DataSource = new EnumerableDataSource<WindDAQ.WindDataPoint>(Chart1Data);
Chart1DataSource.SetXMapping(x => Chart1XAxis.ConvertToDouble(x.Time));
Chart1DataSource.SetYMapping(y => Chart1XAxis.ConvertToDouble(y.Lift));
Chart1.AddLineGraph(Chart1DataSource, Colors.Blue, 2, "Lift");
Chart1.Viewport.AutoFitToView = true;
}
private void UpdateChart()
{
for (int i = 0, count = itsDAQ.getStreamCount(); i < count; i++)
{
Chart1Data.Add(itsDAQ.getValue());
if(Chart1Data.Count >= 300)
{ Chart1Data.RemoveAt(0); }
}
}
InitializeCharts() is called once when creating the window.
UpdateChart() is called on a timer event.
WindDAQ.WindDataPoint contains Lift, Drag, Velocity, and Time data. Lift and Time are shown selected.
you should use the AppendAsync method of your observableCollection.
You'r updating only the list used to create the observable one, not the source of your graph.
private void UpdateChart()
{
for (int i = 0, count = itsDAQ.getStreamCount(); i < count; i++)
{
Chart1DataSource.AppendAsync(itsDAQ.getValue());
if (Chart1DataSource.Collection.Count >= 300) // this part should work in a thread-safe
{
Chart1DataSource.Collection.RemoveAt(0); // context and with some precaution
}
}
}
I've a game with 90 grey numbers and, periodically, some of them become blue.(Simplified!)
Now i use:
Game-->Timer-->Thread-->coloring numbers
because i want use my game also when the numbers in coloration.
the code:
some variables..
public FormHome()
{
InitializeComponent();
TEstrazione = new System.Timers.Timer(10000);
TEstrazione.AutoReset = false;
TEstrazione.Elapsed += timerEstrazioneElapsed;
TEstrazione.Start();
}
private void timerEstrazioneElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
ThreadEstrazione = new Thread(cThread);
ThreadEstrazione.IsBackground = true;
ThreadEstrazione.Start();
}
private void cThread()
{
//other methods that change others controls..
coloring();
TEstrazione.Start();
}
void coloring()
{
for (int i = 0; i < 20; i++)//20 numbers to make colored
{
int num = *get random number*;
Label lbl;
lbl = (Label)panelNumembers.Controls["n" + num.ToString()];
if (lbl.InvokeRequired)
{
lbl.Invoke(new Action(coloring), new object[] { });
return;
}
lbl.BackColor = Color.DodgerBlue;
lbl.ForeColor = Color.Navy;
lbl.Refresh();
Thread.Sleep(800);
}
}
excuse me if some names are in italian but i think they aren't important.
The problem is that when the thread is coloring the label, the program locks: i can't push buttons and even close it! I think is a thread-safe problem but i don't know other ways.
As i've written, there are other methods inside a cThread, all change the controls. One of theese is:
void refreshLabel()
{
//here I always used one of the controls for the InvokeRequired
because in some cases I have so many and if I do the InvokeRequired
for each control becomes too long the code
if (label1.InvokeRequired)
{
ne.Invoke(new Action(refreshLabel), new object[] { });
return;
}
label1.Text = "xxxxx";
label1.Refresh();
label2.Text = "xxxxxxxxxxxxxxxx";
label2.Refresh();
}
In conclusion I have a lot of methods started by cThread that change a lot of controls, there is a way to make it thread safe without stretching too much code?
I apologize for the English but I'm Italian and is not easy explain the problems in an another language.
I'm using my girlfriend laptop so I'll edit my answer and give you a much better answer ASAP. the problem occurred because most of the execution is in the UI thread. you should execute most of your execution in background. when you want to update the UI you need to change context to the UI thread(only updates!).
Change coloring to:
void coloring()
{
for (int i = 0; i < 20; i++)//20 numbers to make colored
{
int num = *get random number*;
Label lbl;
lbl = (Label)panelNumembers.Controls["n" + num.ToString()];
var updateUI = new Action(() => {
lbl.BackColor = Color.DodgerBlue;
lbl.ForeColor = Color.Navy;
lbl.Refresh();
});
if (lbl.InvokeRequired)
{
Invoke(updateUI);
}
else
{
updateUI();
}
Thread.Sleep(800);
}
if there is no other blocking operations inside your code then this might work
I am making an application using WPF and C#. What I'm trying to do is to print a lots of shapes on a canvas with some time in between when I push a button. At the moment when I push the button, everything just pops up at once. I have tried to sleep the thread for some time between every "print" but that doesn't help, it just take longer time before everything splash up at once. What I want to achive is that the shapes pop up one at a time with lets say 0.5 seconds in between. The code is the following:
private void Create_Click(object sender, RoutedEventArgs e)
{
Random random = new Random();
for (int i = 0; i < 50; i++)
{
Thread.Sleep(500);
Path f = FlowerFactory.createFlower(FlowerBP, true);
Canvas.SetLeft(f, random.Next(0, 1650));
Canvas.SetTop(f, random.Next(0,1000));
DrawBoard.Children.Add(f);
}}
You need to, first, run the loop in a background thread so that it doesn't block the UI from updating; and second, send the UI-rendering tasks back to the UI thread. For the first, you can use Task.Factory.StartNew, and for the second, use Dispatcher.Invoke:
Random random = new Random();
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 50; i++)
{
Thread.Sleep(500);
Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
{
Path f = FlowerFactory.createFlower(FlowerBP, true);
Canvas.SetLeft(f, random.Next(0, 1650));
Canvas.SetTop(f, random.Next(0,1000));
DrawBoard.Children.Add(f);
}));
}
});
It was bad decision to sleep your main thread that responsible for GUI.
Try to use DispatchTimer. For example:
DispatcherTimer m_dispatcherTimer = new DispatcherTimer();
int m_count = 50;
private void Init()
{
m_dispatcherTimer.Tick += new EventHandler(OnTick);
m_dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
}
private void Create_Click(object sender, RoutedEventArgs e)
{
m_count = 50;
m_dispatcherTimer.Start();
}
private void OnTick(object sender, EventArgs e)
{
// Draw your shapes here
if(0>=--m_count)
{
m_dispatcherTimer.Stop();
}
}
using System.Reactive.Concurrency;
using System.Reactive.Linq;
...
var subscription = Observable.Interval(TimeSpan.FromSeconds(0.5)).Take(50).Subscribe(x=>CreateRandomFlower);
I am trying to get my media controls to auto hide after 2 seconds. However the way I have it set up is that it only works when my mouse leaves the StackPanel I have the media controls in. And if I keep moving the in and out of the StackPanel then it will start to flicker as it is firing the hide even multiple times. I am unsure on how to go about this logically. Anyone have any tips or suggestions?
Here is what I got right now (StackPanel is named controls).
...
controls.MouseMove += new MouseEventHandler(control_unhide);
controls.MouseLeave += new MouseEventHandler(control_hide);
...
void control_hide(object sender, MouseEventArgs e)
{
var miniTimer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(2) };
miniTimer.Tick += (s, i) => { miniTimer.Stop(); controls.Opacity = 0; };
miniTimer.Start();
}
//Unhide controls
void control_unhide(object sender, MouseEventArgs e)
{
controls.Opacity = 100;
}
Also from some sample code I have seen people say to use Collapse and Visible to make the controls hide and reappear. This however doesn't work as the Collapse seems to make the boundaries unresponsive to the mouse entering.
Thanks!
**Edit
I asked this question because I spent a lot of time trying to figure this out yesterday only to sit down today and think of a really easy solution. What I did was this:
//global
private DispatcherTimer hideTimer;
....
//init
hideTimer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(2) };
hideTimer.Tick += (s, i) => { hideTimer.Stop(); controls.Opacity = 0; };
hideTimer.Start();
controls.MouseMove += new MouseEventHandler(control_unhide);
controls.MouseLeave += new MouseEventHandler(control_hide);
...
void control_hide(object sender, MouseEventArgs e)
{
hideTimer.Start();
}
//Unhide controls
void control_unhide(object sender, MouseEventArgs e)
{
controls.Opacity = 100;
hideTimer.Stop();
}
A simple solution would be to put in a guard variable and only hide if you weren't currently hiding:
bool currentlyHiding;
void control_hide(object sender, MouseEventArgs e)
{
if (!currentlyHiding)
{
var miniTimer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(2) };
miniTimer.Tick += (s, i) =>
{
miniTimer.Stop();
controls.Opacity = 0;
currentlyHiding = false;
};
miniTimer.Start();
currentlyHiding = true;
}
}
You will also need to do something similar for the unhide.