1

As the title states, is it possible / how can you create a custom window to draw onto? Normally, you would just use a form and form controls, but I want my own window with a handle that I'll attach hooks to and handle the paint events and the like. Is this possible? Essentially, I just need a container for my program's image that isn't a Form. If not in VB.Net, is it possible in C#?

EDIT: I'm just not very fond of how the window draws (even with control over paint event). I removed the form border and the control bar and replaced them with my own functions (to place the max/min/exit buttons, title, form borders + sizing, etc) so the form I'm using is essentially just a floating panel - though with built in hooks that are nice of course. But the form still flickers too much and so I wanted to handle everything myself. I use doublebuffering on all controls I use and I use setbounds to move/resize controls as opposed to setting width/height individually (reduced some of the flicker). I draw the form border in the form's paint event, the rest is drawn as controls (including the form's top bar).

I mostly hate the black boxes that I see when I expand the form (generally don't see that when decreasing window size, but still some small amount of flicker). An alternative method, perhaps a different draw style (in VB 2010) or something, would work as well I guess.

EDIT (again): The black box issue happens regardless of how many controls are on the form. If I try to manually resize it (the custom empty form control posted below that inherits from Form), using setbounds on each mousemove during a click and drag event (does not occur when not intended, so I know it's not running the sub more than it has to).

EDIT (code): http://img211.imageshack.us/img211/900/j9c.png So even on a blank "SimpleForm" (as posted in the first answer") with no controls, when resized to be larger (in the pic, resized northeast), black boxes are drawn under where the form will be drawn. Controlstyles / backbuffering done as posted in the second answer, as well as the createparams posted by Hans. This is what I used to set the form bounds:

Protected Overrides ReadOnly Property CreateParams() As CreateParams
    Get
        Dim cp As CreateParams = MyBase.CreateParams
        cp.ExStyle = cp.ExStyle Or &H2000000
        cp.Style = cp.Style Or &H2000000
        Return cp
    End Get
End Property 'CreateParams

Public Sub New(ByRef ContentFolder As String, ByRef x As Integer, ByRef y As Integer, ByRef w As Integer, ByRef h As Integer)
    FormBorderStyle = FormBorderStyle.None
'Note, I have tried the original suggested control styles in many combinations 
    Me.SetStyle(ControlStyles.OptimizedDoubleBuffer Or ControlStyles.ResizeRedraw Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.UserPaint
    UpdateStyles()

    OL = x 'Used for resizing, to know what the original bounds were - especially in maximizing, didn't like the standards maximize call
    OT = y
    OW = w
    OH = h
    BackColor = Color.White
    BorderColor = New Pen(BarColor.Color)
    MinimumSize = New Size(200, 200)
    TransparencyKey = Color.FromArgb(255, 255, 0, 128)
    CF = ContentFolder
    ControlBar = New FormBar(Me, "Explorer woo", CF)

    AddHandler Me.Load, AddressOf EF_Load
    AddHandler Me.MouseUp, AddressOf EF_MouseUp
    AddHandler Me.MouseDown, AddressOf EF_MouseDown
    AddHandler Me.MouseMove, AddressOf EF_MouseMove
    AddHandler Me.LostFocus, AddressOf EF_LostFocus
End Sub

Public Sub EF_Load(ByVal sender As Object, ByVal e As EventArgs)
    SetFormBounds(OL, OT, OW, OH)
End Sub

Protected Overrides Sub OnSizeChanged(ByVal e As EventArgs)
    ControlBar.SetBar(Width) 'Sets the width of controlbar to new width, and updates position of the 3 top-right form buttons
    If Not (_backBuffer Is Nothing) Then
        _backBuffer.Dispose()
        _backBuffer = Nothing
    End If
    RaiseEvent Resized(Me, e) 'Resizes controls in custom handler, in this example, it is unused - with controls, they don't flicker when resized though
    MyBase.OnSizeChanged(e)
End Sub

Private Sub SetFormBounds(ByRef l As Integer, ByRef t As Integer, ByRef w As Integer, ByRef h As Integer)
    If w < Me.MinimumSize.Width Then
        w = Me.MinimumSize.Width
        l = Left
    End If
    If h < Me.MinimumSize.Height Then
        h = Me.MinimumSize.Height
        t = Top
    End If
    If l = Left AndAlso t = Top AndAlso w = Width AndAlso h = Height Then Exit Sub
    ControlBar.SetBar(w)
    SetBounds(l, t, w, h)
    'Used for detecting if user coords are on the form borders with L-shaped areas so as to not include too much of the interior of the bar, Borderthickness = pixel width of border
    CornerRects = New List(Of Rectangle) From {{New Rectangle(0, 0, BorderThickness, 15)}, {New Rectangle(0, 0, 15, BorderThickness)}, {New Rectangle(Width - 15, 0, 15, BorderThickness)}, {New Rectangle(Width - BorderThickness, 0, BorderThickness, 15)}, {New Rectangle(0, Height - 15, BorderThickness, 15)}, {New Rectangle(BorderThickness, Height - BorderThickness, 10, BorderThickness)}, {New Rectangle(Width - BorderThickness, Height - 15, BorderThickness, 15)}, {New Rectangle(Width - 15, Height - BorderThickness, 10, BorderThickness)}}
End Sub

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
    If _backBuffer Is Nothing Then
        _backBuffer = New Bitmap(Me.ClientSize.Width, Me.ClientSize.Height)
    End If

    Dim g As Graphics = Graphics.FromImage(_backBuffer)
    g.Clear(SystemColors.Control)
    'Draw Control Box
    g.TextRenderingHint = Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit
    g.FillRectangle(BarColor, 0, 0, Width, ControlBar.Height)
    If ControlBar.Title <> "" Then g.DrawString(ControlBar.Title, ControlBar.Font, ControlBar.FontBrush, ControlBar.TextLeft, ControlBar.TextTop)
    g.DrawImage(FormBar.bmpCorners(0), 0, 0) 'Makes transparent corner, very small bitmap created at run-time
    g.DrawImage(FormBar.bmpCorners(1), Width - FormBar.bmpCorners(0).Width, 0)
    'Draw Control Box buttons top right
    If ControlBar.ExitButton.Enabled = True Then g.DrawImage(ControlBar.ExitButton.Img, ControlBar.ExitButton.Rect.X, ControlBar.ExitButton.Rect.Y)
    If ControlBar.MaximizeButton.Enabled = True Then g.DrawImage(ControlBar.MaximizeButton.Img, ControlBar.MaximizeButton.Rect.X, ControlBar.MaximizeButton.Rect.Y)
    If ControlBar.MinimizeButton.Enabled = True Then g.DrawImage(ControlBar.MinimizeButton.Img, ControlBar.MinimizeButton.Rect.X, ControlBar.MinimizeButton.Rect.Y)
    If Not ControlBar.Ico Is Nothing Then g.DrawImage(ControlBar.Ico, 5, 5) 'Draw Control Box icon (program icon) if it is set

    'Draw the form border
    For i = 0 To BorderThickness - 1
        g.DrawLine(BorderColor, i, ControlBar.Height, i, Height - 1)
        g.DrawLine(BorderColor, Width - 1 - i, ControlBar.Height, Width - 1 - i, Height - 1)
        g.DrawLine(BorderColor, BorderThickness, Height - 1 - i, Width - BorderThickness, Height - 1 - i)
    Next
    g.Dispose()
    e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0)
End Sub

Protected Overrides Sub OnPaintBackground(ByVal pevent As PaintEventArgs)
End Sub
  • It is *very* unclear why you are looking for an alternative. The Form class is a nice .NET wrapper class for a native window and permit *anything* you'd ever want to do with a window. Be specific about what problem you are trying to solve. – Hans Passant Jul 28 '13 at 14:21
  • What makes you think that "handling everything myself" isn't going to cause flicker as well? It is innate to the way child windows work, you'll do a bunch of work and end up with the exact same problem. Just don't use child windows. Check [this answer](http://stackoverflow.com/questions/2612487/how-to-fix-the-flickering-in-user-controls/2613272#2613272) for quick fixes. – Hans Passant Jul 28 '13 at 14:32
  • Hmm, I see that the question has been edited since I wrote my answer. In that case, Hans is right: the things you draw yourself are still subject to flicker if you do it wrong. There are better ways of solving this. I suppose it serves as a general example of an [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), and why you should explain all of these details about what you're trying to accomplish the first time around. – Cody Gray - on strike Jul 28 '13 at 14:36
  • So I used that EmptyForm control and did the create params with Composite, and tried various ControlStyles, yet I still cant' get rid of black boxes when expanding only (not contracting). No control draws more than once, and I handle all of said controls' paint functions manually. – Heathicus Miller Jul 28 '13 at 16:40
  • I should also mention that this does not just occur where a control will be drawn, but alongside the entire form side. It draws a black rectangle for a split second before the form is finished resizing. No flicker amongst controls. – Heathicus Miller Jul 28 '13 at 16:47
  • Really makes you appreciate how good Windows is at drawing forms on its own, doesn't it? This is a good reason why you shouldn't use a completely custom drawn interface. Besides the argument that it sticks out like a sore thumb, works differently from other apps, and users hate it. I don't think there's a perfect fix for this. All the suggestions are aimed at *reducing* the black borders, but the root of the problem is that the computer can't paint everything that you want quickly enough when rapidly resizing the window. Nearly all WPF apps have this problem because they reinvent the UI wheel. – Cody Gray - on strike Jul 30 '13 at 05:31
  • Rapidly resizing only increases the width of the black boxes - however, even small increases still show the black box (just very thin), which is not typical of any form being resized. – Heathicus Miller Jul 30 '13 at 11:23

2 Answers2

1

It is not really possible at all, in either language. This isn't a language thing, or even a framework (i.e. WinForms) thing. Rather, it's more because of the design of Windows itself. Essentially, everything in Windows is a window, and the Form class represents a basic top-level window that can be displayed directly on the desktop. If you want a window displayed on the desktop, you need to use the Form class. Moreover, if you want to have a window handle that you can attach hooks to, you'll need to use this class; it's the one with all the necessary plumbing to get that going.

But that doesn't mean it has to look like a default Form object does. The appearance is infinitely customizable. Start by setting the FormBorderStyle property of your form to remove the default window frame/chrome. That will give you a completely blank slate. Then, do like you said and handle its Paint event. Except that when you're wanting to handle the events of a derived class, you should override the OnXxx method directly, instead of subscribing to the events. So you'd have this code:

Public Class SimpleForm : Inherits Form

   Public Sub New()
      ' Alter the form's basic appearance by removing the window frame,
      ' which gives you a blank slate to draw onto.
      FormBorderStyle = FormBorderStyle.None

      ' Indicate that we're painting our own background.
      SetStyle(ControlStyles.Opaque, True)
   End Sub

   Protected Overrides Sub OnPaint(e As System.Windows.Forms.PaintEventArgs)
      ' Call the base class.
      MyBase.OnPaint(e)

      ' Paint the background...
      e.Graphics.FillRectangle(Brushes.MediumAquamarine, Me.ClientRectangle)

      ' ...and then the foreground.
      ' For example, drawing an 'X' to mark the spot!
      Using p As New Pen(Color.Navy, 4.0)
         e.Graphics.DrawLine(p, 0, 0, Me.Width, Me.Height)
         e.Graphics.DrawLine(p, Me.Width, 0, 0, Me.Height)
      End Using
   End Sub

End Class

Of course, such a window has severe usability problems. For starters, the user has no way to move it around on the screen or to close it. You'll need to handle those things yourself if you're eliminating the default border.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • I did this and it reduced flicker on scaling the form down almost entirely, but I still see the black-boxes drawn when expanding the form, especially when rapidly expanding the size. – Heathicus Miller Jul 28 '13 at 16:36
0

Can you show the method you are using to enable double buffering? Here's an article that addresses this. Perhaps it will help.

https://web.archive.org/web/20140811193726/http://bobpowell.net/doublebuffer.aspx

Basically, the code is like this (from the article):

Private _backBuffer As Bitmap

Public Sub New
    InitializeComponents()

    Me.SetStyle(ControlStyles.AllPaintingInWmPaint OR _
                ControlStyles.UserPaint OR _
                ControlStyles.DoubleBuffer, True)
End Sub

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    If _backBuffer Is Nothing Then
        _backBuffer = New Bitmap(Me.ClientSize.Width, Me.ClientSize.Height)
    End If

    Dim g As Graphics = Graphics.FromImage(_backBuffer)

    'Paint on the Graphics object here

    g.Dispose()

   'Copy the back buffer to the screen
   e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0)

End Sub 'OnPaint


'Don't allow the background to paint
Protected Overrides Sub OnPaintBackground(ByVal pevent As PaintEventArgs)
End Sub 'OnPaintBackground


Protected Overrides Sub OnSizeChanged(ByVal e As EventArgs)
    If Not (_backBuffer Is Nothing) Then
      _backBuffer.Dispose()
      _backBuffer = Nothing
    End If
    MyBase.OnSizeChanged(e)
End Sub 'OnSizeChanged
Andrew Morton
  • 24,203
  • 9
  • 60
  • 84
Chris Dunaway
  • 10,974
  • 4
  • 36
  • 48
  • I used this, and the results are still about the same. Black boxes constantly appear under where the form is resized when resized up. When scaled down, the form cleanly resizes it seems. No flicker in controls. – Heathicus Miller Jul 29 '13 at 16:54