Listbox DragReorderRows does not scroll properly

Anyone found a workaround for <https://xojo.com/issue/1589> ? While drag reordering in a Cocoa app, the drag stalls after a second or two unless you wiggle the mouse.

I think I had solved this somehow once. Try running a background timer, maybe a CarbonTimer from MBS as that may fire when Xojo’s doesn’t.

I just played with a project following the instructions in the bug report. It appears the trick is to maintain the cursor within the Listbox edges, and under the drop line. On my screen that is apparently less than 10 pixels. Not very comfortable. But I did not see any stalling. Maybe the issue is the precision at which one is suppose to keep the pointer, and the wiggling just happens to get out of the boundaries where scrolling stops.

I just experimented quickly with an improvement that lets you drag the mouse cursor down, out of the ListBox, and see the scroll continue. Then to place the dragged row one has to go back inside the listbox. If the button is released, scrolling stops and nothing more happens.

I used a 100 ms timer set to off with this :

Sub Action() If not System.MouseDown then me.Mode = Timer.Modeoff Return end if ListBox1.ScrollPosition = ListBox1.ScrollPosition + 1 End Sub

In ListBox :

Sub DragExit(obj As DragItem, action As Integer) if System.MouseY > Self.Top+me.top+me.Height then Timer1.Mode = Timer.ModeMultiple end if End Sub

Function DragEnter(obj As DragItem, action As Integer) As Boolean Timer1.Mode = Timer.ModeOff End Function

Michel: In my code, it’s a listbox in a container control on a window, both horizontal and vertical scrollbars are enabled. If you drag down into the area of the horizontal scrollbar, the listbox will scroll down a few lines and then stop - if you wiggle the mouse horizontally, it will scroll a little more each time you wiggle. It’s interesting that you aren’t seeing the stalls.

Thomas: interesting ideas about the timer. Does the timer need to do anything or just fire to tickle the event loop?

[quote=226388:@Michael Diehr]Michel: In my code, it’s a listbox in a container control on a window, both horizontal and vertical scrollbars are enabled. If you drag down into the area of the horizontal scrollbar, the listbox will scroll down a few lines and then stop - if you wiggle the mouse horizontally, it will scroll a little more each time you wiggle. It’s interesting that you aren’t seeing the stalls.
[/quote]

Sorry, I built a small project from your description in 2015R3 under El Capitan and I see no issue with having the cursor in the horizontal scrollbar.

Maybe if you could post a sample project ?

The workaround I posted to extend the zone in which scroll takes place could help.

I’ve updated <https://xojo.com/issue/1589> with a demo project

Following Thomas’ idea I came up with this code. It uses DragRow to detect when row dragging begins and starts the timer (DragRow is really for DragItems but is working for this purpose). In the Timer, mouse coordinates are compared and ScrollPosition updated as appropriate.

Although this code is a very very rough proof of concept. getRelXY(), which gets the mouse coordinates relative to the listbox, only works for a Listbox directly on the Window. Detecting when to scroll only uses the Y coordinate, the Timer Period might be too slow or fast and other things that need to be fiddled.

A drawback is that the Insertion line will scroll away from the cursor when the Timer does the scrolling, but the row will get dropped where the cursor is.

[code]Private oldX As Integer
Private oldY As Integer

Sub Open() //lb
for i As integer = 0 to 100
me.AddRow "apples " + Str(i)
next
me.DefaultRowHeight = 50
me.ColumnWidths = “110%”
me.ScrollBarHorizontal = true
End Sub

Function DragRow(drag As DragItem, row As Integer) As Boolean //lb
getRelXY(oldX, oldY)
Timer1.Mode = 2
End Function

Sub Action() //Timer1 (initial Mode=0, Period = 100)

if not System.MouseDown then //stop when mouse up
me.Mode = 0
return
end

dim x, y As integer //get current cursor position
getRelXY(x, y)

if x = oldX and y = oldY then //cursor hasn’t moved
if y < lb.RowHeight / 2 then //near top
lb.ScrollPosition = lb.ScrollPosition - 1
elseif y > lb.Height - lb.RowHeight / 2 then //near bottom
lb.ScrollPosition = lb.ScrollPosition + 1
end
else //update stored position
oldX = x
oldY = y
end

End Sub

Private Sub getRelXY(byref x As integer, byref y As integer)
x = System.MouseX - self.Left - lb.Left
y = System.MouseY - self.Top - lb.Top
End Sub[/code]

An alternative method I’ve done way in the past is to handle all the mousing yourself. Much more work to replicate all the ways of selecting, unselecting, double clicking, editing, drawing insertion line, but then you have full control of reorder behavior.

[quote=226606:@Will Shank]Following Thomas’ idea I came up with this code. It uses DragRow to detect when row dragging begins and starts the timer (DragRow is really for DragItems but is working for this purpose). In the Timer, mouse coordinates are compared and ScrollPosition updated as appropriate.

[/quote]

This is very close to what I posted above. I did not find a way to suppress the going away insertion bar as well. Maybe the way is not to use the built-in DragRow and replace it entirely.

I missed that in the fray. The main difference is I wouldn’t use DragEnter/Exit for controlling the timer since the stuck scrolling manifests inside the listbox bounds.

I thought that would require replicating all mouse interaction but there’s a nice sweet spot trigger. Turn OFF EnableDragReorder and turn ON EnableDrag. I thought these were 2 separate systems but both will trigger DragRow. Returning False in DragRow when only EnableDrag is on is a good place to start Timer tracker and has no default behavior to contend with. All the index positioning, insertion line drawing and scrolling is in your control.

You have to move the row data yourself though. And there’s header and scrollbar heights to factor in, which I didn’t do in this project…

http://trochoid.weebly.com/uploads/6/2/6/4/62644133/lbdrag2.zip
The insertion line drawing is done in CellTextPaint. CellBackgroundPaint would be a better choice but then you’ll also need to draw the selection fill color.

Also, the timer runs at 100fps but scrolling is limited to 20fps, yet scales with distance.

Elegant and easy!

To make Michel’s code work on Windows, the timer had to be a property of the window, and you have to ignore the DragExit’s action value. Then it works great!

I do not consider that standard (or good) UI behavior. I am pretty sure that I used to be able to drag the mouse up and down even to outside of the window and would still get a scrolling effect. But my testing with OSX’s TextEdit shows that this behavior is not more standard in Cocoa. Not sure when it still worked. Was it pre-Cocoa? Or was it pre-OSX, or pre-Mac? Maybe only on Windows? I think I’m getting Alzheimers, mixing up past events with current ones.

My memory says it worked (in TextEdit at least) on 10.9

I agree it should not work that way, reason why I posted a workaround. Since there is a bug report, we can only hope it gets some love eventually.