Solved

Title bar not drawn on Windows 7

Posted on 2011-03-21
12
1,084 Views
Last Modified: 2012-05-11
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

 Bug
0
Comment
Question by:cdebel
  • 6
  • 6
12 Comments
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 35192930
*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

0
 
LVL 10

Author Comment

by:cdebel
ID: 35193447
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.
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 35193709
Have you got it working correctly now then?...or do you still need help?
0
 
LVL 10

Author Comment

by:cdebel
ID: 35193740
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).
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 35196083
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.
0
 
LVL 10

Author Comment

by:cdebel
ID: 35198231
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).
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 500 total points
ID: 35200452
Not sure what is going on...I'm running Win 7 Pro x64 and the titlebar/borders are not getting clipped.  =\

Here's my test code:  * Sorry for VB.Net...I just prefer it! *
Imports System.Text
Public Class Form1

    Private Structure RECT
        Public Left As Integer
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer
    End Structure

    Private Const SRCCOPY As Integer = &HCC0020
    Private Const CAPTUREBLT As Integer = &H40000000

    Private Declare Function GetWindowDC Lib "user32" (ByVal handle As IntPtr) As Integer
    Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As Boolean
    Private Declare Function GetWindowRect Lib "user32" Alias "GetWindowRect" (ByVal hwnd As IntPtr, ByRef lpRect As RECT) As Integer
    Private Declare Function BitBlt Lib "gdi32" Alias "BitBlt" (ByVal hDestDC As Integer, ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hSrcDC As Integer, ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal dwRop As Integer) As Integer

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim we As New WindowsEnumerator
        Dim DialogWindows As List(Of ApiWindow) = we.GetTopLevelWindows()

        Dim bmp As New Bitmap(SystemInformation.PrimaryMonitorSize.Width, SystemInformation.PrimaryMonitorSize.Height)
        Using destG As Graphics = Graphics.FromImage(bmp)
            destG.Clear(Color.Black)

            Dim destDC As IntPtr = destG.GetHdc

            Dim RC As Rectangle
            Dim screenRC As Rectangle = Screen.PrimaryScreen.Bounds
            For Each aw In DialogWindows
                RC = GetWindowRectangle(aw.Handle)
                If Not RC.Size.Equals(screenRC.Size) Then
                    Dim srcDC As IntPtr = GetWindowDC(aw.Handle)

                    BitBlt(destDC, RC.X, RC.Y, RC.Width, RC.Height, srcDC, 0, 0, SRCCOPY Or CAPTUREBLT)

                    ReleaseDC(aw.Handle, srcDC)
                End If
            Next

            destG.ReleaseHdc(destDC)

            PictureBox1.Image = bmp
        End Using
    End Sub

    Private Function GetWindowRectangle(ByVal handle As IntPtr) As Rectangle
        Dim apiRC As RECT
        GetWindowRect(handle, apiRC)
        Return New Rectangle(apiRC.Left, apiRC.Top, apiRC.Right - apiRC.Left, apiRC.Bottom - apiRC.Top)
    End Function

End Class

Public Class ApiWindow
    Public Text As String = ""
    Public ClassName As String = ""
    Public Handle As IntPtr
End Class

Public Class WindowsEnumerator

    Private Delegate Function EnumCallBackDelegate _
        (ByVal handle As IntPtr, ByVal lParam As Integer) As Integer
    Private Declare Function EnumWindows Lib "user32" _
        (ByVal lpEnumFunc As EnumCallBackDelegate, ByVal lParam As Integer) As Integer
    Private Declare Function EnumChildWindows Lib "user32" _
        (ByVal ParentHandle As IntPtr, ByVal lpEnumFunc As EnumCallBackDelegate, _
         ByVal lParam As Integer) As Integer
    Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _
        (ByVal handle As IntPtr, ByVal lpClassName As StringBuilder, ByVal nMaxCount As Integer) As Integer
    Private Declare Function IsWindowVisible Lib "user32" (ByVal handle As IntPtr) As Integer
    Private Declare Function GetParent Lib "user32" (ByVal handle As IntPtr) As IntPtr
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
        (ByVal handle As IntPtr, ByVal wMsg As Integer, _
         ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
        (ByVal handle As IntPtr, ByVal wMsg As Integer, _
         ByVal wParam As Integer, ByVal lParam As StringBuilder) As Integer

    Private _listChildren As New List(Of ApiWindow)
    Private _listTopLevel As New List(Of ApiWindow)

    Private _topLevelClass As String = ""
    Private _childClass As String = ""

    Public Overloads Function GetTopLevelWindows() As List(Of ApiWindow)
        EnumWindows(AddressOf EnumWindowProc, &H0)
        Return _listTopLevel
    End Function

    Public Overloads Function GetTopLevelWindows(ByVal className As String) As List(Of ApiWindow)
        _topLevelClass = className
        Return Me.GetTopLevelWindows()
    End Function

    Public Overloads Function GetChildWindows(ByVal handle As IntPtr) As List(Of ApiWindow)
        ' Clear the window list.
        _listChildren = New List(Of ApiWindow)
        ' Start the enumeration process.
        EnumChildWindows(handle, AddressOf EnumChildWindowProc, &H0)
        ' Return the children list when the process is completed.
        Return _listChildren
    End Function

    Public Overloads Function GetChildWindows(ByVal handle As IntPtr, ByVal ChildClass As String) As List(Of ApiWindow)
        ' Set the search
        _childClass = childClass
        Return Me.GetChildWindows(handle)
    End Function

    Private Function EnumWindowProc(ByVal handle As IntPtr, ByVal lParam As Integer) As Integer
        'If GetParent(handle) = 0 AndAlso CBool(IsWindowVisible(handle)) Then
        If CBool(IsWindowVisible(handle)) Then
            ' Get the window title / class name.
            Dim window As ApiWindow = GetWindowIdentification(handle)

            ' Match the class name if searching for a specific window class.
            If _topLevelClass.Length = 0 OrElse window.ClassName.ToLower() = _topLevelClass.ToLower() Then
                _listTopLevel.Add(window)
            End If
        End If

        Return 1
    End Function

    Private Function EnumChildWindowProc(ByVal handle As IntPtr, ByVal lParam As Integer) As Integer
        Dim window As ApiWindow = GetWindowIdentification(handle)

        ' Attempt to match the child class, if one was specified, otherwise
        ' enumerate all the child windows.
        If _childClass.Length = 0 OrElse window.ClassName.ToLower() = _childClass.ToLower() Then
            _listChildren.Add(window)
        End If

        Return 1
    End Function

    Private Function GetWindowIdentification(ByVal handle As IntPtr) As ApiWindow
        Const WM_GETTEXT As Integer = &HD
        Const WM_GETTEXTLENGTH As Integer = &HE

        Dim window As New ApiWindow()
        Dim title As New StringBuilder()

        ' Get the size of the string required to hold the window title.
        Dim size As Integer = SendMessage(handle, WM_GETTEXTLENGTH, 0, 0)

        ' If the return is 0, there is no title.
        If size > 0 Then
            title = New StringBuilder(size + 1)
            SendMessage(handle, WM_GETTEXT, title.Capacity, title)
        End If

        ' Get the class name for the window.
        Dim classBuilder As New StringBuilder(64)
        GetClassName(handle, classBuilder, classBuilder.Capacity)

        ' Set the properties for the ApiWindow object.
        window.ClassName = classBuilder.ToString()
        window.Text = title.ToString()
        window.Handle = handle

        Return window
    End Function

End Class

Open in new window

BitBlt.jpg
0
 
LVL 10

Author Comment

by:cdebel
ID: 35200910
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.Sample1
On this other sample, you can see that Internet Explorer titlebar & borders are not correct too.Sample2
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.
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 35201067
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.
0
 
LVL 10

Author Comment

by:cdebel
ID: 35201144
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
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 35201297
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.
0
 
LVL 10

Author Comment

by:cdebel
ID: 35214313
(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
0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

Does the idea of dealing with bits scare or confuse you? Does it seem like a waste of time in an age where we all have terabytes of storage? If so, you're missing out on one of the core tools in every professional programmer's toolbox. Learn how to …
Displaying an arrayList in a listView using the default adapter is rarely the best solution. To get full control of your display data, and to be able to refresh it after editing, requires the use of a custom adapter.
An introduction to basic programming syntax in Java by creating a simple program. Viewers can follow the tutorial as they create their first class in Java. Definitions and explanations about each element are given to help prepare viewers for future …
Viewers will learn how to properly install Eclipse with the necessary JDK, and will take a look at an introductory Java program. Download Eclipse installation zip file: Extract files from zip file: Download and install JDK 8: Open Eclipse and …

760 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

22 Experts available now in Live!

Get 1:1 Help Now