VB.NET Custom Control (custom drawing) Refresh issue - vb.net

I've created a simple solution with 2 projects. The 1st project (class library) contains a custom control called Container which draws itself with rounded corners. The 2nd project (windows forms) is a test application.
If I add a Container instance to main Form in the 2nd project it shows the rounded corners nicely. Also when I run the 2nd project I can see the Container.
However when I start moving the form (click and hold the title bar), especially when I move it very fast, all the drawing is messed up, drawn over and over again but not clearing it's surface first...
I can call Container1.Refresh() in the Form1.Move event, but I don't want to set this every time because this also means I have to call Container1.Refresh() in the Form1.Resize event and who knows which other event...
Is there an event in the Container (control) class itself where I should call Me.Refresh() or Me.Update() or Me.Invalidate() ?
For reference (Form1.vb)
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
Private Sub Form1_Move(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Move
Me.Container1.Refresh()
End Sub
End Class
for reference (Container.vb):
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Drawing.Drawing2D
Public Class Container : Inherits Control
Private _Gp As GraphicsPath
Private Sub Container_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim r As Rectangle = e.ClipRectangle
Dim gp As New GraphicsPath
Dim cs As Integer = 25 'CornerSize'
r.Inflate(-5, -5)
gp.AddArc(r.X, r.Y, cs, cs, 180, 90)
gp.AddArc(r.X + r.Width - cs, r.Y, cs, cs, 270, 90)
gp.AddArc(r.X + r.Width - cs, r.Y + r.Height - cs, cs, cs, 0, 90)
gp.AddArc(r.X, r.Y + r.Height - cs, cs, cs, 90, 90)
Dim t As Single = cs / 2 + r.Y
gp.AddLine(r.X, r.Y + r.Height - cs, r.X, t)
e.Graphics.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias
e.Graphics.DrawPath(Pens.Black, gp)
End Sub
End Class

This is your problem:
Dim r As Rectangle = e.ClipRectangle
Change it to:
Dim r As Rectangle = Me.ClientRectangle

It looks to me as though your Container class isn't painting its entire area - normally a control is responsible for painting its entire rectangle.
In order to have a control that doesn't do this - that has transparent areas (like your rounded corners) - you need to give your control the WS_EX_TRANSPARENT property. Note that this is a Windows API subject, not a .NET one, so you're heading in the direction of some minor voodoo.
While it's written in C#, the CodeProject article Making Transparent Controls with C# and .NET 3.5 does seem directly relevant to what you're trying to achieve.
To quote that article, you first need to override the constructor of your UserControl and configure the background:
public TranspControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, true);
this.BackColor = Color.Transparent;
}
Then, you need to override the CreateParams() method to set the control style WS_EX_TRANSPARENT:
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}

It shouldn't be necessary to force a redraw here (under normal circumstances) since that redraw is automatically forced as soon as your control gets nudged.
However, what you need to do is clearing the background of your control before painting anything else: otherwise, your painting operation will mingle with previous painting processes. Just add an
e.Graphics.Clear(BackColor)
before your other drawing operations in the Paint event handler. Also, consider using the OnPaint method rather than the Paint event since you subclass the control and don't need to resort to the Paint event handler.
For the record, Refresh forces a synchronous redraw which is usually not desired. Rather, use Invalidate which enqueues the redraw request into the default window message queue.

Related

How to screen a rectangle with a keyup event?

The sun is still above the horizon. With 7:43 from Baden Austria i try to mention my titel question with some additional example.
Following the Window10 environment build 19041.985 Visual Studio Community Version 4.8.04084,
the predfined Keys of a Logitech Deluxe 250 Keyboard are not altered with the visual basic method
Sub Kein_Stress_beim_Essen(e As KeyEventArgs)
Select Case e.KeyCode
Case Keys.Space
Dim Kautchuj As Drawing.Graphics = Me.CreateGraphics
Kautchuj.DrawRectangle(New Pen(Color.PaleGreen, 2), 250, 150, 100, 50)
End Select
End Sub
.
To strengthen the sum for physical exercises, pull the sholder bladebones to the rising sun balance until pushing back without regret where i need them, the DrawRectangle is set with the form property CreateGraphics.
For more then one property i search, to use, the GDI+ objects that can be set to link a Me.KeyUp delegate with the event literature.
For my personal argument i use an additional cross to start and end a even, odd number disscusion.
I try to screen a rectangle through the case Keys.Space. While CreateGraphics is, literally, used for the aim of controls in visual basic with a rectangle object i can imagine a paint event and do not know if the key event can also be used for a object.
I have a search pattern to concatenate operators a not named function. It is not possible that everything is an object. Even with some energy exercises. To aim i fade the predefined color names to build an enum naming convention. Some dictionary brainstorm words are delegate, event eventargs, tupel, keys, select, property, method and instance.
I do not concider the possibility to make new fonts because an ime substitution is not an additional information for me.
It seems like you're asking how to draw boxes on the KeyUp event. In that case, here's a quick demo of how it should be done:
Private ReadOnly points As New List(Of Point)
Private ReadOnly rng As New Random
Private Const BOX_DIMENSION As Integer = 10
Private Sub Form1_KeyUp(sender As Object, e As KeyEventArgs) Handles MyBase.KeyUp
Select Case e.KeyCode
Case Keys.Space
'Draw a new box at a random location.
Dim x = rng.Next(ClientSize.Width)
Dim y = rng.Next(ClientSize.Height)
points.Add(New Point(x, y))
'Repaint just the area that will be occupied by the new box.
Invalidate(New Rectangle(x, y, BOX_DIMENSION + 1, BOX_DIMENSION + 1))
Case Keys.Escape
'Erase all boxes.
points.Clear()
'Repaint the whole form.
Invalidate()
End Select
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
'Draw a box at each location.
For Each point In points
e.Graphics.DrawRectangle(Pens.Red, point.X, point.Y, BOX_DIMENSION, BOX_DIMENSION)
Next
End Sub
As you can see, all the drawing is done in the Paint event handler. The KeyUp event handler updates the data that represents the drawing and then forces a repaint.

How do I clear a drawn line in vb.net?

I am writing a simple test program that draws an axis/crosshair in a form. I have two text boxes, where I put in the x-center and y-center and draw the crosshair based on that. I want to be able to put in new coordinates, and move the crosshair to the new position, but when I do, the old drawing stays there. I want to erase the old drawing and then draw the new one.
My code is below:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim mypen As Pen
mypen = New Pen(Drawing.Color.Red, 1)
Dim mygraphics As Graphics = Me.CreateGraphics
Dim x_center = Integer.Parse(xPos.Text)
Dim y_center = Integer.Parse(yPos.Text)
mygraphics.DrawLine(mypen, x_center - 50, x_center, x_center + 50, x_center)
mygraphics.DrawLine(mypen, y_center, y_center - 50, y_center, y_center + 50)
End Sub
End Class
The Drawing on a Control surface is usually handled through the Control's Paint() event, using its PaintEventArgs class object.
To raise the Paint() event of a Control, call its Invalidate() method.
(Note that the Invalidate() method has a number of overloads, some of which allows to re-paint only a defined region of the surface.)
If a Graphics object is created elsewhere (as you're doing now), the drawings performed with this object will persist or will be erased when you don't want to (e.g. if a Control needs to repaint itself - and this happens quite often - the drawings will be erased).
Also, the Graphics object can't be stored. It will become an invalid object as soon as a Control has repainted its surface.
You could re-design you code in this way.
Create a shared Pen (you can redefined it at any moment if you need to, using its properties) so you don't have to create a new one every time you need to draw something.
Use a shared Point field to store the current center of the drawing.
Move the Graphics.DrawLine() to the Paint event of your Form.
Remember to Dispose() the Pen object when the Form closes (you can use it's Dispose() pre-defined method).
Public Class Form1
Private mypen As Pen = New Pen(Color.Red, 1)
Private Position As Point = New Point(-1, -1)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If (Integer.TryParse(xPos.Text, Position.X) = True) AndAlso
(Integer.TryParse(yPos.Text, Position.Y) = True) Then
Me.Invalidate()
End If
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
If Position.X > -1 Then
e.Graphics.DrawLine(mypen, Position.X - 50, Position.Y, Position.X + 50, Position.Y)
e.Graphics.DrawLine(mypen, Position.X, Position.Y - 50, Position.X, Position.Y + 50)
End If
End Sub
End Class
This is, however, not that much efficient, because you need to invalidate the entire Form.
For a full implementation, take a look a this Class (PasteBin - CrossHair).

VB.net Graphics not drawing to screen

I am creating a hangman game and i cant get my dashes to draw on the screen.
Public Class Form1
Public lines() As String= IO.File.ReadAllLines("C:\Users\axfonath14\Desktop\words_alpha.txt")
Dim thisArray As String() = lines.ToArray
Public word As String = thisArray(CInt(Int((thisArray.Length * Rnd()) + 1)))
Public g As Graphics = Me.CreateGraphics()
Public dash As Image = My.Resources.dash
Public x As Integer = 1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Label1.Text = word
For Each c As Char In word
x += 1
g.DrawImage(dash, 125 + (100 * x), 100)
Next
Label2.Text = x
End Sub
End Class
when i run this code it runs through the loop and changes the label to the amount of letters but it just wont draw anything.
Never, EVER call CreateGraphics. Lazy people do so without explanation in examples and tutorials but it is never appropriate in a real application. ALWAYS do your drawing in the Paint event handler of the control you want to draw on. In your case, get rid of that g field altogether, move your drawing code to the Paint event handler of the form and use the Graphics object that it provides:
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
For Each c As Char In word
x += 1
e.Graphics.DrawImage(dash, 125 + (100 * x), 100)
Next
End Sub
That way, your drawing gets restored each time the form gets erased and repainted, which can be often and will be after the Load event handler completes.
If you want to change what gets drawn then you need to store the appropriate data in fields that can then be accessed from the Paint event handler. To force a repaint, call Invalidate on the form or control whose Paint event you're handling.
You're grabbing the graphics context before the form has initialized. Move your initialization code to the actual constructor, after the InitializeComponent() method is called.

Custom rounded button with outline

I am tring to create a custom button width rounded corner and a white outline which follows its shape. On the OnPaint event I've added the following code.
Public Class RoundedButton
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
Dim grPath As GraphicsPath = New GraphicsPath(FillMode.Winding)
grPath.AddArc(0, 0, ClientSize.Height, ClientSize.Height, 90, 180)
grPath.AddLine(grPath.GetLastPoint, New Point(ClientSize.Width - grPath.GetLastPoint.X * 2, 0))
grPath.AddArc(New RectangleF(grPath.GetLastPoint, New Size(ClientSize.Height, ClientSize.Height)), 270, 180)
grPath.CloseFigure()
Me.Region = New Region(grPath)
Dim mypen As New Pen(Color.White, 2)
mypen.Alignment = PenAlignment.Inset
e.Graphics.DrawPath(mypen, grPath)
End Sub
End Class
If I try to use it in a form, it works only if Backcolor property is set to Transparent. If not I can't see the pen path.
I would like to change the backcolor without loose the rounded white border of the pen.
I would like to obtain something like this:
With all projects that you want custom buttons, it is usually easiest to curve a PictureBox with a picture of your button, then using your coding language (VB.NET, C#, C++) to add an on click function to make the action.
Here is a mini example using VB.NET:
Private Sub pictureBox1_Click(sender As Object, e As EventArgs) Handles pictureBox1.Click
Process.start("http://stackoverflow.com")
End Sub
Hope I helped,
Matt

How can you create a custom window (not a form object) in VB.net?

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 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.
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