I am using the Telerik map, I am driving the map with custom controls, that I have on a timer update function
When I move the map struggles to catch up & becomes quite jerky. I need to drive the map this way I don't have a choice in this. Using the Telerik controls to drive it is not an option unfortunately.
From what I can tell it looks like the map or the provider is blocking the UI thread which causes the stall.
This bug is more apparent in high resolutions.
Below is the code I am using to generate the error (I know the code isn't perfect but it highlights the bug)
turn these defines on to change timer methods (1 at a time please) or turn them both off to use the onIdle event
When I move the map struggles to catch up & becomes quite jerky. I need to drive the map this way I don't have a choice in this. Using the Telerik controls to drive it is not an option unfortunately.
From what I can tell it looks like the map or the provider is blocking the UI thread which causes the stall.
This bug is more apparent in high resolutions.
Below is the code I am using to generate the error (I know the code isn't perfect but it highlights the bug)
turn these defines on to change timer methods (1 at a time please) or turn them both off to use the onIdle event
#define DISPACTTIMER #define THREADTIMER
<
Window
x:Class
=
"WpfRadMap.MainWindow"
Title
=
"MainWindow"
Height
=
"1080"
Width
=
"1900"
xmlns:telerik
=
"http://schemas.telerik.com/2008/xaml/presentation"
ResizeMode
=
"NoResize"
Activated
=
"Window_Activated"
>
<
Grid
>
<
telerik:RadMap
Panel.ZIndex
=
"1"
HorizontalAlignment
=
"Stretch"
Name
=
"radMap1"
VerticalAlignment
=
"Stretch"
Center
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Mode=TwoWay, Path=MapLocation}"
ZoomChanged
=
"radMap1_ZoomChanged"
/>
<
Canvas
Panel.ZIndex
=
"2"
x:Name
=
"gizmoPanel"
Visibility
=
"Visible"
HorizontalAlignment
=
"Stretch"
VerticalAlignment
=
"Stretch"
RenderTransformOrigin
=
"0.5,0.5"
>
<
Canvas.RenderTransform
>
<
RotateTransform
x:Name
=
"GizmoRotate"
Angle
=
"0"
/>
</
Canvas.RenderTransform
>
<
Line
Stroke
=
"Red"
StrokeThickness
=
"3"
ClipToBounds
=
"True"
X1
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX1}"
X2
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX1}"
Y1
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY1}"
Y2
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY2}"
Visibility
=
"Visible"
IsHitTestVisible
=
"False"
>
<
Line.Effect
>
<
DropShadowEffect
BlurRadius
=
"5"
ShadowDepth
=
"0"
/>
</
Line.Effect
>
</
Line
>
<!-- Small Line 1 -->
<
Line
Stroke
=
"Red"
StrokeThickness
=
"3"
ClipToBounds
=
"True"
X1
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX1}"
X2
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX2}"
Y1
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY2}"
Y2
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY3}"
Visibility
=
"Visible"
IsHitTestVisible
=
"False"
>
<
Line.Effect
>
<
DropShadowEffect
BlurRadius
=
"5"
ShadowDepth
=
"0"
/>
</
Line.Effect
>
</
Line
>
<!-- Small Line 2 -->
<
Line
Stroke
=
"Red"
StrokeThickness
=
"3"
ClipToBounds
=
"True"
X1
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX1}"
X2
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX3}"
Y1
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY2}"
Y2
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY3}"
Visibility
=
"Visible"
IsHitTestVisible
=
"False"
>
<
Line.Effect
>
<
DropShadowEffect
BlurRadius
=
"5"
ShadowDepth
=
"0"
/>
</
Line.Effect
>
</
Line
>
</
Canvas
>
<
Button
Panel.ZIndex
=
"3"
Content
=
"Rotate Clockwise"
Height
=
"39"
Name
=
"button1"
Width
=
"99"
Click
=
"button1_Click"
Margin
=
"1690,12,39,980"
/>
<
Button
Panel.ZIndex
=
"3"
Content
=
"Rotate AntiClockwise"
Height
=
"39"
Name
=
"button2"
Width
=
"119"
Click
=
"button2_Click"
Margin
=
"1680,57,28,935"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"1670,100, 0,910"
Content
=
"[W] to move along arrow"
Height
=
"28"
Name
=
"label1"
Width
=
"180"
FontWeight
=
"Bold"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"1670,120, 0,890"
Content
=
"[S] to move opposite to arrow"
Height
=
"28"
Name
=
"label2"
Width
=
"180"
FontWeight
=
"Bold"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"1670,140, 0,870"
Content
=
"[A] to move left of arrow"
Height
=
"28"
Name
=
"label3"
Width
=
"180"
FontWeight
=
"Bold"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"1670,160, 0,850"
Content
=
"[D] to move right of arrow"
Height
=
"28"
Name
=
"label4"
Width
=
"180"
FontWeight
=
"Bold"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"0,10, 1670,980"
Content
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=FastestTime}"
Height
=
"28"
Name
=
"lblMinSpeed"
Width
=
"180"
FontWeight
=
"Bold"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"0,40, 1670,950"
Content
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=SlowestTime}"
Height
=
"28"
Name
=
"lblMaxSpeed"
Width
=
"180"
FontWeight
=
"Bold"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"0,70, 1670,920"
Content
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=AverageTime}"
Height
=
"28"
Name
=
"lblAvgSpeed"
Width
=
"180"
FontWeight
=
"Bold"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"0,110, 1670,880"
Content
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=FastestUpdateTime}"
Height
=
"28"
Name
=
"lblMinUpdate"
Width
=
"180"
FontWeight
=
"Bold"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"0,140, 1670,850"
Content
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=SlowestUpdateTime}"
Height
=
"28"
Name
=
"lblMaxUpdate"
Width
=
"180"
FontWeight
=
"Bold"
/>
<
Label
Panel.ZIndex
=
"3"
Margin
=
"0,170, 1670,820"
Content
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=AverageUpdateTime}"
Height
=
"28"
Name
=
"lblAvgUpdate"
Width
=
"180"
FontWeight
=
"Bold"
/>
</
Grid
>
</
Window
>
#define QUERY_PERFORMANCE
//#define DISPACTTIMER
//#define THREADTIMER
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Data;
using
System.Windows.Documents;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Imaging;
using
System.Windows.Navigation;
using
System.Windows.Shapes;
using
System.Windows.Threading;
using
System.Timers;
using
System.ComponentModel;
namespace
WpfRadMap
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public
partial
class
MainWindow : Window, INotifyPropertyChanged
{
Telerik.Windows.Controls.Map.Location m_location;
double
width = 1900.0;
double
height = 1080.0;
private
double
gizmoX1;
private
double
gizmoX2;
private
double
gizmoX3;
private
double
gizmoY1;
private
double
gizmoY2;
private
double
gizmoY3;
private
RotateTransform longLatRotate;
private
double
framesPerSecond = 60.0;
#if DISPACTTIMER
DispatcherTimer m_updateTimer;
#elif THREADTIMER
Timer m_updateTimer;
#endif
public
MainWindow()
{
gizmoX1 = width * 0.5;
gizmoX2 = width * 0.485;
gizmoX3 = width * 0.515;
gizmoY1 = height * 0.5;
gizmoY2 = height * 0.45;
gizmoY3 = height * 0.475;
longLatRotate =
new
RotateTransform();
longLatRotate.Angle = 0.0;
m_location =
new
Telerik.Windows.Controls.Map.Location();
InitializeComponent();
const
string
bingKey =
""
;
if
(bingKey.Length > 0)
{
Telerik.Windows.Controls.Map.BingMapProvider bingProvider =
new
Telerik.Windows.Controls.Map.BingMapProvider(Telerik.Windows.Controls.Map.MapMode.Aerial,
true
, bingKey);
bingProvider.IsTileCachingEnabled =
true
;
bingProvider.IsLabelVisible =
true
;
radMap1.Provider = bingProvider;
SetLabelsFontColor(Brushes.White);
}
else
{
Telerik.Windows.Controls.Map.OpenStreetMapProvider openProvider =
new
Telerik.Windows.Controls.Map.OpenStreetMapProvider();
openProvider.IsTileCachingEnabled =
true
;
radMap1.Provider = openProvider;
}
radMap1.ZoomLevel = 19;
m_location.Latitude = 48.856941702106354;
m_location.Longitude = 2.3383069038354383;
MapLocation = m_location;
#if DISPACTTIMER
m_updateTimer =
new
DispatcherTimer();
m_updateTimer.Tick +=
new
EventHandler(m_updateTimer_Tick);
m_updateTimer.Interval = TimeSpan.FromSeconds(1.0/framesPerSecond);
#elif THREADTIMER
m_updateTimer =
new
Timer();
m_updateTimer.Elapsed +=
new
ElapsedEventHandler(m_updateTimer_Tick);
m_updateTimer.Interval = (1.0 / framesPerSecond) * 100;
m_updateTimer.Enabled =
false
;
#else
System.Windows.Interop.ComponentDispatcher.ThreadIdle +=
new
System.EventHandler(ComponentDispatcher_ThreadIdle);
#endif
profileTimer = System.Diagnostics.Stopwatch.StartNew();
}
public
Telerik.Windows.Controls.Map.Location MapLocation
{
get
{
return
m_location; }
set
{ m_location = value; RaisePropertyChanged(
"MapLocation"
);
}
}
double
m_mapHeight = 0.0001;
public
double
MapHeight
{
get
{
return
m_mapHeight;}
set
{m_mapHeight = value; RaisePropertyChanged(
"MapHeight"
);}
}
private
void
radMap1_ZoomChanged(
object
sender, EventArgs e)
{
MapHeight = radMap1.GeographicalBounds.Height;
}
readonly
Queue<
long
> m_frameTicks =
new
Queue<
long
>();
long
m_slowest =
long
.MinValue;
long
m_fastest =
long
.MaxValue;
long
m_avg = 0;
public
string
AverageTime
{
get
{
return
"Avg: "
+ m_avg +
"us"
; }
}
public
string
SlowestTime
{
get
{
return
"Slowest: "
+ m_slowest +
"us"
; }
}
public
string
FastestTime
{
get
{
return
"Fastest: "
+ m_fastest +
"us"
; }
}
readonly
Queue<
long
> m_frameUpdateTicks =
new
Queue<
long
>();
long
m_slowestUpdate =
long
.MinValue;
long
m_fastestUpdate =
long
.MaxValue;
long
m_avgUpdate = 0;
System.Diagnostics.Stopwatch profileTimer;
public
string
AverageUpdateTime
{
get
{
return
"AvgUpdate: "
+ m_avgUpdate +
"ms"
; }
}
public
string
SlowestUpdateTime
{
get
{
return
"SlowestUpdate: "
+ m_slowestUpdate +
"ms"
; }
}
public
string
FastestUpdateTime
{
get
{
return
"FastestUpdate: "
+ m_fastestUpdate +
"ms"
; }
}
protected
void
CalculatePerformance(
long
a_currentTick, Queue<
long
> a_avgTicks,
ref
long
a_slowestTick,
ref
long
a_fastestTick,
ref
long
a_avgTick)
{
a_avgTicks.Enqueue(a_currentTick);
if
(a_avgTicks.Count > 60)
{
a_avgTicks.Dequeue();
}
long
slowest = a_currentTick;
long
fastest = a_currentTick;
long
total = 0;
foreach
(
long
time
in
a_avgTicks)
{
total += time;
slowest = Math.Max(slowest, time);
fastest = Math.Min(fastest, time);
}
a_avgTick = total / a_avgTicks.Count;
a_slowestTick = slowest;
a_fastestTick = fastest;
}
#if DISPACTTIMER
void
m_updateTimer_Tick(
object
sender, EventArgs e)
#elif THREADTIMER
void
m_updateTimer_Tick(
object
sender, ElapsedEventArgs e)
#else
void
ComponentDispatcher_ThreadIdle(
object
sender, EventArgs e)
#endif
{
long
elapsedTime = profileTimer.ElapsedTicks;
long
elapsedMilliSeconds = System.Convert.ToInt64((System.Convert.ToDouble(elapsedTime) / System.Convert.ToDouble(System.Diagnostics.Stopwatch.Frequency)) * 1000);
// if (elapsedMilliSeconds < (1.0/framesPerSecond)*1000)
// {
// return;
// }
CalculatePerformance(elapsedMilliSeconds, m_frameUpdateTicks,
ref
m_slowestUpdate,
ref
m_fastestUpdate,
ref
m_avgUpdate);
RaisePropertyChanged(
"SlowestUpdateTime"
);
RaisePropertyChanged(
"FastestUpdateTime"
);
RaisePropertyChanged(
"AverageUpdateTime"
);
profileTimer.Restart();
double
tearRepair = 1.0;
if
(elapsedMilliSeconds > 0)
{
tearRepair = elapsedMilliSeconds / framesPerSecond;
}
double
speed = 2.0;
double
latitude = m_location.Latitude;
double
longitude = m_location.Longitude;
#if THREADTIMER
Point stepDir =
new
Point(0.0, (MapHeight/(speed * framesPerSecond))*tearRepair);
Point dir = stepDir;
//longLatRotate.Transform(stepDir);
latitude += dir.Y;
longitude -= dir.X;
#else
#if DISPACTTIMER
Point stepDir =
new
Point(0.0, (MapHeight/(speed * framesPerSecond))*tearRepair);
#else
Point stepDir =
new
Point(0.0, (MapHeight / (speed * framesPerSecond)) * tearRepair);
#endif
Point dir = longLatRotate.Transform(stepDir);
if
((Keyboard.GetKeyStates(Key.W) & KeyStates.Down) > 0)
{
latitude += dir.Y;
longitude -= dir.X;
}
if
((Keyboard.GetKeyStates(Key.S) & KeyStates.Down) > 0)
{
latitude -= dir.Y;
longitude += dir.X;
}
if
((Keyboard.GetKeyStates(Key.A) & KeyStates.Down) > 0)
{
latitude -= dir.X;
longitude -= dir.Y;
}
if
((Keyboard.GetKeyStates(Key.D) & KeyStates.Down) > 0)
{
latitude += dir.X;
longitude += dir.Y;
}
#endif
while
(latitude>360)
{
latitude-=360;
}
while
(latitude < 0)
{
latitude += 360;
}
while
(longitude > 360)
{
longitude -= 360;
}
while
(longitude < 0)
{
longitude += 360;
}
m_location.Latitude = latitude;
m_location.Longitude = longitude;
RaisePropertyChanged(
"MapLocation"
);
elapsedTime = profileTimer.ElapsedTicks;
long
elapsedMicroSeconds = System.Convert.ToInt64((System.Convert.ToDouble(elapsedTime) / System.Convert.ToDouble(System.Diagnostics.Stopwatch.Frequency))*1000000);
CalculatePerformance(elapsedMicroSeconds, m_frameTicks,
ref
m_slowest,
ref
m_fastest,
ref
m_avg);
RaisePropertyChanged(
"SlowestTime"
);
RaisePropertyChanged(
"FastestTime"
);
RaisePropertyChanged(
"AverageTime"
);
}
public
double
GizmoX1
{
get
{
return
gizmoX1; }
set
{ gizmoX1 = value; }
}
public
double
GizmoX2
{
get
{
return
gizmoX2; }
set
{ gizmoX2 = value; }
}
public
double
GizmoX3
{
get
{
return
gizmoX3; }
set
{ gizmoX3 = value; }
}
public
double
GizmoY1
{
get
{
return
gizmoY1; }
set
{ gizmoY1 = value; }
}
public
double
GizmoY2
{
get
{
return
gizmoY2; }
set
{ gizmoY2 = value; }
}
public
double
GizmoY3
{
get
{
return
gizmoY3; }
set
{ gizmoY3 = value; }
}
private
void
button2_Click(
object
sender, RoutedEventArgs e)
{
longLatRotate.Angle = GizmoRotate.Angle = (GizmoRotate.Angle + 15) % 360;
}
private
void
button1_Click(
object
sender, RoutedEventArgs e)
{
longLatRotate.Angle = GizmoRotate.Angle = (GizmoRotate.Angle - 15);
if
(GizmoRotate.Angle == 0)
{
longLatRotate.Angle = GizmoRotate.Angle = 360;
}
}
void
SetLabelsFontColor(Brush a_color)
{
lblMinUpdate.Foreground = a_color;
lblMaxUpdate.Foreground = a_color;
lblAvgUpdate.Foreground = a_color;
lblMinSpeed.Foreground = a_color;
lblMaxSpeed.Foreground = a_color;
lblAvgSpeed.Foreground = a_color;
label1.Foreground = a_color;
label2.Foreground = a_color;
label3.Foreground = a_color;
label4.Foreground = a_color;
}
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected
void
RaisePropertyChanged(
string
propertyName)
{
this
.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler =
this
.PropertyChanged;
if
(handler !=
null
)
{
PropertyChangedEventArgs e =
new
PropertyChangedEventArgs(propertyName);
handler(
this
, e);
}
}
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
public
void
VerifyPropertyName(
string
propertyName)
{
// If you raise PropertyChanged and do not specify a property name,
// all properties on the object are considered to be changed by the binding system.
if
(String.IsNullOrEmpty(propertyName))
{
return
;
}
// Verify that the property name matches a real,
// public, instance property on this object.
if
(TypeDescriptor.GetProperties(
this
)[propertyName] ==
null
)
{
string
msg =
"Invalid property name: "
+ propertyName;
if
(
this
.ThrowOnInvalidPropertyName)
{
throw
new
ArgumentException(msg);
}
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected
virtual
bool
ThrowOnInvalidPropertyName {
get
;
private
set
; }
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public
event
PropertyChangedEventHandler PropertyChanged;
private
void
Window_Activated(
object
sender, EventArgs e)
{
MapHeight = radMap1.GeographicalBounds.Height;
#if DISPACTTIMER
m_updateTimer.Start();
#elif THREADTIMER
m_updateTimer.Enabled =
true
;
#endif
}
}
}