Click here to Skip to main content
15,063,287 members
Please Sign up or sign in to vote.
5.00/5 (2 votes)
See more:
I am re-posting my question from Stack Overflow here for more exposure.



I need to move several external application windows at the same time (together) as a group to simulate scrolling functionality.

If your first thought is "why would you want to do that" or "you should never do that", just pretend that it's a good idea and would be fantastic.

So far I have tried the PInvoke methods MoveWindow, SetWindowPos, and (BeginDeferWindowPos, DeferWindowPos, EndDeferWindowPos).

All of these methods produce about the same performance. Moving one window is super snappy, two windows is no big deal, but once I try to move three or more windows things start to get ugly. Here is a snippet using the deferred method to show you how I am doing things and keeping track of performance.
C#
private void multiMoveWindows(int moveAmt, int startingIndex)
{
    int tempX;
    int howmanyTimes = 0;
    int numOfWidgetsToMove = 0;
    int moveAmtRemaining = moveAmt;
    IntPtr MultiWindowStructure;
    System.Diagnostics.Stopwatch t = new Stopwatch();            
    WidgetTracker[] tmp_WhichWidgets = new WidgetTracker[10];           

    foreach (KeyValuePair<string, WidgetTracker> entry in m_dictWT)
    {
        WidgetTracker tmp = new WidgetTracker();
        tmp = entry.Value;
        if (tmp.WIndex >= startingIndex)
        {
            tmp_WhichWidgets[numOfWidgetsToMove] = tmp;
            ++numOfWidgetsToMove;
        }
    }

    for (int i = 0; i < moveAmtRemaining; i++)  
    {
    MultiWindowStructure = Native_Methods.BeginDeferWindowPos(numOfWidgetsToMove);
        foreach (WidgetTracker tmp in tmp_WhichWidgets)
        {
            if (tmp != null)
            {
                tmp.surveilWidget();
                tempX = tmp.WidgetRectLeft - 1;                    
                MultiWindowStructure = Native_Methods.DeferWindowPos(
                    MultiWindowStructure, tmp.WidgetHandle, HWND.NOTOPMOST, tempX, tmp.WidgetRectTop, tmp.WidgetWidth, tmp.WidgetHeight, SWP.NOREDRAW);
                howmanyTimes++;
            }
        }
        t.Start();
        Native_Methods.EndDeferWindowPos(MultiWindowStructure);
        t.Stop();             
    }
    MessageBox.Show(howmanyTimes.ToString() + " move requests made." + Environment.NewLine + 
        "Execution of EndDeferWindowPos took " + t.Elapsed.Seconds.ToString() + " sec " + t.Elapsed.Milliseconds.ToString() + " ms." );    
}



The result for moving 4 windows, 320px left, 1px at a time?
> 1280 move requests made in 14.445 sec.

As you can imagine, waiting 14.5 seconds to see 4 windows make their way across the screen 320 pixels is painful to watch.
- How can this be accomplished in a fluid manner?

Yes, I realize that I can move more than 1px at a time, but that won't resolve the underlying issue. The way I understand it, using any of the PInvoke methods to move windows posts messages to the WinAPI message pump (WndProc()) and waits for the message to be closed / caught / handled before posting the next message. I think this is where I am spending that 14.5 seconds - waiting for messages to be handled.

I have tried using SWP ASYNCWINDOWPOS and the result is horrendous. Each window only gets a few dozen of the messages seemingly at random so that windows are overlapping and as a whole the train barely moves.

The closest I've come to a solution is to assign each "widget" a parent (for example a WindowsFormsHost + an MDI window) and putting them in a form/window and then scrolling that form/window. However I have several reasons for not wanting to do that.
Please help!
Posted
Comments
Ron Beyer 23-Jan-14 11:27am
   
Are these windows that are owned by your application, or windows that are from other software? Judging by your code you are displaying video cameras or something like a video wall, correct?
Okuma.Scott 23-Jan-14 11:57am
   
The windows are processes that I have started, and they aren't owned by the application.
Their content is whatever developers for the platform can dream up.
TnTinMn 23-Jan-14 12:56pm
   
Just an observation and guess.

hWndInsertAfter is set to HWND_NOTOPMOST, does setting the SWP_NOZORDER flag help the performance? It may eliminate some z-order checking and the determination if the window is topmost or not.
Okuma.Scott 23-Jan-14 13:25pm
   
There is no discernible difference when using NOZORDER or NOOWNERZORDER.
TnTinMn 23-Jan-14 15:02pm
   
Darn!, worth a shot though.

What is the surveilWidget method? I'm guessing that it is a call to GetWindowRect. If so,I would think that this could be called at the start of the movemovement and the position updated in your code by adding the requested movement. That would be one less interop call per window on each iteration. May not be much gain though.

Edit: Might as well set the SWP_NOSIZE and SWP_NOACTIVATE flags as well.
Okuma.Scott 23-Jan-14 15:35pm
   
You are correct about my surveilWidget method.
I've modified my DeferWindowPos call to this:

MultiWindowInitialStructure = Native_Methods.DeferWindowPos(
MultiWindowInitialStructure, tmp.WidgetHandle, HWND.NOTOPMOST, tempX, tmp.WidgetRectTop, tmp.WidgetWidth, tmp.WidgetHeight, SWP.NOREDRAW | SWP.NOZORDER | SWP.NOACTIVATE | SWP.NOSIZE);

Lo and behold it IS faster! But it still takes 8.7 seconds and looks pretty bad. I'm currently working on a time-dependent moving function that will vary the move amount. that combined with this might give me something acceptable, even if it flickers. Combining that with a message to freeze the window refresh might provide a workable solution. I'll let you know, but PLEASE give advice if you've done this before or know a better way! Thanks!
TnTinMn 23-Jan-14 19:02pm
   
Something is amiss here!

This interested me; so I hacked out a version of what you are doing and it works fast and looks good. I ran it on both Vista/32 and Win8.1/64 with no issues and the Vista machine is an old laptop that runs like molasses at the North Pole. :)

I defined my variables a bit different than you did, but I think you will comprehend the meaning
IntPtr windowpositionstructure = default(IntPtr);
for (Int32 i = 1; i <= 300; i++)
{
windowpositionstructure = BeginDeferWindowPos(Windows.Count);
foreach (Window w in Windows)
{
if (windowpositionstructure != IntPtr.Zero)
{
windowpositionstructure = DeferWindowPos(windowpositionstructure,
w.hwnd,
IntPtr.Zero,
w.pos.X + 1,
w.pos.Y,
0, 0,
DeferWindowFlags.NOACTIVATE |
DeferWindowFlags.NOSIZE |
DeferWindowFlags.NOZORDER);
w.UpdatePosition();
}
}
bool res = false;
if (windowpositionstructure != IntPtr.Zero)
{
res = EndDeferWindowPos(windowpositionstructure);
System.Threading.Thread.Sleep(1); // needed to slow it down a bit
}
}
Since I am not changing z-order, I passed it IntPtr.Zero for hWndInsertAfter and since the size is not changing I dive it 0 for cx and cy). My Window class has a Point structure to store its position.

Note that I did not disable ReDraw.
Okuma.Scott 24-Jan-14 7:32am
   
How many windows are you moving and what type of window?

I changed my hWndInsertAfter to IntPtr.zero and took out noredraw, and performance has gone down. It takes about 2 seconds longer to execute a the move for 4 windows. I couldn't hardly believe that it works so great for you so I tried using notepad windows to test with... Bingo. The same operation with 4 notepad windows takes .6 seconds.

The test app I was using was a WPF application that has some bouncy animation going on as well as a read-out of the time including milliseconds, oh and a gradient background.

This seems to be the REAL issue and not the code.

TnTinMn 24-Jan-14 9:25am
   
I was using 4 "calc.exe" instances. I just changed to using one Windows Media Player with video playing and three "calc.exe" instances. That also worked fine.

So I guess that WPF is just a pig with updating. You could also try setting the SWP_NOSENDCHANGING flag to prevent the window from receiving the WM_WINDOWPOSCHANGING message. It may be that WPF does a bunch of stuff on a move.

I have not tried this, but I wonder if you sent the application the WM_SETREDRAW message to disable redrawing before moving it would work? A window does not need to process this message.

Anyway, it sounds like you are focused in on the true problem.



Okuma.Scott 24-Jan-14 9:40am
   
Thanks for your help, you inspired me to try other test windows and helped steer my sails back into the wind.

Oh and I tried adding SWP_NOSENDCHANGING but that prevents the windows from moving at all!
BillWoodruff 23-Jan-14 14:16pm
   
+5 A question with real "depth," and the OP is responding in depth to all comments.

1 solution

1. same question Ron Beyer asked you: please define 'external application windows' in this context precisely.

2. is this a WindowsForms project ? if not, what's the context: WPF ? ... C++ ?

3. is there one-and-only-one window that if moved moved all others ? if more than one window can move all other windows, then you have possible recursion to deal with.

4. have you done anything to try and suspend internal refresh of windows being moved as a result of another window being moved ?

5. assuming only one Window can be moved and cause the other windows to "follow" it: is it a requirement that you "track" the move by the user's interaction with the TitleBar ... or, is some action in the Window okay: like click-dragging some graphic element, or click-dragging in the active window area ?

6. are you using a GlobalHook now; if so, what's the source of the code: CP ?
   
v2
Comments
Okuma.Scott 23-Jan-14 12:34pm
   
1: The external apps are any executable program. For example, notepad.exe.

2: I am doing prototyping with both Winforms and WPF. attaching these external processes to parents works in both contexts but each has different flaws, which is why I am trying to do it externally using the WinAPI (which can be done easily using either context). Also the primary language I'm using is C# as you can see from the snippet.

3: No. The main problem I'm trying to solve now is this scenario: the user has 4 widgets open, and they decide to close the first one (index 0, on the left side). Now the remaining widgets need to collapse to fill the space that has opened ie. they all need to move to the left the number of pixels that has opened up. I have the logic worked out, its actually MOVING the windows that is not working as expected.

4: Not yet, although I have thought about it. "Freezing" the content of the moving windows would be acceptable but I have yet to implement the necessary messages. Therefore I don't know how it would effect the performance.

5: Refer to scenario in point 3. This is something which will come later, after I have a solution to this problem. Eventually I will need to catch move messages from title bars.

6: Refer to point 4: no, but it will be a requirement.
BillWoodruff 23-Jan-14 12:53pm
   
I appreciate your detailed responses (a refreshingly rare event here on CP QA).

The issue, #3, of "collapsing to fill space" I interpret as: you are performing a type of tiling.

This is a "deep" problem requiring using the WinAPI, and it's a type of problem I've never worked on. I have created effective ... and rapid ... movement synchronization between multiple independent Forms in Win Forms, but I used mouse-down-and-drag on a UI element on the Forms, which did not require I use WinAPI.

Wish I could help you further; hopefully, with your scenario now more detailed, you'll get other helpful responses.

At the point you are ready to use a GlobalHook, I suggest you try out George Mamaladze's code here on CP:

http://www.codeproject.com/KB/cs/globalhook/globalhook2_src.zip

Here's a recent discussion of using George's code for another purpose:

http://www.codeproject.com/Answers/712448/Global-keyboard-hook-e-Handled-equals-true-does-no
Okuma.Scott 23-Jan-14 13:14pm
   
You are correct about tiling.
Thank you for taking the time to read my question and the information you've provided!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900