-1

Using VB.Net 2012

I would like to put a command button on the of my windows form to the left of the control box and to the right of the title. Is this possible?

I don't see a way of doing this through the 'standard' windows means perhaps with some more advanced GDI trickery?

I was going to add a picture of what I am trying to accomplish but apparently my reputation is too low to post images, I will try an ascii picture, please use your imagination!

  ________________________________________________________
  |Q Windows Title          [New BUTTON]      [ _ O  X ] |
  |______________________________________________________|
  |                                                      |
  | Normal windows area                                  |
Mcp
  • 11
  • 2
  • possible duplicate of [How to draw custom button in Window Titlebar with Windows Forms?](http://stackoverflow.com/questions/106912/how-to-draw-custom-button-in-window-titlebar-with-windows-forms) – The Blue Dog Sep 12 '14 at 16:35
  • Yes I think that is the same thing, I did not hit it on my search... I shall try it. – Mcp Sep 12 '14 at 16:45
  • It turns out that, that doesn't answer the question. Its in C#. It seemed promising but I am unable to get it to work. The final (Working) code in the example is incomplete. So I'm back where I started. – Mcp Sep 12 '14 at 17:48

1 Answers1

0

It has some work but can be done. Create a new class APIHelp:

Imports System.Runtime.InteropServices

Public Class APIHelp

    Public Const WS_EX_LAYERED As Int32 = 524288
    'Public Const HTCAPTION As Int32 = 2
    'Public Const WM_NCHITTEST As Int32 = 132
    Public Const ULW_ALPHA As Int32 = 2
    Public Const AC_SRC_OVER As Byte = 0
    Public Const AC_SRC_ALPHA As Byte = 1

    Public Enum Bool
        [False] = 0
        [True] = 1
    End Enum

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure Point
        Public x As Int32
        Public y As Int32

        Public Sub New(ByVal x As Int32, ByVal y As Int32)
            Me.x = x
            Me.y = y
        End Sub
    End Structure

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure Size
        Public cx As Int32
        Public cy As Int32

        Public Sub New(ByVal cx As Int32, ByVal cy As Int32)
            Me.cx = cx
            Me.cy = cy
        End Sub
    End Structure

    <StructLayout(LayoutKind.Sequential, Pack:=1)> _
    Private Structure ARGB
        Public Blue As Byte
        Public Green As Byte
        Public Red As Byte
        Public Alpha As Byte
    End Structure

    <StructLayout(LayoutKind.Sequential, Pack:=1)> _
    Public Structure BLENDFUNCTION
        Public BlendOp As Byte
        Public BlendFlags As Byte
        Public SourceConstantAlpha As Byte
        Public AlphaFormat As Byte
    End Structure

    Public Declare Auto Function UpdateLayeredWindow Lib "user32.dll" (ByVal hwnd As IntPtr, ByVal hdcDst As IntPtr, ByRef pptDst As Point, ByRef psize As Size, ByVal hdcSrc As IntPtr, ByRef pprSrc As Point, _
                      ByVal crKey As Int32, ByRef pblend As BLENDFUNCTION, ByVal dwFlags As Int32) As Bool

    Public Declare Auto Function CreateCompatibleDC Lib "gdi32.dll" (ByVal hDC As IntPtr) As IntPtr

    Public Declare Auto Function GetDC Lib "user32.dll" (ByVal hWnd As IntPtr) As IntPtr

    <DllImport("user32.dll", ExactSpelling:=True)> _
    Public Shared Function ReleaseDC(ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As Integer
    End Function

    Public Declare Auto Function DeleteDC Lib "gdi32.dll" (ByVal hdc As IntPtr) As Bool

    <DllImport("gdi32.dll", ExactSpelling:=True)> _
    Public Shared Function SelectObject(ByVal hDC As IntPtr, ByVal hObject As IntPtr) As IntPtr
    End Function
    Public Declare Auto Function DeleteObject Lib "gdi32.dll" (ByVal hObject As IntPtr) As Bool

End Class

Add a new form eg Form2. Set TopMost = False, FormBorderStyle = None and ShowInTaskbar = False. This form will be your button. Because we want to draw tranparently we set the WS_EX_LAYERED style and we draw with UpdateLayeredWindow function. You can not draw directly on a form with WS_EX_LAYERED style. You have to use a device context, draw in it and then call UpdateLayeredWindow:

Public Class Form2

Private sourceLocation As New APIHelp.Point(0, 0)
Private newSize As New APIHelp.Size(...., .....)
Private newLocation As APIHelp.Point
Private blend As New APIHelp.BLENDFUNCTION()
Private memDcNormal, memDcEnter, memDcDown, screenDc, hBmpNormal, hBmpNormalOld, _
        hBmpEnter, hBmpEnterOld, hBmpDown, hBmpDownOld As IntPtr

Private Sub Form2_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    Me.Location = New Point(Form1.Location.X + ..., Form1.Location.Y + ...) 'complete the dots
    Me.Size = New Size(..., ...) 'size of button

    Initialize()

End Sub

Private Sub Initialize()
    ' Only works with a 32bpp bitmap
    blend.BlendOp = APIHelp.AC_SRC_OVER
    ' Always 0
    blend.BlendFlags = 0
    ' Set to 255 for per-pixel alpha values
    blend.SourceConstantAlpha = 255
    ' Only works when the bitmap contains an alpha channel
    blend.AlphaFormat = APIHelp.AC_SRC_ALPHA

    screenDc = APIHelp.GetDC(IntPtr.Zero)

    Using bmp As Bitmap = CType(Bitmap.FromFile(".....Normal.png"), Bitmap)
        memDcNormal = IntPtr.Zero
        memDcNormal = APIHelp.CreateCompatibleDC(screenDc)
        hBmpNormal = bmp.GetHbitmap(Color.FromArgb(0))
        hBmpNormalOld = APIHelp.SelectObject(memDcNormal, hBmpNormal)
    End Using

    Using bmp As Bitmap = CType(Bitmap.FromFile("......Enter.png"), Bitmap)
        memDcEnter = IntPtr.Zero
        memDcEnter = APIHelp.CreateCompatibleDC(screenDc)
        hBmpEnter = bmp.GetHbitmap(Color.FromArgb(0))
        hBmpEnterOld = APIHelp.SelectObject(memDcEnter, hBmpEnter)
    End Using

    Using bmp As Bitmap = CType(Bitmap.FromFile("......Down.png"), Bitmap)
        memDcDown = IntPtr.Zero
        memDcDown = APIHelp.CreateCompatibleDC(screenDc)
        hBmpDown= bmp.GetHbitmap(Color.FromArgb(0))
        hBmpDownOld = APIHelp.SelectObject(memDcDown, hBmpDown)
    End Using

    APIHelp.DeleteDC(screenDc)
End Sub


Protected Overloads Overrides ReadOnly Property CreateParams() As CreateParams
    Get
        'Add the layered extended style (WS_EX_LAYERED) to this window
        Dim createParam As CreateParams = MyBase.CreateParams
        createParam.ExStyle = createParam.ExStyle Or 524288 'WS_EX_LAYERED
        Return createParam
    End Get
End Property


Private Sub Form2_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
    newLocation = New APIHelp.Point(Me.Location.X, Me.Location.Y)

    APIHelp.UpdateLayeredWindow(Handle, IntPtr.Zero, newLocation, newSize, memDcDown, sourceLocation, _
                                0, blend, APIHelp.ULW_ALPHA)
End Sub

Private Sub Form2_MouseEnter(sender As System.Object, e As System.EventArgs) Handles MyBase.MouseEnter
    newLocation = New APIHelp.Point(Me.Location.X, Me.Location.Y)

    APIHelp.UpdateLayeredWindow(Handle, IntPtr.Zero, newLocation, newSize, memDcEnter, sourceLocation, _
                                0, blend, APIHelp.ULW_ALPHA)
End Sub

Private Sub Form2_MouseMove(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
    Static i As Integer = 1
    Static j As Integer = 1

    If e.Button = Windows.Forms.MouseButtons.Left Then
        If e.X < 0 Or e.X > Me.Width Or e.Y < 0 Or e.Y > Me.Height Then
            If i = 1 Then
                newLocation = New APIHelp.Point(Me.Location.X, Me.Location.Y)

                APIHelp.UpdateLayeredWindow(Handle, IntPtr.Zero, newLocation, newSize, memDcNormal, sourceLocation, _
                                            0, blend, APIHelp.ULW_ALPHA)
                i = 0
                j = 1
            End If
        Else
            If j = 1 Then
                newLocation = New APIHelp.Point(Me.Location.X, Me.Location.Y)

                APIHelp.UpdateLayeredWindow(Handle, IntPtr.Zero, newLocation, newSize, memDcDown, sourceLocation, _
                                            0, blend, APIHelp.ULW_ALPHA)
                j = 0
                i = 1
            End If
        End If
    End If
End Sub

Private Sub Form2_MouseLeave(sender As System.Object, e As System.EventArgs) Handles MyBase.MouseLeave
    newLocation = New APIHelp.Point(Me.Location.X, Me.Location.Y)

    APIHelp.UpdateLayeredWindow(Handle, IntPtr.Zero, newLocation, newSize, memDcNormal, sourceLocation, _
                                0, blend, APIHelp.ULW_ALPHA)
End Sub

Private Sub Form2_MouseClick(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseClick
    newLocation = New APIHelp.Point(Me.Location.X, Me.Location.Y)

    APIHelp.UpdateLayeredWindow(Handle, IntPtr.Zero, newLocation, newSize, memDcEnter, sourceLocation, _
                                0, blend, APIHelp.ULW_ALPHA)

    'Do your staff

End Sub

Private Sub Form2_FormClosing(sender As System.Object, e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
    APIHelp.SelectObject(memDcNormal, hBmpNormalOld)
    APIHelp.DeleteObject(hBmpNormal)
    APIHelp.DeleteDC(memDcNormal)

    APIHelp.SelectObject(memDcEnter, hBmpEnterOld)
    APIHelp.DeleteObject(hBmpEnter)
    APIHelp.DeleteDC(memDcEnter)

    APIHelp.SelectObject(memDcDown, hBmpDownOld)
    APIHelp.DeleteObject(hBmpDown)
    APIHelp.DeleteDC(memDcDown)
End Sub

End Class

As you can see from the above code you need three png images for each state of the button.

In your initial form:

Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load

    Form2.Show(Me)
End Sub

Private Sub Form1_Move(sender As System.Object, e As System.EventArgs) Handles MyBase.Move
    Form2.Location = New Point(Me.Location.X + ..., Me.Location.Y + ...)
End Sub

valter