undo strategy for MouseWheel actions

I have my own undo engine that works pretty well. I’ve used it in many applications.

However, I’ve never needed to implement an undo for the MouseWheel event, and I’m having trouble coming up with a good way to handle it.

With mouse dragging, things are easy, because there are “start” and “stop” events : MouseDown and MouseUp. Just store the current value on MouseDown, put it on the undo stack on MouseUp if it has changed. No problem.

With MouseWheel, there are no start or stop events. So … this is where I’m not sure what to do. Calling the undo engine in the event will create a huge slew of events instead of one action. Without definite “start” and “stop” events, I can’t see a good way to implement undo for MouseWheeling a value.

I bet I’m missing something, so I’m asking for your help.

Thanks.

The only thing that came to mind is to put the undo in the MouseWheel event, but in the undo engine, check the timestamp of the proposed undo, and if not enough time has passed, don’t store it. This basically gives me a “stop” event (it’s stored if enough time has passed). But this doesn’t solve the problem of determining when to get the value that should be undone, before the series of MouseWheel events begins, so I don’t have a “start” event.

… similar to what I said above, but keep a timestamp for the MouseWheel event, not the undo, I can get a “start” event by checking a timestamp in the MouseWheel event, and if enough time has passed, then store the value (that’s the “start” event).

But how to get a “stop” event? …

Seems like a catch 22. I know I’m missing something … or I’m just a little confused.

For such events, you want to use a timer to catch the iddle state.

  • Add an iddle as boolean property to the window
  • Add a ModeOff 300 ms timer to the window, with this in its Action event :

Sub Action() iddle = True // Stop event system.DebugLog "iddle" Timer1.mode = Timer.ModeOff End Sub

In MouseWheel :

Function MouseWheel(X As Integer, Y As Integer, DeltaX as Integer, DeltaY as Integer) As Boolean if iddle = true then // Start event system.DebugLog "Start wheel" iddle = False end if Timer1.mode = Timer.ModeOff Timer1.mode = Timer.ModeSingle End Function

The MouseWheel event keeps pushing back the timer. When the user stops moving the wheel, the timer fires.

That way you have both the start of mousewheel, and when the user stops moving it. So you can record the states at both ends. The period of the timer has to be tuned so an average user won’t make false stops. 1/3 a second seems to work here.

YES, that’s it! :slight_smile:

Thanks very much, Michel.

[quote=228219:@Aaron Hunt]YES, that’s it! :slight_smile:

Thanks very much, Michel.[/quote]

You’re welcome.

To make this easier to work with ( and more object oriented ) …

  1. add a computed property to the object requiring the mousewheel undo:

MouseWheel_Idle as Boolean

  1. add 2 event definitions to the object:

MouseWheel_Start
MouseWheel_Stop

  1. in the Set of the MouseWheel_Idle property, put
if value then RaiseEvent MouseWheel_Stop else RaiseEvent MouseWheel_Start

Then use a custom timer and shadowed MouseWheel event definition basically as described above, hidden in the object.

This way you get an object that works normally, but fires MouseWheel_Start and MouseWheel_Stop events.

P.S. I found that 500 ms works much better than 300 ms, allows for multiple wheel motions with very small pauses between them before a “stop” event fires.

Second Postscript:

This implementation works differently on Mac and Windows because of the way the MouseWheel event fires.

I found on Mac I was able to use one Undo object for both dragging and Wheeling the mouse. The strategy was:

  1. MouseDown or MouseWheelStart fires, a new MouseUndo object is created
  2. MouseUp or MouseWheelStop fires, MouseUndo is added to the undo stack (if flags for those actions were thrown)

This works on Mac, and it results in a hard APPCRASH on Windows. The reason appears to be the memory occupied by MouseUndo is corrupted due to the way Windows mouse events work. The MouseUndo object ends up trying to be set and got concurrently on Windows, crashing the app.

The solution is to have 2 mouse undo objects, one for mouse clicking and the other for mouse wheeling.

  1. MouseDown fires, a new MouseUndoDrag object is created
  2. MouseUp fires, MouseUndoDrag is added to the undo stack (if dragging took place)
  3. MouseWheelStart fires, a new MouseUndoWheel object is created
  4. MouseWheelStop fires, MouseUndoWheel is added to the undo stack

This solved the problem.

[quote=229559:@Aaron Hunt]Second Postscript:

This implementation works differently on Mac and Windows because of the way the MouseWheel event fires.

I found on Mac I was able to use one Undo object for both dragging and Wheeling the mouse. The strategy was:

  1. MouseDown or MouseWheelStart fires, a new MouseUndo object is created
  2. MouseUp or MouseWheelStop fires, MouseUndo is added to the undo stack (if flags for those actions were thrown)

This works on Mac, and it results in a hard APPCRASH on Windows. The reason appears to be the memory occupied by MouseUndo is corrupted due to the way Windows mouse events work. The MouseUndo object ends up trying to be set and got concurrently on Windows, crashing the app.

The solution is to have 2 mouse undo objects, one for mouse clicking and the other for mouse wheeling.

  1. MouseDown fires, a new MouseUndoDrag object is created
  2. MouseUp fires, MouseUndoDrag is added to the undo stack (if dragging took place)
  3. MouseWheelStart fires, a new MouseUndoWheel object is created
  4. MouseWheelStop fires, MouseUndoWheel is added to the undo stack

This solved the problem.[/quote]

Please file a bug report so that we can track down the crash.

Thank you, but I think it isn’t a bug in Xojo; it was rather a design flaw in my code. It just so happened that on Mac, the design flaw did not cause a problem, because of the different way mouse events fire on Mac as compared to Windows.