OK, now I have a working DataWindow solution based on a very simplified version of http://anvil-of-time.com/wordpress/powerbuilder/powerbuilder-datawindow-dragdrop-rows-with-business-rules/
I created a user object DW u_drag_dw. It has the DragIcon set to DataPipeline! (Maybe there are better options.)
The DataWindow to be inserted into it has to have a 1-char String column (not displayed) named row_drag, a horizontal line at the very top of the Detail area that is invisible, with a DW expression for Visible that is IF (row_drag = 'T',1,0)
, and a horizontal line at the very bottom of the Detail area that is invisible, with a DW expression for Visible that is IF ( row_drag = 'B',1,0)
. Those lines will appear as part of moving the mouse to show where you are dropping the row.
Declare this Local External Function:
SUBROUTINE SleepMs(ulong milliseconds) LIBRARY "kernel32.dll" Alias for "Sleep"
Declare these Instance variables:
boolean ib_drag, ib_mouse_down
long il_dragged_row, il_mouse_down_x, il_mouse_down_y
Clicked event script:
this.SelectRow(0, FALSE) // unselect all selected rows
this.SelectRow(row, TRUE) // select the clicked row
ib_drag = (row > 0) // are we capable of dragging?
il_dragged_row = row // source row which is dragged (used for visual indicator on dw)
IF ib_drag THEN
ib_mouse_down = TRUE // button is clicked
il_mouse_down_x = xpos // original coordinates of pointer
il_mouse_down_y = ypos
ELSE
ib_mouse_down = FALSE
END IF
Create event ue_dwnmousemove, mapped to pbm_dwnmousemove, with script:
IF ib_drag THEN // we are capable of being dragged
IF ib_mouse_down THEN // mouse is down
IF (Abs(PointerX() - il_mouse_down_x) > 50) OR (Abs(PointerY() - il_mouse_down_y) > 50) OR &
(PointerX() = 0) OR (PointerY() = 0) THEN
// we have moved the pointer more than 50 powerbuilder units so we are dragging the row
Drag(Begin!)
END IF
END IF
END IF
DraggedWithin event script:
// scroll the datawindow if not all row fit
long l_i, ll_firstrow, ll_lastrow
ll_firstrow = long(this.Object.DataWindow.FirstRowOnPage)
ll_lastrow = long(this.Object.DataWindow.LastRowOnPage)
IF (row = ll_firstrow OR row = ll_firstrow + 1) AND ll_firstrow > 1 THEN
this.ScrollPriorRow( )
SleepMS(100) // don't scroll too fast!
ELSEIF (row = ll_lastrow OR row = ll_lastrow - 1) AND ll_lastrow < this.rowcount( ) THEN
this.ScrollNextRow( )
SleepMS(100)
END IF
// set the visual indicator for the drop location, by setting the column value that determines the
// visibility of the lines at the top and bottom of each row
FOR l_i = 1 TO this.RowCount()
this.SetItem(l_i, "row_drag", " ")
NEXT
// set up for visual indicators
IF il_dragged_row > Row THEN
this.SetItem(Row, "row_drag", "T")
IF Row <> 1 THEN
this.SetItem(Row - 1, "row_drag", "B")
END IF
ELSE
this.SetItem(Row, "row_drag", "B")
IF Row <> this.RowCount() THEN
this.SetItem(Row + 1, "row_drag", "T")
END IF
END IF
Create event ue_lbuttonup, mapped to pbm_dwnlbuttonup, with script:
long l_i
ib_Mouse_Down = False
IF ib_drag THEN // are we dragging?
ib_drag = False // stop dragging
This.Drag(End!)
END IF
// reset the visual indicator on the datawindow
FOR l_i = 1 TO this.RowCount()
this.SetItem(l_i, "row_drag", " ")
NEXT
DragDrop event script:
long ll_row, ll_beforerow
if source <> this then return
ll_row = GetSelectedRow(0) // should be same as il_dragged_row
IF ll_row = 0 THEN return
IF ll_row = row THEN return
if row > ll_row then
ll_beforerow = row + 1
else
ll_beforerow = row
end if
RowsMove(ll_row, ll_row, Primary!, this, ll_beforerow, Primary!)
ll_row = GetSelectedRow(0)
this.ScrollToRow(ll_row)
That's it! Place that object on a window, assign it a DataObject as explained above (with the row_drag column and the two lines), and it should just work. The one thing I don't like is that it is a bit flaky about scrolling at the top or bottom, you sometimes have to jiggle the position a bit before it really works. (I added the SleepMS calls to the code in the original article, because otherwise I found the whole DW scrolled at once, all the way to the top or bottom!)