Issue Using Graphics DrawString On Resize - vb.net

Goal
I want to display a vertical text on the left of a user control to let a user know which product they are creating/editting. Like so:
How am I building it?
This user control is made up of three controls.
Label with text "Product Information". Dock=Top
User Control with a vertical draw string text of "Product #1". Dock=Left
Table Layout panel which contains X amount of user controls inside it. Dock=Fill
Here's the design view:
Here is the code for my product name user control that draws "Product #1"
Public Class uProductName
Public drawString As String = "Product #1"
Protected Overrides Sub OnPaint(e As PaintEventArgs)
' Call the OnPaint method of the base class.
MyBase.OnPaint(e)
' Call methods of the System.Drawing.Graphics object.
DrawVerticalString(e)
End Sub
Public Sub DrawVerticalString(ByVal e As PaintEventArgs)
Dim formGraphics As System.Drawing.Graphics = Me.CreateGraphics()
Dim drawFont As New System.Drawing.Font("Arial", 20)
Dim drawBrush As New System.Drawing.SolidBrush(System.Drawing.Color.Black)
Dim stringSize As New SizeF
stringSize = e.Graphics.MeasureString(drawString, drawFont)
Dim x As Single = (Me.Width / 2) - (stringSize.Height / 2)
Dim y As Single = (Me.Height / 2) - (stringSize.Width / 2)
Dim drawFormat As New System.Drawing.StringFormat
drawFormat.FormatFlags = StringFormatFlags.DirectionVertical
formGraphics.DrawString(drawString, drawFont, drawBrush, x, y, drawFormat)
drawFormat.Dispose()
drawFont.Dispose()
drawBrush.Dispose()
formGraphics.Dispose()
End Sub
End Class
Current Problem
When I start selecting buttons, the table layout panel expands to display more selections and the "Product #1" text starts to glitch. See below:
I tried to set the "Double Buffer" property to true and didn't the result. Any advice?

You need to set ControlStyles.ResizeRedraw style for your control to indicate whether the control redraws itself when resized.
Also Instead of using CreateGraphics(), use the graphics object of OnPaint method and never dispose it, because it doesn't belong to you.
Public Sub New()
' If the base class is Control, comment the next line
InitializeComponent()
Me.SetStyle(ControlStyles.ResizeRedraw, True)
End Sub
Public Sub DrawVerticalString(ByVal e As PaintEventArgs)
Dim formGraphics As System.Drawing.Graphics = e.Graphics
'...
End Sub

Related

Is it possible to group multiple PictureBoxes?

I can drag a PictureBox onto a Form Control, a Tab Control or a Panel Control, etc. And I can import an image into a PictureBox. However, I don't know how to group multiple PictureBoxes together in Visual Studio 2017. Looks like there is no such a function. I need this function because I want to generate a big picture based on the user's input. That big picture consists of multiple small pictures, the visibility of which is controlled by the user through multiple checkboxes.
In Excel, I could put multiple pictures in it, group them together, use the VBA to control the visibility of each picture, and finally copy that picture group into a Word file. I would do this in a VSTO Word Document project in Visual Studio 2017 using vb.net.
I added some pictures for demonstrate the expected function.
Picture 1 shows the small pictures to be used in a big picture. (Please ignore the .vslx file)
Picture 2 shows a possible result based on user's input.
You can make your own custom control. here is an example/suggestion how to do it with a User control that can be reused across your application. the user control is holding panels in a matrix, you can set a drag&drop Event to each Panel control and the user will be able to drop a picture box on each panel:
USER CONTROL:
Public Class UserControl1
Public NumberOfPanelsInRow As Integer
Sub New(ByVal height As Integer, width As Integer, Optional ByVal numberofPanelsInRow As Integer = 3)
' This call is required by the designer.'
InitializeComponent()
' Add any initialization after the InitializeComponent() call.'
Me.Height = height
Me.Width = width
Me.NumberOfPanelsInRow = numberofPanelsInRow
End Sub
Private Sub UserControl1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' grouped panels to hold picturebox you can drag & drop to them...'
Dim panelHeight As Integer = Me.Height / NumberOfPanelsInRow
Dim panelWidth As Integer = Me.Width / NumberOfPanelsInRow
Dim colors() As Color = {Color.Pink, Color.Black, Color.Red, Color.Cyan, Color.Green, Color.Orange,
Color.Red, Color.Pink, Color.Black, Color.Red, Color.Cyan, Color.Green, Color.Orange, Color.Red}
Dim total As Integer = NumberOfPanelsInRow * NumberOfPanelsInRow
Dim currentYlocation As Integer = 0
Dim currentXlocation As Integer = 0
Dim location As Point = New Point(0, currentYlocation)
Dim rowcounter As Integer = 0
Dim itemcounter As Integer = 0
For i = 1 To total
If rowcounter >= NumberOfPanelsInRow Then
rowcounter = 0
currentYlocation += panelHeight
currentXlocation = 0
End If
' to each one of this panel you can drag a picture box'
Dim p As New Panel
p.Size = New Size(panelWidth, panelHeight)
p.Location = New Point(currentXlocation, currentYlocation)
p.BackColor = colors(itemcounter)
Me.Controls.Add(p)
rowcounter += 1
itemcounter += 1
currentXlocation += panelWidth
Next
End Sub
End Class
CALLING THE USER CONTROL FROM FORM1:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim uc = New UserControl1(300, 300)
Me.Controls.Add(uc)
End Sub
End Class
GUI OUTPUT:

How to draw amount of blocks of the progressbar in VB.NET?

Progressbar has a maximumm is 10.
So I need to draw ten block without fill color before running progress.
Edit: added code
Public Class MyProgressBar
Inherits ProgressBar
Public Sub New()
Me.ForeColor = Color.Red
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
For i as integer = 1 to 10
Dim g As Graphics = e.Graphics
Dim widthScale As Integer = Me.Width/10
g.DrawRectangle(Pens,Me.Left+(i*widthScale ),Me.Top, Me.Width / 10, Me.Height)
End For
End Sub
End Class
I try override OnPaint() but it's not working.
There are number of ways to do this. The simplest would be to create a transparent image of the boxes and put it on top of a regular progress bar.
Or you could create an array of text boxes, and play with their position, border and background until they look the way you want.
A more powerful way to do graphics is with the built-in .net graphics object. There's a brief introduction here

OnPaint event not firing

I am overriding the paint event as I need to give a text box an extra property. In this case the extra property is a border colour for a text box. When the User Control which contains the text boxes appears it does not fire the OnPaint event. I've no idea why this is happening.
My code is as follows
CustomTaskBox class
Public Class CustomTextBox
Inherits TextBox
<Browsable(True)>
<Category("Extended Properties")>
<Description("Set TextBox border Color")>
Public Sub New()
Multiline = False
BorderStyle = BorderStyle.None
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
Dim buttonPen = New Pen(Color.Firebrick, 3)
Dim buttonRect = New Rectangle(0, 0, Size.Width - 1, Size.Height - 1)
e.Graphics.DrawRectangle(buttonPen, buttonRect)
End Sub
End Class
Main Class
Dim _NewTextBox As New CustomTextBox
_NewTextBox.Name = textBoxName
_NewTextBox.Multiline = multiline
_NewTextBox.Text = textBoxText
_NewTextBox.Top = topForNextControl
_NewTextBox.Left = 17
_NewTextBox.Width = textBoxWidth * widthScaleFactor
_MainContent.Controls.Add(_NewTextBox)
If I recall correctly - you need to useInvalidate(); to notify that control needs to be repainted.

Button stays in MouseDown

Well, I'm getting back into GDI, and I came across my old first attempt, which was in c#. I converted it to VB.NET, and saw no errors. However, when I tested it out, the button would stay the color for the MouseDown state until I closed the MessageBox that it opens. Any ideas?
GDI -
Public Class BasicButton
Inherits Control
Public Enum MouseState
Normal
Down
End Enum
Private _mouseState As MouseState = MouseState.Normal
Protected Overrides Sub CreateHandle()
MyBase.CreateHandle()
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
Dim g = e.Graphics
Select Case _mouseState
Case MouseState.Normal
g.FillRectangle(Brushes.Orange, ClientRectangle)
Exit Select
Case MouseState.Down
g.FillRectangle(Brushes.DarkOrange, ClientRectangle)
Exit Select
End Select
MyBase.OnPaint(e)
Dim sf As New StringFormat()
sf.LineAlignment = StringAlignment.Center
sf.Alignment = StringAlignment.Center
g.DrawString(Text, Font, New SolidBrush(Color.White), New Rectangle(0, 0, Width, Height), sf)
End Sub
Private Sub SwitchMouseState(state As MouseState)
_mouseState = state
Invalidate()
End Sub
Protected Overrides Sub OnMouseUp(e As MouseEventArgs)
SwitchMouseState(MouseState.Normal)
MyBase.OnMouseUp(e)
End Sub
Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
SwitchMouseState(MouseState.Down)
MyBase.OnMouseDown(e)
End Sub
End Class
Button -
Private Sub BasicButton1_Click(sender As Object, e As EventArgs) Handles BasicButton1.Click
MessageBox.Show("Text")
End Sub
MessageBox.Show is a blocking method that gets called between OnMouseDown and OnMouseUp. Basically, your OnMouseUp code is not called until after the MessageBox.Show method returns.
While not an answer, I believe it is important for you to be aware that creating resources within the Paint method should be done as sparingly as possible - hopefully not at all. Paint is called MANY MANY times per second in some cases.
So for instance where your code reads:
Dim sf As New StringFormat()
sf.LineAlignment = StringAlignment.Center
sf.Alignment = StringAlignment.Center
g.DrawString(Text, Font, New SolidBrush(Color.White), New Rectangle(0, 0, Width, Height), sf)
You are creating a StringFormat, and a SolidBrush, and a Rectangle.
The StringFormat and SolidBrush could be cached (by making them class-level variables). The Rectangle can also be cached by making it a class-level variable and updating it during the Resize event.

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