I recently started doing some custom map editing development and have a tool where I want to show some overlay graphics as the user moves their mouse around. The overlay stuff works well but if I just redraw on every mouse move event, it takes too long to process the redraw and it lags behind the mouse cursor. I saw there's a DelayedInvoker class that sounds like it's meant to support these kinds of situations but the documentation is very light and I don't see any examples of how it would be used. For now, I implemented my own method where I push all coordinates from the mouse move even to a stack and then once a redraw is done, it can pop the most recent coordinate from that stack and remove/ignore any points it wasn't able to process while it was drawing. It works well but if there's already something that is able to provide a similar function, I would love to use that instead. Does anyone have any examples of using the DelayedInvoker for mouse move events?
Solved! Go to Solution.
The pattern looks something like this but I dont think it is going to give u the results you are hoping for:
internal class MapTool1 : MapTool {
private DelayedInvoker _invoker = new DelayedInvoker(50);
...
protected async override void OnToolMouseMove(MapViewMouseEventArgs e){
//do something with the invoker
_invoker.InvokeTask(() => {
return QueuedTask.Run(() => {
var mpt = MapView.Active.ClientToMap(e.ClientPoint);
//Do something with the point
});
Basically, just like a "QueuedTask.Run(() => { ........... })" you can wrap whatever u want to be invoked with the delay (50ms in this case) within the "_invoker.InvokeTask" lambda. So, in this case, if I am tracking the cursor position w/ the invoker I will be "processing" its position every 50ms (or whatever delay u specify) which will be quite jittery and so on.
In the Pro SDK are a number of samples where we implement logic to cut down on unnecessary mouse movement - just search for "MouseMove" and you will see them - they are all using the same, basic pattern and are updating the graphics overlay in different ways.
https://github.com/search?q=repo%3AEsri%2Farcgis-pro-sdk-community-samples%20mousemove&type=code
I suggest using something built around the pattern they are using rather than the delayed invoker.
Another way, that I have used from time to time, is this basic pattern:
internal class SelectOnMove1 : MapTool
{
private Point _lastLocation;
private int _deltaPixels = 0;
protected override Task OnToolActivateAsync(bool active) {
if (_deltaPixels == 0)
//usually 3 pixels - use whatever number u want
_deltaPixels = SelectionEnvironment.SelectionTolerance;
protected async override void OnToolMouseMove(MapViewMouseEventArgs e) {
//compare current cursor position to last (captured) position
if (_lastLocation.X >= e.ClientPoint.X - _deltaPixels &&
_lastLocation.X <= e.ClientPoint.X + _deltaPixels &&
_lastLocation.Y >= e.ClientPoint.Y - _deltaPixels &&
_lastLocation.X <= e.ClientPoint.X + _deltaPixels)
return;//within tolerance - ignore
_lastLocation = e.ClientPoint; //we'll use this one
//TODO - use the point
However, it is going to be v difficult to get truly smooth tracking and it will always be a little laggy/jittery. Your mileage will vary.
The pattern looks something like this but I dont think it is going to give u the results you are hoping for:
internal class MapTool1 : MapTool {
private DelayedInvoker _invoker = new DelayedInvoker(50);
...
protected async override void OnToolMouseMove(MapViewMouseEventArgs e){
//do something with the invoker
_invoker.InvokeTask(() => {
return QueuedTask.Run(() => {
var mpt = MapView.Active.ClientToMap(e.ClientPoint);
//Do something with the point
});
Basically, just like a "QueuedTask.Run(() => { ........... })" you can wrap whatever u want to be invoked with the delay (50ms in this case) within the "_invoker.InvokeTask" lambda. So, in this case, if I am tracking the cursor position w/ the invoker I will be "processing" its position every 50ms (or whatever delay u specify) which will be quite jittery and so on.
In the Pro SDK are a number of samples where we implement logic to cut down on unnecessary mouse movement - just search for "MouseMove" and you will see them - they are all using the same, basic pattern and are updating the graphics overlay in different ways.
https://github.com/search?q=repo%3AEsri%2Farcgis-pro-sdk-community-samples%20mousemove&type=code
I suggest using something built around the pattern they are using rather than the delayed invoker.
Another way, that I have used from time to time, is this basic pattern:
internal class SelectOnMove1 : MapTool
{
private Point _lastLocation;
private int _deltaPixels = 0;
protected override Task OnToolActivateAsync(bool active) {
if (_deltaPixels == 0)
//usually 3 pixels - use whatever number u want
_deltaPixels = SelectionEnvironment.SelectionTolerance;
protected async override void OnToolMouseMove(MapViewMouseEventArgs e) {
//compare current cursor position to last (captured) position
if (_lastLocation.X >= e.ClientPoint.X - _deltaPixels &&
_lastLocation.X <= e.ClientPoint.X + _deltaPixels &&
_lastLocation.Y >= e.ClientPoint.Y - _deltaPixels &&
_lastLocation.X <= e.ClientPoint.X + _deltaPixels)
return;//within tolerance - ignore
_lastLocation = e.ClientPoint; //we'll use this one
//TODO - use the point
However, it is going to be v difficult to get truly smooth tracking and it will always be a little laggy/jittery. Your mileage will vary.
Thanks, this is very helpful. Since the documentation on DelayedInvoke doesn't give much information, I wasn't sure if it was the right thing to use or not. Your description and example help a lot. I was just reviewing some of the MouseMove examples in the ProSDK examples. Not sure how I missed or forgot those when I was working on this. It seems what I did is similar to those examples so I think I'm good. The redraw is pretty quick smooth on my system. Although we'll see how it performs on slower VDIs and such where performance can be lacking.