Link to home
Start Free TrialLog in
Avatar of Christian de Bellefeuille
Christian de BellefeuilleFlag for Canada

asked on

Title bar not drawn on Windows 7

We are developing an application which takes a snapshot of a screen.  We use a function from Windows API named "GetWindowDC" to get the handle of the application and we use a BitBlt to copy it.

We use GetWindowRect to get get the size of the window and ClientToScreen to get it's position.
We need to render the image from snapshots of the applications currently running, so the some application could be hide in the process.

The problem that we have is that the titlebar and borders of the application are not drawn properly.  Instead of having them, it put some crap in arround our snapshot where the titlebar should appear.

P.S: We are using Windows 7.

We tried it on Windows XP and while we are getting the title bar and the borders, we are also getting a part that shoudn't be there. The region taken is too large.

Anyone got an idea, a solution?  You can see the result bellow.

Thanks

 User generated image
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

*Instead of a screenshot, next time post your snippet as actual code so people can read it easier.  =)

You're using:

    dc2 = GetDC(GetDesktopWindow());

Which indicates you want to copy directly from the screen.

Instead of GetDC(), GetDesktopWindow(), and BitBlt(), why not simply use Graphics.CopyFromScreen()?
http://msdn.microsoft.com/en-us/library/6yfzc507.aspx

Simplified, it might look like:
private void button1_Click(object sender, EventArgs e)
        {
            Size c = SystemInformation.PrimaryMonitorSize;
            Bitmap bmp = new Bitmap(c.Width, c.Height);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.CopyFromScreen(new Point(0, 0), new Point(0, 0), c);
            }

            // ... do something with "bmp" ...
            pictureBox1.Image = bmp;
        }

Open in new window


Obviously that snippet will capture the entire screen.

If you want to capture just a specific window based on its Handle, then you are correct to start with GetWindowRect().  Remember, though, that the values returned by this API are ALREADY in screen coordinates so ClientToScreen() isn't needed.

Here's an example of capturing just the area covered by a Notepad instance:
public partial class Form1 : Form
    {

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            RECT rcAPI;
            Process[] ps = Process.GetProcessesByName("notepad");
            if (ps.Length > 0)
            {
                GetWindowRect(ps[0].MainWindowHandle, out rcAPI);
                Rectangle rc = new Rectangle(rcAPI.Left, rcAPI.Top, (rcAPI.Right - rcAPI.Left) + 1, (rcAPI.Bottom - rcAPI.Top) + 1);

                Bitmap bmp = new Bitmap(rc.Width, rc.Height);
                using (Graphics g = Graphics.FromImage(bmp))
                {
                    g.CopyFromScreen(rc.Location, new Point(0, 0), rc.Size);
                }

                // ... do something with "bmp" ...
                pictureBox1.Image = bmp;
            }
        }
    }

Open in new window

Avatar of Christian de Bellefeuille

ASKER

I don't use Graphics.CopyFromScreen because it's actually slower than calling Windows' API (more than twice slower) and it's not what I'm actually performing. The code you see on the snapshot only retrieve the whole screen. I'm trying to get only the application snapshot and this is the second reason I'm using Bitblt. Here's the code I'm using to get the application snapshot so far...

static public Bitmap FormSnapShot()
        {
            Size c = SystemInformation.PrimaryMonitorSize;
            Bitmap bmp;
            IntPtr dc1;
            IntPtr dc2;
            Graphics g;

            bmp = new Bitmap(c.Width, c.Height);
            g = Graphics.FromImage(bmp);
            //Retrieve all the processes that have a MainWindowHandle
            IntPtr[] tabHandle = Procslst.listProcHandle();
            //So I can see the blank...
            g.FillRectangle(Brushes.Aqua,0,0,c.Width,c.Height);
            dc1 = g.GetHdc();

            Rectangle irect;
            //dc2 = GetDC(GetDesktopWindow());

            //BitBlt(dc1, 0, 0, c.Width, c.Height, dc2, 0, 0, SRCCOPY);

            //ReleaseDC(GetDesktopWindow(), dc2);
            //Draws all the windows on my image
            foreach (IntPtr ihandle in tabHandle)
            {
                dc2 = GetWindowDC(ihandle);

                irect = getWindowBound(ihandle);
                BitBlt(dc1, irect.Left, irect.Top, irect.Width, irect.Height, dc2, 0, 0, SRCCOPY);
                ReleaseDC(ihandle, dc2);
            }
            g.ReleaseHdc(dc1);
            
            g.Dispose();

            return bmp;
        }

Open in new window


Although you didn't provide the solution, thanks for showing me how to use the rect the right way... I was always wandering why my rect.{x,y} were always 0,0.
Have you got it working correctly now then?...or do you still need help?
Well the title bar and the borders are still not drawn. I need to know how can I get them to be drawn other than by taking a snapshot of the whole screen (application by application).
I think line #29 is wrong:

    BitBlt(dc1, irect.Left, irect.Top, irect.Width, irect.Height, dc2, 0, 0, SRCCOPY);

The coords passed in should be CLIENT coords because you are getting a DC to a specific window.

Try this instead:

    BitBlt(dc1, 0, 0, irect.Width, irect.Height, dc2, 0, 0, SRCCOPY);

*If you were using the DC of the desktop itself then you'd need the screen coords of the window.
The images are draw at the right place. I tested your code and all the applications are draw on the upper left corner. I forgot to comment my code a bit but line 29 copies the DC into the DC of an image the size of the screen. I hope it helps you helping me. The only problem we have is that the title bar and the borders are not draw with the form when we do our Bitblt, probably because it is draw in another context (this is with Aero we're getting different results with Aero disabled).
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Don't worry for VB.NET.

I've tried your sample, as-is in VB.NET, but i get the same result...

As you can see on this sample, Notepad titlebar & borders are complete crap.User generated image
On this other sample, you can see that Internet Explorer titlebar & borders are not correct too.User generated image
I've tried this code on 2 computers with Windows 7 Home Edition x64, and both get the same result.
My target framework was 4.0, but i've tried with 2.0 and i have the same problem.
Wish I knew what the difference is...   =\

Can you give a good "big picture" of your overall app again...maybe there is a different approach that is acceptable.
We are trying to develop a VNC-Like application to display our screen to someone else.  

We want to add a functionality where the user can uncheck an application from a list of loaded applications, to hide this application to the remote viewer.  (It would still be displayed locally, but it wouldn't be seen by  the remote).

If it wouldn't be about that application choice, we would have taken the whole screen and check for differences only... but we can't do that as you can see
Oh wow...you might need to go "lower" and utilize something like C++ where you have better access to windows messages and do things like DLL injection.  (not my cup of tea though!)  You might want to get some folks from the C++ zone in on the question as they might have insight from a lower level windows messages perspective.
(Sorry for the delay)

We are looking for another possible solution using MaskBlt.  We are crawling inside the API to find something useful.  I think that we can create a Mask and use it with a screenshot of the full screen to achieve what we want to do.

I'll accept your solution as the solution since it should be working on "most" systems.  

Thanks a lot for your lights Idle_Mind