Hi,
in my code I'm continously updating a map overlay. At some point of time I raise a MessageBox
ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show(
message,
"Kursverfolgung",
MessageBoxButton.OK,
MessageBoxImage.Information,
MessageBoxResult.OK
);
This makes my map window freezing and my overlay position don't gets updated anymore. Maybe it's a problem with WPF MessageBox itself but is there a way to show an into message to the user without blocking the UI?
There is no user interaction necessary. The info pane can disappear by itself let's say after 5 seconds.
Can I create a invisible pane, position it over the map view and make it visible for a short amount of time?
Thanks for any help.
Solved! Go to Solution.
Hi, the MapView Overlay Control would likely fit your needs.
https://github.com/esri/arcgis-pro-sdk/wiki/ProConcepts-Map-Exploration#mapview-overlay-control
Hi, the MapView Overlay Control would likely fit your needs.
https://github.com/esri/arcgis-pro-sdk/wiki/ProConcepts-Map-Exploration#mapview-overlay-control
Hello @Anonymous User,
thank you for your link. I remembered there was something like that.
But for some reason the MapViewOverlayControl is not visible for me. I also reinstalled the .Net SDK templates and utilities for VS2017 and tried with the embeddable control template but same effect. Maybe it's a thread problem again?
_handleGpsDataThread = new Thread(StartHandleGpsDataLoop);
_handleGpsDataThread.Priority = ThreadPriority.BelowNormal;
_handleGpsDataThread.Start();
private async void StartHandleGpsDataLoop()
{
await HandleNewGpsDataAsync(GpsMessageBuffer.Last());
}
private async Task HandleNewGpsDataAsync(GpsData newGpsData)
{
await QueuedTask.Run(() =>
{
try
{
...
var targetCourse =
GetDistanceToCurrentTarget(workstationLocation, 2).Result;
...
}
catch (EndOfRouteException eore)
{
var courseEndMessageControl =
new CourseEndMessageControlView();
var mapViewOverlayControl =
new MapViewOverlayControl(
courseEndMessageControl,
true,
true,
true,
OverlayControlRelativePosition.TopLeft);
MapView.Active.AddOverlayControl(mapViewOverlayControl);
}
}
}
private Task<Course> GetDistanceToCurrentTarget(MapPoint currentLocation, int decimalPrecision)
{
return QueuedTask.Run(() =>
{
...
throw new EndOfRouteException(
"You reached the end of the current route.");
...
}
}
<UserControl x:Class="AddIn.Gps.UserControls.CourseEndMessageControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="clr-namespace:AddIn.Gps.UserControls"
xmlns:extensions="clr-namespace:ArcGIS.Desktop.Extensions;assembly=ArcGIS.Desktop.Extensions"
mc:Ignorable="d"
d:DesignHeight="75" d:DesignWidth="415"
d:DataContext="{Binding Path=ui.CourseEndMessageControlViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<extensions:DesignOnlyResourceDictionary Source="pack://application:,,,/ArcGIS.Desktop.Framework;component\Themes\Default.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Border Background="White" BorderBrush="LightSkyBlue" BorderThickness="2">
<StackPanel Orientation="Vertical" >
<TextBlock FontSize="20" FontWeight="Medium" Margin="20">
Sie haben den letzten Zielpunkt erreicht.
</TextBlock>
</StackPanel>
</Border>
</Grid>
</UserControl>
Do you have any suggestion how I can make the overlay appear?
To add the overlay control to the active map you can use this method: MapView.AddOverlayControl. This method takes a MapViewOverlayControl as its parameter.
public void AddOverlayControl(IMapViewOverlayControl overlayControl)
Let me know if this works, otherwise I will create a sample snippet.
Hi @Wolf,
I added my relevant code under the first answer above. From my point of view that looks like what the documentation links contain.
We are at SDK version 2.4 and Pro version 2.4.1, due to customer decisions. Makes this a difference? Is my thread/task management the problem?
I will try to get this to work with 2.4.1.
Here's an existing overlay control snippet that may help as well:
https://github.com/esri/arcgis-pro-sdk/wiki/ProSnippets-MapExploration#mapview-overlay-control
I was able to get this to work but I had to overcome a couple of problems. The main problem was indeed thread related.
The MapViewOverlayControl needs to be created on the main/UI thread. Furthermore the AddOverlayControl and RemoveOverlayControl methods need to be called on the same thread where the control has been created.
My solution is now a little heavy. I create the control when my map view is initialized.
_courseEndOverlay =
new MapViewOverlayControl(new CourseEndMessageControlView(), true, true,
true, OverlayControlRelativePosition.Center, 0.5, 0.5);
It sends a message from inner thread to outer with delegate callback but then I'm still not exactly on the same thread as my control ... so I need
System.Windows.Application.Current.Dispatcher.Invoke((Action)delegate {
_logger.Debug("show course end control");
MapView.Active.AddOverlayControl(_courseEndOverlay);
});
await QueuedTask.Run(() => {
Task.Delay(4000).Wait();
System.Windows.Application.Current.Dispatcher.Invoke((Action)delegate {
MapView.Active.RemoveOverlayControl(_courseEndOverlay);
});
});
You can use the following code to run actions on the UI thread - it is optimized as it first determines the 'thread context' the caller is running on:
/// <summary>
/// utility function to enable an action to run on the UI thread (if not already)
/// </summary>
/// <param name="action">the action to execute</param>
/// <returns></returns>
internal static void RunOnUiThread(Action action)
{
try
{
if (IsOnUiThread)
action();
else
Application.Current.Dispatcher.BeginInvoke(action);
}
catch (Exception ex)
{
MessageBox.Show($@"Error in OpenAndActivateMap: {ex.Message}");
}
}
/// <summary>
/// Determines whether the calling thread is the thread associated with this
/// System.Windows.Threading.Dispatcher, the UI thread.
///
/// If called from a View model test it always returns true.
/// </summary>
public static bool IsOnUiThread => ArcGIS.Desktop.Framework.FrameworkApplication.TestMode || System.Windows.Application.Current.Dispatcher.CheckAccess();
You can then 'invoke' your UI actions like this:
// simple action
RunOnUiThread(() => { /* UI action functionality */ });
// action with await
RunOnUiThread(async () => { /* UI action functionality with await */ });
I think in most cases you should refrain from using Dispatcher.Invoke because it's a blocking call (meaning it doesn't return to you until it has been executed), instead you should use Dispatcher.BeginInvoke which is non blocking.
Also there's another issue I noticed as the following code snippet with block the 'main CIM thread' for 4 seconds:
await QueuedTask.Run(() => {
Task.Delay(4000).Wait();
// delayed action
});
So I would recommend to run a 'delay' from an asynchronous task using something like this (this is a Pro button's OnClick method):
internal class TestDelay : Button
{
protected override void OnClick()
{
_ = DelayTaskAsync();
System.Diagnostics.Debug.WriteLine("OnClick done");
}
static async Task DelayTaskAsync()
{
Task delay = Task.Delay(5000);
await delay;
// do your delayed action here
System.Diagnostics.Debug.WriteLine("Delayed action");
}
}
This will not block the UI nor the CIM thread.
Very helpful @Wolf , thank you!