TableListBox drag image stops working


#1

I don’t know what causes it, but very often when I drag from my TableListBox, the drag image doesn’t appear. This happened with the default Juce createSnapShotOfSelectedRows(), and still happens with my own custom image. I’m not doing anything special.

It seems to happen often, after I bring up a dialogue. For example, if I bring up a modal window with the AudioDeviceSelectorComponent, or if I bring up the native FileChooser, 9 times out of 10 the drag image stops appearing when I drag from my TableListBox.

When I first launch my app, it always works. The drag image always appears (either my custom one, or the default snapshot of the selected rows). But at some point, especially if I muck with the audio hardware, the image stops appearing.

This problem has existed for a while but I didn’t want to say anything because I know you’re gonna think I’m nuts! But I can’t seem to figure it out, anyone else have this problem?

I tried it in the demo and I couldn’t get it to stop working.


#2

NO responses? This is still happening! I have tracked it down:

// DragAndDropContainer::startDragging (...)

        if (allowDraggingToExternalWindows)
        {
            if (! Desktop::canUseSemiTransparentWindows())
                dragImageComponent->setOpaque (true);

            dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks
                                               | ComponentPeer::windowIsTemporary
                                               | ComponentPeer::windowIgnoresKeyPresses);
        }
        else
            thisComp->addChildComponent (dragImageComponent);

I’m setting allowDraggingToExternalWindows==true and I think that with recent changes to addToDesktop(), the drag window isn’t making it to the front or perhaps not becoming visible.

In the place where I originate the drag, i changed it to pass allowDraggingToExternalWindows==false and this problem magically went away!

So…this problem still exists (when allowDraggingToExternalWindows==true), it has something to do with the drag image going into a ComponentPeer added to the desktop, and it usually manifests itself after bringing another ComponentPeer dialog up. For example, an audio settings dialog, an about box, an AlertWindow, a FileBrowser, etc…

Happens under Windows, likely platform specific, can’t confirm anything on other platforms.


#3

It must be deciding that you’ve dragged the mouse outside the juce window, at which point it switches from “internal” to “external” drag mode (and in “external” mode you just get the normal system drag cursor.


#4

Hmm…I don’t know about this. It very consistently happens AFTER bringing up another dialog in my app. For example, the AudioDeviceSelector Component. It seems the drag image works properly, but as soon as I pop a dialog and dismiss it, then the drag image disappears.

This only happens when allowDraggingToExternalWindows==true. It’s got nothing to do with the TableListBox, this happens with any call to DragAndDropContainer::startDragging.

Unfortunately there is no API for telling a ListBox to pass false for allowDraggingtoExternalWindows in ListBox::startDragAndDrop().


#5

I guess it needs some way of passing that info to the ListBox, I’ll have a look.


#6

Sorry, I was talking crap, it doesn’t work like that at all. Ignore my comment about external/internal drags.

No idea what’s happening then! If you can reproduce it in the demo, let me know.


#7

I tried, and no luck. I will try again, this time changing the demo window to be more similar to my app (native titlebar, etc)


#8

Still can’t seem to reproduce this reliably but here’s one more piece of information. Randomly, when I run my app and try to bring up a FileBrowser dialog (I use the native one), my app goes into a modal state but the FileBrowser doesn’t appear. Juce is acting like the window is up, my app window is disabled and what not, but the FileBrowser never shows up on the desktop or the list of active window icons.

I should point out that my Juce window is running at high coordinates (something like getBounds().left = 3000) since I’m on a two-display setup.

Also this is under Windows and likely, a platform specific issue.


#9

Hmm, nothing springs to mind… I don’t think the coordinates would make any difference.


#10

Wow dude… I think I figured it out! Well how to reproduce it. My multithreaded app creates a CallbackMessage from a background thread and posts it. This causes one of my Components to call repaint() from the message thread. When I start the operation that creates repeated CallbackMessages, the drag image stops functioning. When this background operation completes, and the CallbackMessages stop coming in, the drag image magically starts working / appears (even while I am in the middle of a non-functioning drag, if the background operation finishes, the drag image suddenly appears during the mouseDown / mouseDrag gesture).

Now the crappy part is that during audio playback, I am continually creating CallbackMessage from my audio thread to inform the Gui to repaint various Components like level meters and all that good stuff. So of course, the drag image is completely broken during audio playback.

This only happens when allowDraggingToOtherJuceWindows == true.

So here is my code that processes the CallbackMessage:

namespace {

//
// CallbackMessage posted to Ui / message thread that processes all asychronous
// operations on the Ui thread via m_queue.Process();
//
struct ProcessThreadQueueCallbackMessage : CallbackMessage
{
  ProcessThreadQueueCallbackMessage (Vf::ThreadQueue& queue)
    : m_queue (queue)
  {
  }

  void messageCallback()
  {
    m_queue.Process();
  }

private:
  Vf::ThreadQueue& m_queue;
};

}

// Called from various threads to signal the Ui ThreadQueue to process
void CoreView::processUiThreadQueue()
{
  ProcessThreadQueueCallbackMessage* m =
    new ProcessThreadQueueCallbackMessage (m_uiThreadQueue);
  m->post();
}

And here is an example of a function that might be called during m_queue.Process()

void AudioDataView::onMoreDataAvailable()
{
  repaint(); // just redraw everything
}

Here is my sequence of operations that makes the drag image (when allowDraggingToOtherJuceWindows==true, always the case for ListBox):

  • Start a background thread operation
  • Background thread operation is continuously generating CallbackMessages from another thread
  • CallbackMessages get processed on the Ui thread, leading to repeated calls to AudioDataView::onMoreDataAvailable()
  • While these CallbackMessages are happening, the drag image does not appear
  • As soon as the background thread stops generating CallbackMessages, the drag image pops into view / works

#11

Also this seems to have nothing to do with native title bars I changed my app to use a Juce title bar and it still happens.


#12

This might have something to do with WM_PAINT messages getting prioritized by the system? Hmm this seems the likely answer because it is consistent with the behavior observed with allowDraggingToOtherJuceWindows == true / false.


#13

This fixes the problem:

void DragImageComponent::updateLocation (const bool canDoExternalDrag, const Point<int>& screenPos)
{
        //...

        ComponentPeer* peer = getPeer();
        if (peer)
          peer->performAnyPendingRepaintsNow();
}

Not a very satisfying solution but this provides a clue into the nature of the problem. Seems WM_PAINT is not getting to the drag window when its a separate HWND (allowDraggingToExternalWindows== true) because I am continually calling Component::repaint() on some widgets via a CallbackMessage.


#14

Maybe just try using an AsyncUpdater rather than continuously bombarding the message queue with callbacks?


#15

From juce_AsyncUpdater.h:

If an update callback is already pending but hasn't happened yet, calls to this method will be ignored.

I already have the equivalent functionality in my core code - a new CallbackMessage is not produced until the old one is known to have been processed. So there is no bombardment, only one is outstanding at any given time.

But I will give AsyncUpdater a whirl and see how it goes.


#16

As I thought… juce_AsyncUpdater.cpp largely resembles what I have already done in my own ThreadQueue object, although it seems to work a little bit better, but even using an AsyncUpdater, the drag image disappears when I have a lot of repainting going on (for example, if I have two streams playing back and redrawing on screen).

This is definitely a problem with WM_PAINT being lower priority - the drag image component heavyweight peer has a lower priority on its WM_PAINT than the main Component window and as such, never gets drawn during playback :frowning:


#17

I’m quite surprised that wm_paint would never gets called, even if the message queue is really busy. Not really sure what I could suggest as an elegant solution…


#18

One solution that I could implement from the client side would be, for every handleAsyncUpdate(), go through the Desktop singleton and call performAnyPendingRepaintsNow() for every TopLevelWindow…yeah it stinks but I dislike not seeing the drag image more.

We still need to give ListBox an API for allowing a caller to determine if allowDraggingToExternalWindows will be true or false for the drag.


#19

Well this fixed it. Maybe not the most elegant solution but like you said nothing obvious comes to mind:

#if JUCE_WIN32
static void updateAllTopLevelWindows ()
{
  int n = TopLevelWindow::getNumTopLevelWindows();
  for (int i = 0; i < n; ++i)
  {
    TopLevelWindow* w = TopLevelWindow::getTopLevelWindow (i);
    ComponentPeer* peer = w->getPeer ();
    if (peer)
      peer->performAnyPendingRepaintsNow ();
  }
}
#endif

void CoreView::handleAsyncUpdate ()
{
  m_uiThreadQueue.Process();

#if JUCE_WIN32
  // Fix for Windows WM_PAINT which is low priority for some windows
  updateAllTopLevelWindows ();
#endif
}

#20

I’ve had a similar issue in the past, and simply added a manual reset WaitableEvent that I signal in the paint() method.
If I need to send an async message, I check the event, if it’s not set, I wait until it get signalled (or abort sending the message).
If it’s signalled, I reset it, and send my message.

This works for a single message-sender however.