ComponentMovementWatcher class is not working as expected

Hello guys and @jules and @fabian !

I have an application on Windows 8.1 64 bits where I want to monitor the movement of the main window to move a component that has been added to the Desktop with AlwaysOnTop = true.

The way to do so is to have one of my classes heriting from ComponentMovementWatcher. This way, the top level parent component, my window, is automatically registered by a ComponentListener. By the way, the declaration of the ComponentMovementWatcher must be done as late as possible, which means not in the constructors, because when the components are created, they are not added to their future parent components yet, so the research of the main parent component might be wrong…

So I did all that stuff, and when I move the window, the function ComponentMovementWatcher::componentMovedOrResized that I had to override is properly called, with the argument “moved” == true as expected, but… only once ! Then it is never called anymore when I move the window !

After some research, I have found the guilty function :

void ComponentMovementWatcher::componentMovedOrResized (Component&, bool wasMoved, bool wasResized)
{
    if (component != nullptr)
    {
        if (wasMoved)
        {
            Point<int> newPos;
            Component* const top = component->getTopLevelComponent();

            if (top != component)
                newPos = top->getLocalPoint (component, Point<int>());
            else
                newPos = top->getPosition();

            wasMoved = lastBounds.getPosition() != newPos;
            lastBounds.setPosition (newPos);
        }

        wasResized = (lastBounds.getWidth() != component->getWidth() || lastBounds.getHeight() != component->getHeight());
        lastBounds.setSize (component->getWidth(), component->getHeight());

        if (wasMoved || wasResized)
            componentMovedOrResized (wasMoved, wasResized);
    }
}

I don’t understand what happens in the “if (wasMoved)” section. I can understand that some extra checking is done to see if something has really been moved or resized, even if for me it’s unnecessary at this point, but in this particular code, something that is supposed to be valid becomes not valid anymore. What happens in that code is that newPos = top->getLocalPoint (component, Point()) the first time, which means the relative position of the control put in the argument of the MovementWatcher depending on the position of the window. Then, the result is stored for the next calls. However, that newPos will never change then if the window moves, so all future window movements are going to be discarded !! Indeed the second time, the localBounds position == newPos, and the call will never be made anymore.

Why isn’t newPos just equal to top->getPosition() all the time ?

Note : I can’t init the ComponentMovementWatcher with the control that I want to follow the window either, since it is just a child of the Desktop instance.

So I don’t get that logic, and this prevents my code from working. Could it be possible to explain me what was the point here ? And if there is a mistake, could it be possible to solve it ? That class was supposed to be the key for making a given component monitor the window changes, however with this implementation, it is just impossible to react to window moves.

I would be fine with just newPos = top->getPosition();

Thanks in advance !
Ivan

The logic looks fine to me.

You say you’re trying to watch a top-level component, but if top != component, then it can’t be… And if it’s not a top-level component, then you’ll only be told when it moves relative to its parent, not to the screen.

Hello Jules !

The variable “component” here is the one provided as an argument in the constructor of the ComponentMovementWatcher. As that time, the function “registerWithParentComps” scans the list of parent components of that “component” to register the ComponentListeners up to the top level parent component, which in my case is the DialogWindow. So basically, “component” might be anything that is inside the window, but not the window itself, otherwise the parent component scan wouldn’t make any sense, and that’s what I did in my application. So component != top for me.

Maybe you’re confusing that variable “component” with the variable which should be an argument for the function componentMovedOrResized, but which isn’t here in the code ? If THAT component was compared with “top”, it would make more sense to me, since it’s the one which started the listener calls.

Or maybe what you mean is that my ComponentMovementWatcher constructor should be initialized with the DialogWindow component instead of any component in the window like I did to work properly ? In that case wouldn’t that be in contradiction with the class documentation, saying I quote “An object that watches for any movement of a component or any of its parent components.” ? Indeed, the only way to watch for the top level parent component movement of any component here would be… to watch for the movement of a component which has no parent component.

Would it make more sense ?

void CustomComponentMovementWatcher::componentMovedOrResized(Component& componentThatHasChanged, bool wasMoved, bool wasResized)
{
    if (&componentThatHasChanged != nullptr)
    {
        if (wasMoved)
        {
            Point<int> newPos;
            Component* const top = componentThatHasChanged.getTopLevelComponent();

            if (top != &componentThatHasChanged)
                newPos = top->getLocalPoint (&componentThatHasChanged, Point<int>());
            else
                newPos = top->getPosition();

            wasMoved = lastBounds.getPosition() != newPos;
            lastBounds.setPosition(newPos);
        }

        wasResized = (lastBounds.getWidth() != component->getWidth() || lastBounds.getHeight() != component->getHeight());
        lastBounds.setSize(component->getWidth(), component->getHeight());

        if (wasMoved || wasResized)
            componentMovedOrResized(wasMoved, wasResized);
    }
}

Yes, that’s the idea. I think you’ve just misunderstood the purpose of it… I agree that the description isn’t quite clear.

The only thing it watches is the target component, and it will only tell you when that component is moved relative to either the screen (if it is itself a window) or the top-level comp, if it’s embedded at some arbitrary depth within a window.

It watches other intermediate parent comps, but only so that it can find out internally whether those moves have resulted in the movement of your target comp.

(TBH it’s quite hard to explain this, I’m not sure I even explained it very well here either…)

OK :slight_smile:

So I need to initialize ComponentMovementWatcher in my example with any of my component->getTopLevelComponent() I guess. I look forward to the clarification in the documentation :wink: