Question

How I could paint a gradient on the titlbebar (just paint to a different color than the visual XP theme color) preserving the windows XP style?

I've tried to find examples but I don't found nothing even just to replace the titlebar gradient for a solid color, I've found a simple code to paint a solid color but it throws an exception at the Dim g As Graphics saying that the value cannot be null:

<Runtime.InteropServices.DllImport("user32.dll")>
Private Shared Function GetDCEx(
        ByVal hWnd As IntPtr,
        ByVal hrgnClip As IntPtr,
        ByVal DeviceContextValues As DeviceContextValues
) As IntPtr
End Function

Protected Overrides Sub WndProc(ByRef m As Message)

    MyBase.WndProc(m)

    If (m.Msg = 133) Then
        Dim DCX_CACHE As Integer = 2
        Dim DCX_WINDOW As Integer = 1
        Dim g As Graphics = Graphics.FromHdc(GetDCEx(Me.Handle, m.WParam, (DCX_WINDOW Or DCX_CACHE)))
        g.DrawLine(Pens.Red, New Point(0, 0), New Point(30, 30)) 'Draw Here
    End If

End Sub
Was it helpful?

Solution

There are a number of things you might do here to avoid using API calls within a dot net application.

  1. Removed the windows border and create a dummy titlebar within the form. This is the easiest solution that will allow you to do all sorts of UI look and feel, without getting having to deal with Windows APIs.
  2. Review the System.Drawing.Drawing2D.LinearGradientBrush namespace.
  3. Review how your solution may migrate to WPF and metro UI in windows 8. The whole approach to application menus changes, so your work may get thrown away.
  4. Ask your client, what the business case is for the change in title bar colour. That being said I've had graphic artists dictate the titlebar colour to me in the past. I used option 1 above.
  5. Once you get any solution completed, your going to have to test it in various operating systems to confirm the API are still relevant (hence I would recommend you don't use them as M$ is not your friend with API calls anymore, hence the recommendation to use .net methods).

OTHER TIPS

Sometime after but here it goes:

a) You must remove the form's theme

SetWindowTheme(Me.Handle, String.Empty, String.Empty)

b) For m.WParam = 1, GetDCEx fails. So, GetWindowDC is what you need. Your override goes like this

    Protected Overrides Sub WndProc(ByRef m As Message)

    MyBase.WndProc(m)

    If (m.Msg = 133) Then
        If m.WParam = 1 Then
            Dim g As Graphics = Graphics.FromHdc(GetWindowDC(Me.Handle))
            Using g
                g.DrawLine(Pens.Red, New PointF(0, 0), New PointF(30, 30)) 'Draw Here
            End Using
            Me.Refresh()

        Else

            Dim DCX_CACHE As Integer = 2
            Dim DCX_WINDOW As Integer = 1
            Dim g As Graphics = Graphics.FromHdc(GetDCEx(Me.Handle, m.WParam, (DCX_WINDOW Or DCX_CACHE)))
            Using g
                g.DrawLine(Pens.Red, New PointF(0, 0), New PointF(30, 30)) 'Draw Here
            End Using
            Me.Invalidate()
            Me.Refresh()
        End If
    End If

End Sub

The other two declares you need are as following

    <Runtime.InteropServices.DllImport("user32.dll")>
Private Shared Function GetWindowDC(ByVal hWnd As IntPtr) As IntPtr
End Function
<Runtime.InteropServices.DllImport("uxtheme.dll")>
Private Shared Function SetWindowTheme(ByVal hWnd As IntPtr, ByVal appName As String, ByVal partList As String) As Integer
End Function

The SetThemeWindow I called it in the form_load event and it worked fine. The Using statement was for GC to deal with the graphics (cleaning up) and the Me.Refresh() as for the client area to not got hayhire in resizing the form (or maximizing).

Credits go to Mike (from How to set the client area (ClientRectangle) in a borderless form?) for his code in C#, that is fairly more complete and with a different direction then this is. His coding is directed to NC area sizes, but also painting them. Here it's more just the painting.

Hope it helps!

[EDIT]

I had a first edit, but futher investigation showed that it probably works in particular cases, not being a actual "final solution"!

So goes the recent findings. GetDCEx function actually fails because of the m.WParam doesn't provide needed information, and getting GetDCEx function to work, you don't need the "m.WParam = 1" IF test. Just use the following:

Using g As Graphics = Graphics.FromHdc(GetDCEx(Me.Handle, IntPtr.Zero, (DeviceContextValues.Window Or DeviceContextValues.Cache)))
    g.ExcludeClip(New Rectangle((Me.Width - Me.ClientSize.Width) / 2, (Me.Height - Me.ClientSize.Height) - ((Me.Width - Me.ClientSize.Width) / 2), Me.ClientRectangle.Width, Me.ClientRectangle.Height))
    g.FillRectangle(Brushes.DarkRed, New Rectangle(0, 0, Me.Width, Me.Height))
    g.DrawRectangle(Pens.Blue, New Rectangle(0, 0, Me.Width - 1, Me.Height - 1))
    g.DrawString(Me.Text, New Font("Tahoma", 10), Brushes.LightCyan, New PointF(Me.Width / 2 - Len(Me.Text) / 2, 4))
    End Using

instead of the IF block you have, and pass the second parameter of GetDCEx as Intptr.Zero. You never get a null return value. This also draws a border (kinda like some Office or MS software) and the forms text (or caption) titlebar horizontally centered. There is an add to the painting that is excluding the client area. This way, nothing goes wrong with this area (client one). Hope it helps.

I really can't make much sense of the above code, considering I've never done what you're trying to do. As long as you don't mind not being able to change the gradient, you could make a Visual Studio theme and select it as your theme for each form in your code. I've never done that either, so I'm not sure how hard it would be either.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top