How to dynamically add and manipulate multiple, unique pictureboxes in VB.NET? - vb.net

I'm a high-school student making a basic game for an assessment.
The game makes baseballs fly across the screen at the player, who bats them away.
All baseballs are contained in pictureboxes, and I need to be able to make an infinite number of them which can be individually referenced and moved, all during runtime.
I'm currently adding and storing pictureboxes in dictionaries. However, whenever I create a new picturebox and add it to the form, it overrides any previously created pictureboxes on the form.
I need to find code that allows previously created pictureboxes to remain on the form while creating new ones.
For context, I have added the logic flow of my program below.
Current Program Logic
Sub that randomly determines whether the ball will spawn on the left, right, north or south sides of the screen.
Sub that based on the above result, sets up a new "baseball" picturebox by altering a blank variable.
Sub that adds this as the Value to a dictionary, with the Key a variable called ballNameNumber.
At the same time, a random number is set as the Value for a dictionary called ballVelocity, with Key ballNameNumber. This is multiplied by a variable called level, which increases as the game time increases.
So at this point, a ball has been created, with identical key names in 3 dictionaries that each store its picturebox values and speed.
Sub that randomly chooses 1 of 4 spawn locations, records the direction of the ball to a dictionary called ballDirection based on this value, then creates the ball at this location.
All these subs occur in this order per tick of a timer called tmrGameTime (interval 500), on a form called frmGame (size 700,700)
'Dictionaries used to log and describe the movement of the balls onscreen.
Dim spawnedBalls As Dictionary(Of Integer, PictureBox) = New Dictionary(Of Integer, PictureBox)
Dim ballVelocity As Dictionary(Of Integer, Integer) = New Dictionary(Of Integer, Integer)
Dim ballDirection As Dictionary(Of Integer, String) = New Dictionary(Of Integer, String)
'Variables used in the composition of dictionaries.
Dim ballNameNumber As Integer = 1
Dim numberOfBalls As Integer = 0
Dim level As Integer = 1
Dim ball As New Picturebox
'Turns on the game timer
Private Sub frmGame_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
tmrGameTime.Enabled = True
tmrGameTime.Start()
End Sub
'Sets values to the ball variable (must be done within a sub otherwise an error is dispayed)
Public Sub ballSetUpLeft(ByRef ballTemplate)
ballTemplate.SizeMode = PictureBoxSizeMode.StretchImage
ballTemplate.Width = 34
ballTemplate.Height = 29
ballTemplate.Top = 325
ballTemplate.Left = 55
ballTemplate.Image = My.Resources.Baseball_Sprite
End Sub
Public Sub ballSetUpRight(ByRef ballTemplate)
ballTemplate.SizeMode = PictureBoxSizeMode.StretchImage
ballTemplate.Width = 34
ballTemplate.Height = 29
ballTemplate.Top = 325
ballTemplate.Left = 593
ballTemplate.Image = My.Resources.Baseball_Sprite
End Sub
Public Sub ballSetUpTop(ByRef ballTemplate)
ballTemplate.SizeMode = PictureBoxSizeMode.StretchImage
ballTemplate.Width = 34
ballTemplate.Height = 29
ballTemplate.Top = 59
ballTemplate.Left = 333
ballTemplate.Image = My.Resources.Baseball_Sprite
End Sub
Public Sub ballSetUpBottom(ByRef ballTemplate)
ballTemplate.SizeMode = PictureBoxSizeMode.StretchImage
ballTemplate.Width = 34
ballTemplate.Height = 29
ballTemplate.Top = 574
ballTemplate.Left = 333
ballTemplate.Image = My.Resources.Baseball_Sprite
End Sub
'Generates a random speed for a spawned ball based on the level value.
Public Function generateBallSpeed(ByVal level) As Integer
Randomize()
Dim ans As Integer = (((Rnd() * 10) * level) + 1)
Return ans
End Function
'Logs the ball data into dictionaries
Public Sub createBall(ByRef spawnedBalls, ByRef ballVelocity, ByRef ballNameNumber, ByRef numberOfBalls, ByRef ballTemplate)
'Adds a new ball with name and ballTemplate values to the dictionary.
spawnedBalls.Add(ballNameNumber, ballTemplate)
Dim v As Integer = generateBallSpeed(level)
'Using the matching name, adds a velocity value to the ball
ballVelocity.Add(ballNameNumber, v)
End Sub
'Spawns ball at a specific location on the form.
Public Sub spawnBallAtPitcher()
Randomize()
Dim pitcher As Integer = Int((4 - 1 + 1) * Rnd() + 1)
Select Case pitcher
Case 1
Call ballSetUpLeft(ballTemplate)
Call createBall(spawnedBalls, ballVelocity, ballNameNumber, numberOfBalls, ballTemplate)
ballDirection.Add(ballNameNumber, "Left")
ballNameNumber += 1
Me.Controls.Add(spawnedBalls.Item(ballNameNumber - 1))
Case 2
Call ballSetUpRight(ballTemplate)
Call createBall(spawnedBalls, ballVelocity, ballNameNumber, numberOfBalls, ballTemplate)
ballDirection.Add(ballNameNumber, "Right")
ballNameNumber += 1
Me.Controls.Add(spawnedBalls.Item(ballNameNumber - 1))
Case 3
Call ballSetUpTop(ballTemplate)
Call createBall(spawnedBalls, ballVelocity, ballNameNumber, numberOfBalls, ballTemplate)
ballDirection.Add(ballNameNumber, "Top")
ballNameNumber += 1
Me.Controls.Add(spawnedBalls.Item(ballNameNumber - 1))
Case 4
Call ballSetUpBottom(ballTemplate)
Call createBall(spawnedBalls, ballVelocity, ballNameNumber, numberOfBalls, ballTemplate)
ballDirection.Add(ballNameNumber, "Down")
ballNameNumber += 1
Me.Controls.Add(spawnedBalls.Item(ballNameNumber - 1))
End Select
End Sub
'Repeatedly spawns balls
Private Sub tmrGameTime_Tick_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrGameTime.Tick
Call spawnBallAtPitcher()
End Sub
End Class
I expect that the pictureboxes will appear in random order on my form, until four a visible at different points (as any further pictureboxes will be created on top of the previous). However, on picturebox is created and then jumps between spawn locations. No error messages are received.

Your problem is you're using only 1 reference of ball in your code. So each time you want to create another ball, you use replace the previous ball with your new ball. Change your code in the 4 procedures into functions then return the new ball.
'Sets values to the ball variable (must be done within a sub otherwise an error is dispayed)
Public Function ballSetUpLeft() As PictureBox
Dim newBall As New PictureBox
newBall.SizeMode = PictureBoxSizeMode.StretchImage
newBall.Width = 34
newBall.Height = 29
newBall.Top = 325
newBall.Left = 55
newBall.Image = My.Resources.Baseball_Sprite
Return newBall
End Function
Public Function ballSetUpRight() As PictureBox
Dim newBall As New PictureBox
newBall.SizeMode = PictureBoxSizeMode.StretchImage
newBall.Width = 34
newBall.Height = 29
newBall.Top = 325
newBall.Left = 593
newBall.Image = My.Resources.Baseball_Sprite
Return newBall
End Function
Public Function ballSetUpTop() As PictureBox
Dim newBall As New PictureBox
newBall.SizeMode = PictureBoxSizeMode.StretchImage
newBall.Width = 34
newBall.Height = 29
newBall.Top = 59
newBall.Left = 333
newBall.Image = My.Resources.Baseball_Sprite
Return newBall
End Function
Public Function ballSetUpBottom() As PictureBox
Dim newBall As New PictureBox
newBall.SizeMode = PictureBoxSizeMode.StretchImage
newBall.Width = 34
newBall.Height = 29
newBall.Top = 574
newBall.Left = 333
newBall.Image = My.Resources.Baseball_Sprite
Return newBall
End Function
Then change how you call the functions (used to be procedures) like this:
Select Case pitcher
Case 1
Dim newBall = ballSetUpLeft()
Call createBall(spawnedBalls, ballVelocity, ballNameNumber, numberOfBalls, newBall)
ballDirection.Add(ballNameNumber, "Left")
ballNameNumber += 1
Me.Controls.Add(spawnedBalls.Item(ballNameNumber - 1))
Case 2
Dim newBall = ballSetUpRight()
Call createBall(spawnedBalls, ballVelocity, ballNameNumber, numberOfBalls, newBall)
ballDirection.Add(ballNameNumber, "Right")
ballNameNumber += 1
Me.Controls.Add(spawnedBalls.Item(ballNameNumber - 1))
Case 3
Dim newBall = ballSetUpTop()
Call createBall(spawnedBalls, ballVelocity, ballNameNumber, numberOfBalls, newBall)
ballDirection.Add(ballNameNumber, "Top")
ballNameNumber += 1
Me.Controls.Add(spawnedBalls.Item(ballNameNumber - 1))
Case 4
Dim newBall = ballSetUpBottom()
Call createBall(spawnedBalls, ballVelocity, ballNameNumber, numberOfBalls, newBall)
ballDirection.Add(ballNameNumber, "Down")
ballNameNumber += 1
Me.Controls.Add(spawnedBalls.Item(ballNameNumber - 1))
End Select
Now your balls won't be replaced each time you want to create another ball. I leave the task to animate the balls and removing the balls when the pitcher hit the balls.
I tried to reduce redundancy in your code. The 3 dictionaries are replaced by a single dictionary which hold the ball information (id, direction, velocity and the picture). Use enumeration instead of hard-coded numbers for direction. Use Random class instead of Randomize() and Rnd() functions.
Public Class frmGame
'Dictionaries used to log and describe the movement of the balls onscreen.
Private balls = New Dictionary(Of Integer, Ball)
'Variables used in the composition of dictionaries.
Private random As New Random
Private lastBallID As Integer
Private Const level As Integer = 1
Private Sub frmGame_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Turns on the game timer
tmrGameTime.Enabled = True
tmrGameTime.Start()
End Sub
Private Sub tmrGameTime_Tick_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrGameTime.Tick
'Repeatedly spawns balls
spawnBallAtPitcher()
End Sub
'Spawns ball at a specific location on the form.
Private Sub spawnBallAtPitcher()
lastBallID += 1
Dim pitcher As Direction = random.Next(4) + 1
Dim newBall = createBall(lastBallID, pitcher, random)
Controls.Add(newBall.Picture)
'Logs the ball data into dictionaries
balls.Add(lastBallID, newBall)
End Sub
'Create new ball, set its ID, direction, velocity and picture
Private Function createBall(ballID As Integer, direction As Direction, rnd As Random) As Ball
Return New Ball With {
.BallID = ballID,
.Direction = direction,
.Velocity = generateBallSpeed(level, rnd),
.Picture = generateBallPicture(direction)
}
End Function
'Generates a random speed for a spawned ball based on the level value.
Private Function generateBallSpeed(ByVal level As Integer, ByVal rnd As Random) As Integer
Return (rnd.Next(10) + 1) * level
End Function
'Generates a new picture of ball
Private Function generateBallPicture(direction As Direction) As PictureBox
Dim location = generatePictureLocation(direction)
Return New PictureBox With {
.SizeMode = PictureBoxSizeMode.StretchImage,
.Width = 34,
.Height = 29,
.Top = location.Y,
.Left = location.X,
.Image = My.Resources.Baseball_Sprite
}
End Function
'Generates a location for new picture of ball
Private Function generatePictureLocation(direction As Direction) As Point
Select Case direction
Case Direction.Left
Return New Point(55, 325)
Case Direction.Right
Return New Point(593, 325)
Case Direction.Top
Return New Point(333, 59)
Case Direction.Bottom
Return New Point(333, 574)
End Select
End Function
End Class
Public Enum Direction
Left = 1
Right
Top
Bottom
End Enum
Public Class Ball
Public Property BallID As Integer
Public Property Velocity As Integer
Public Property Direction As Integer
Public Property Picture As PictureBox
End Class

Related

How to refer to controls using variable in a loop?

I have labels named: label1, label2, ...label16. I want to assign a random number to all of them.
Something like this could work but I don't know the syntax:
for i = 1 to 16
label(i).text = Math.Ceiling(Rnd() * 99)
next
Your suggestions would be appreciated.
You can use this using Controls.Find:
For i As Integer = 1 To 16
Dim arrCtrl() As Control = Me.Controls.Find("label" & i, True)
If arrCtrl.Length = 1 AndAlso TypeOf arrCtrl(0) Is Label Then
DirectCast(arrCtrl(0), Label).Text = Math.Ceiling(Rnd() * 99)
End If
Next
Getting a random Integer
Use the Random class instead of the Rnd function to get a random Integer within a specified range in the Random.Next(Int32, Int32) method. Declare a class variable of Random type:
Private ReadOnly rand As New Random
Finding a range of controls
This code snippet iterates over the Controls collection of the container, returns - if any - the Label controls where their names are equals to a range of names starts from label1 to label16, and finally, assign a random Integer to their Text properties:
Private Sub TheCaller()
For Each lbl In Controls.OfType(Of Label).
Where(Function(x) Enumerable.Range(1, 16).
Any(Function(y) x.Name.ToLower.Equals($"label{y}")))
lbl.Text = rand.Next(1, 100).ToString
Next
End Sub
Just in case, if the Label controls are hosted by different containers, then you need a recursive function to get them:
Private Function GetAllControls(Of T)(container As Control) As IEnumerable(Of T)
Dim controls = container.Controls.Cast(Of Control)
Return controls.SelectMany(Function(x) GetAllControls(Of T)(x)).
Concat(controls.OfType(Of T))
End Function
And call it as follows:
Private Sub TheCaller()
For Each lbl In GetAllControls(Of Label)(Me).
Where(Function(x) Enumerable.Range(1, 16).
Any(Function(y) x.Name.ToLower.Equals($"label{y}")))
lbl.Text = rand.Next(1, 100).ToString
Next
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim labels As New List(Of Label) From {Label1, Label2, Label3, Label4}
For Each l As Label In labels
l.Text = rand.Next(99).ToString
Next
End Sub
To use your approach
Declare a variable for the Random class outside your method (a Form level variable).
Create a List of labels.
Loop through all the labels in your list and set the .Text property with the .Next method of the Random class.

Transparent image over two controls with different back colors

I am trying to place a transparent image over two adjacent controls that have different background colors.
I want the image to remain transparent, meaning the Image needs to show the backcolor of each control.
The controls are two Panels set to different background colors and the Image (PictureBox or otherwise) is placed between the two panel controls.
Public Class frmMain
Private Img1 As Image = Image.FromFile("C:\xxxx.png")
Private Sub frmMain_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
e.Graphics.DrawImage(Img1, 5, 5)
End Sub
End Class
Let's try this.
Create a new class in the Project, call it TPanel and paste in the custom Panel class you can find below, overwriting the existing definition.
Compile the Project then find the new TPanel control in the ToolBox and drop one instance inside a Form.
On the Form, not inside one of the Colored Panels, otherwise it will become child of another control and it will be confined inside its bounds.
Add an event handler to the Paint event of the TPanel and insert this code inside the handler method:
Private Sub TPanel1_Paint(sender As Object, e As PaintEventArgs) Handles TPanel1.Paint
Dim canvas As Control = DirectCast(sender, Control)
Dim rect As Rectangle = ScaleImageFrame(imgBasketBall, canvas.ClientRectangle)
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
e.Graphics.CompositingMode = CompositingMode.SourceOver
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half
e.Graphics.DrawImage(imgBasketBall, rect)
End Sub
Private Function ScaleImageFrame(sourceImage As Bitmap, destinationFrame As Rectangle) As Rectangle
Dim rect As RectangleF = New RectangleF(0, 0, sourceImage.Width, sourceImage.Height)
'Define the ratio between the Image Rectangle and the Container ClientRectangle
Dim ratio As Single = CType(Math.Max(destinationFrame.Width, destinationFrame.Height) /
Math.Max(rect.Width, rect.Height), Single)
rect.Size = New SizeF(rect.Width * ratio, rect.Height * ratio)
'Use Integer division to avoid negative values
rect.Location = New Point((destinationFrame.Width - CInt(rect.Width)) \ 2,
(destinationFrame.Height - CInt(rect.Height)) \ 2)
Return Rectangle.Round(rect)
End Function
In the Form, create an instance of a Bitmap object that will contain the Image; also set the Location of the Panel (TPanel)
The Controls called panColored1 and panColored2 are supposed to be the names of the two existing Panels where the Image must be positioned. The sample code positions the Image in the middle of the 2 Panels, using TPanel1.Location( (...) )
Private imgBasketBall As Bitmap = Nothing
Public Sub New()
InitializeComponent()
imgBasketBall = DirectCast(Image.FromStream(New MemoryStream(File.ReadAllBytes("basketball.png"))), Bitmap)
TPanel1.Size = New Size(120, 120)
TPanel1.Location = New Point(panColored1.Left + (panColored1.Width - TPanel1.Width) \ 2,
panColored1.Top + (panColored1.Height + panColored2.Height - TPanel1.Height) \ 2)
TPanel1.BringToFront()
End Sub
Result:
Bitmap Size Bitmap Size
(1245x1242) (1178x2000)
The TPanel (Transparent Panel) class:
Imports System.ComponentModel
<DesignerCategory("Code")>
Public Class TPanel
Inherits Panel
Private Const WS_EX_TRANSPARENT As Integer = &H20
Public Sub New()
SetStyle(ControlStyles.AllPaintingInWmPaint Or
ControlStyles.UserPaint Or
ControlStyles.Opaque Or
ControlStyles.ResizeRedraw, True)
SetStyle(ControlStyles.OptimizedDoubleBuffer, False)
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
e.Graphics.FillRectangle(Brushes.Transparent, Me.ClientRectangle)
MyBase.OnPaint(e)
End Sub
Protected Overrides ReadOnly Property CreateParams() As CreateParams
Get
Dim parameters As CreateParams = MyBase.CreateParams
parameters.ExStyle = parameters.ExStyle Or WS_EX_TRANSPARENT
Return parameters
End Get
End Property
End Class
There is also something you can also try, It may not be professional but it works. Split the images into two halves. Draw the first half on one of the panels and the second half on the other panel.
Be Sure to Import System.IO in Your project.
The code for the splitting goes like this:
Imports System.IO
...
Public Function SplitImage(ByVal imgpath As String) As Image()
Dim img As Image = Image.FromFile(imgpath)
Dim bmp As Bitmap = DirectCast(img, Bitmap)
Dim i As Integer = bmp.Height / 2
Dim image1 As Bitmap = New Bitmap(bmp.Width, i)
Dim image2 As Bitmap = New Bitmap(bmp.Width, i)
Dim yPos As Integer = 0
For x As Integer = 0 To image1.Width - 1
For y As Integer = 0 To image1.Height - 1
image1.SetPixel(x, y, bmp.GetPixel(x, y))
yPos = y
Next
Next
yPos += 1
Dim ycount As Integer = 0
For x As Integer = 0 To image2.Width - 1
For y As Integer = yPos To bmp.Height - 1
If ycount = i Then
ycount -= 1
End If
image2.SetPixel(x, ycount, bmp.GetPixel(x, y))
ycount += 1
Next
ycount = 0
Next
Dim ms As MemoryStream = New MemoryStream
Dim ms1 As MemoryStream = New MemoryStream
image1.Save(ms, Imaging.ImageFormat.Png)
image2.Save(ms1, Imaging.ImageFormat.Png)
Dim returnedImage(2) As Image
returnedImage(0) = image1
returnedImage(1) = image2
Return returnedImage
End Function
Create Two panels on your form (Panel1 and Panel2) and a Button(Button1).
Place The two panels the way you want it, set the BackgroundImageLayout property of the panels to StretchImage.
Then from your code you can call the function like this, i.e From the Button's click event:
Public Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim pic() As Image = SplitImage("C:\xxxx.png")
Panel1.BackgroundImage = pic(0)
Panel2.BackgroundImage = pic(1)
End Sub
For More Information about the Bitmap Class, Check out this link Bitmap Class

Grid I can paint on

So I am trying to create an application to ease creation of pixel arts (school project), what I've done so far is draw a grid in a panel, next step would be to allow the user to click on a cell and have it painted, but I can't manage to make it work, here's the code I have:
Private Sub drawGrid(g As Graphics, rows As Integer, columns As Integer)
Dim originPoint As Point = New Point(10, 2)
Dim size As Size = New Size(64, 64)
Dim left As Integer = originPoint.X
Dim up As Integer = originPoint.Y
Dim right As Integer = originPoint.X + (columns * size.Width)
Dim down As Integer = originPoint.Y + (rows * size.Height)
For y As Integer = up To down + 1 Step size.Height
Dim pt1 As New Point(left, y)
Dim pt2 As New Point(right, y)
g.DrawLine(Pens.Black, pt1, pt2)
Next
For x As Integer = left To right + 1 Step size.Width
Dim pt1 As New Point(x, up)
Dim pt2 As New Point(x, down)
g.DrawLine(Pens.Black, pt1, pt2)
Next
End Sub
This draws a grid with the amount of columns and rows the user wants, but I've been struggling to allow painting
What I've been thinking is: dispose this code, and create a 'pixel' class, create the amount of 'pixel' objects based on user rows and columns, and draw each one individually, then just change each 'pixel's' color
This is a Grid class that allows setting the color of its cells.
The Grid cell are referenced using a List(Of List(Of Class)).
The Cell class Object contains is a simple Rectagle property that measures the size of the cell, and a Color property, which allows to set the color of the single cell:
Friend Class GridCell
Public Property Cell() As Rectangle
Public Property CellColor() As Color
End Class
You can define:
The size of the Grid → ColoredGrid.GridSize = new Size(...)
The number of Columns and Rows → ColoredGrid.GridColumnsRows = new Size(...)
The position of the Grid inside the Canvas → ColoredGrid.GridPosition = New Point(...)
The color of the Grid → ColoredGrid.GridColor = Color.Gray
The BackGround color of the cells → ColoredGrid.CellColor = Color.FromArgb(32, 32, 32)
The color of a selected cell → ColoredGrid.SelectedCellColor = Color.OrangeRed
The Grid class holds a reference to the control which will be used as the Canvas for the grid painting. This reference is set in the class contructor.
The Grid registers the Canvas control Paint() and MouseClick() events to respond to the related actions automatically.
When a Mouse Click is detected on the Canvas surface, the MouseEventArgs e.Location property reports the coordinates where the Click occurred.
To identify the Grid Cell where this action is performed, the GetUpdateCell() method inspects the List(Of List(Of GridCell)) using a simple LINQ SelectMany() and identified the Cell rectangle that contains the Mouse Click coordinates (expressed as a Point() value).
This identification is performed simply checking whether the Cell Rectangle.Contains(Point()).
When the cell is identified, the Canvas Invalidate() method is called, specifing the area to repaint.
This area corresponds to the Cell Rectangle, so only this section is repainted when a Cell is colored, to save resources and time.
To test it, create a Panel and a Button in a Form:
Imports System.Drawing
'This Grid object in defined at Form Class scope
Public ColoredGrid As ColorGrid
'Button used to trigger the Grid painting
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If ColoredGrid IsNot Nothing Then
ColoredGrid.Dispose()
End If
ColoredGrid = New ColorGrid(Panel1)
ColoredGrid.GridSize = New Size(300, 300)
ColoredGrid.GridColumnsRows = New Size(10, 10)
ColoredGrid.GridPosition = New Point(10, 10)
ColoredGrid.GridColor = Color.White
ColoredGrid.CellColor = Color.FromArgb(32, 32, 32)
ColoredGrid.SelectedCellColor = Color.OrangeRed
ColoredGrid.BuildGrid()
End Sub
This is a visual sample that shows how it works:
This is the main Grid class.
The ColorGrid Class supports IDisposable, because it registers the described events. These must be unregistered when the Class is not used anymore. Weird things can happen if you don't.
Public Class ColorGrid
Implements IDisposable
Private Grid As List(Of List(Of GridCell))
Private CurrentGridSize As New Size(100, 100)
Private GridColRows As New Size(10, 10)
Private CellSize As New Size(10, 10)
Private MouseCell As Point = Point.Empty
Private Canvas As Control = Nothing
Private UpdateCell As Boolean = False
Private NewGrid As Boolean = False
Public Sub New(DrawingControl As Control)
If DrawingControl IsNot Nothing Then
Me.Canvas = DrawingControl
AddHandler Me.Canvas.Paint, New PaintEventHandler(AddressOf Me.ControlPaint)
AddHandler Me.Canvas.MouseClick, New MouseEventHandler(AddressOf Me.MouseHandler)
Me.GridPosition = New Point(10, 10)
Me.CellColor = Color.FromArgb(32, 32, 32)
End If
End Sub
Public Property GridPosition() As Point
Public Property CellColor() As Color
Public Property SelectedCellColor() As Color
Public Property GridColor() As Color
Public Property GridSize() As Size
Get
Return Me.CurrentGridSize
End Get
Set(value As Size)
Me.CurrentGridSize = value
SetCellSize()
End Set
End Property
Public Property GridColumnsRows() As Size
Get
Return Me.GridColRows
End Get
Set(value As Size)
Me.GridColRows = value
SetCellSize()
End Set
End Property
Private Property RefreshCell() As GridCell
Friend Class GridCell
Public Property Cell() As Rectangle
Public Property CellColor() As Color
End Class
Private Sub SetCellSize()
Me.CellSize = New Size((Me.CurrentGridSize.Width \ Me.GridColRows.Width),
(Me.CurrentGridSize.Height \ Me.GridColRows.Height))
If Me.CellSize.Width < 4 Then Me.CellSize.Width = 4
If Me.CellSize.Height < 4 Then Me.CellSize.Height = 4
End Sub
Public Sub BuildGrid()
If Me.Canvas Is Nothing Then Return
Me.Grid = New List(Of List(Of GridCell))()
For row As Integer = 0 To GridColumnsRows.Height - 1
Dim RowCells As New List(Of GridCell)()
For col As Integer = 0 To GridColumnsRows.Width - 1
RowCells.Add(New GridCell() With {
.Cell = New Rectangle(New Point(Me.GridPosition.X + (col * Me.CellSize.Width),
Me.GridPosition.Y + (row * Me.CellSize.Height)),
Me.CellSize),
.CellColor = Me.CellColor})
Next
Me.Grid.Add(RowCells)
Next
Me.NewGrid = True
Me.Canvas.Invalidate()
End Sub
Private Sub ControlPaint(o As Object, e As PaintEventArgs)
If Me.NewGrid Then
e.Graphics.Clear(Me.Canvas.BackColor)
Me.NewGrid = False
End If
Me.Grid.
SelectMany(Function(rowcells) rowcells).
Select(Function(colcell)
If Me.UpdateCell Then
Using brush As New SolidBrush(Me.RefreshCell.CellColor)
e.Graphics.FillRectangle(brush, Me.RefreshCell.Cell.X + 1, Me.RefreshCell.Cell.Y + 1,
Me.RefreshCell.Cell.Width - 1, Me.RefreshCell.Cell.Height - 1)
End Using
Me.UpdateCell = False
Return Nothing
Else
Using pen As New Pen(Me.GridColor)
e.Graphics.DrawRectangle(pen, colcell.Cell)
End Using
Using brush As New SolidBrush(colcell.CellColor)
e.Graphics.FillRectangle(brush, colcell.Cell.X + 1, colcell.Cell.Y + 1,
colcell.Cell.Width - 1, colcell.Cell.Height - 1)
End Using
End If
Return colcell
End Function).TakeWhile(Function(colcell) colcell IsNot Nothing).ToList()
End Sub
Private Sub MouseHandler(o As Object, e As MouseEventArgs)
Me.RefreshCell = GetUpdateCell(e.Location)
Me.RefreshCell.CellColor = Me.SelectedCellColor
Dim CellColorArea As Rectangle = Me.RefreshCell.Cell
CellColorArea.Inflate(-1, -1)
Me.UpdateCell = True
Me.Canvas.Invalidate(CellColorArea)
End Sub
Private Function GetUpdateCell(CellPosition As Point) As GridCell
Return Me.Grid.
SelectMany(Function(rowcells) rowcells).
Select(Function(gridcell) gridcell).
Where(Function(gridcell) gridcell.Cell.Contains(CellPosition)).
First()
End Function
Public Sub Dispose() Implements IDisposable.Dispose
If Me.Canvas IsNot Nothing Then
RemoveHandler Me.Canvas.Paint, AddressOf Me.ControlPaint
RemoveHandler Me.Canvas.MouseClick, AddressOf Me.MouseHandler
Me.Grid = Nothing
End If
End Sub
End Class

getting control object with Graphics from class instance

i dont have much experience with Graphics class in winforms.
i am only in the sketching stage of it (also the code i have added).
my problem is that i am trying to create a panel: clockPanel with some graphics on it, no exception is thrown but the panel (as i can see in the UI) have no graphics on it. tryed to look for examples but i cant find mistakes or something i missed in my code. probably its an easy one for those of you that experienced with graphics.
thank you for your time an consideration.
VB CODE:
adding 'clockpanel' panel to other pannel ('secondaryPannel') via instance to GoalsClock class
Public Class ManagersTab
...
Public Sub BuiledDashBoard()
...
Dim p As GoalsClock = New GoalsClock(100, 100, 0.8)
p.Create()
p.clockPanel.Location = New Point(200, 100)
secondaryPannel.Controls.Add(p.clockPanel)
...
End Sub
...
End Class
Create() method is the most relevant part:
Class GoalsClock
Private Gclock As Graphics
Private clockWidth As Int16
Private clockHeight As Int16
Private xPos As Int16
Private yPos As Int16
Public clockPanel As Panel
Private panelColor As Color
Private PercentTextColor As Color
' rectangles to store squares
Protected OuterRect As Rectangle
Protected InnerRect As Rectangle
Protected InnerStringBrush As Brush
Protected InnerStringColor As Color
Protected InnerStringFontSize As Byte
' inner square
Private InnerSquarePen As Pen
Private InnerSquarePen_Color As Color
Private InnerSquarePen_Width As Byte
' outer square
Private OuterSquarePen As Pen
Private OuterSquarePen_Color As Color
Private OuterSquarePen_Width As Byte
Private _PercentOfGoals As Single ' to calculate the goals deg arc
Public Property PercentOfGoals() As Single
Get
Return _PercentOfGoals * 100
End Get
Private Set(ByVal value As Single)
If value <= 1.0F Then
_PercentOfGoals = value
Else
value = 0
End If
End Set
End Property
Sub New(ByVal clockWidth As Int16, ByVal clockHeight As Int16, ByVal GoalsPercent As Single)
Me.clockWidth = clockWidth
Me.clockHeight = clockHeight
PercentOfGoals = GoalsPercent
' values for test
xPos = 0
yPos = 0
InnerStringFontSize = 12
OuterSquarePen = New Pen(Color.Gray)
InnerSquarePen = New Pen(Color.Cyan)
OuterSquarePen_Width = 23
InnerSquarePen_Width = 15
End Sub
''' <summary>
'''
''' create graphics of the goals clock on clockPanel
''' </summary>
''' <remarks></remarks>
Public Sub Create()
' panel
clockPanel = New Panel()
clockPanel.Size = New Size(clockWidth, clockHeight)
clockPanel.BackColor = Color.Beige
Gclock = clockPanel.CreateGraphics()
' create outer rectangle
OuterRect = New Rectangle(xPos, yPos, clockWidth, clockHeight)
' create inner rectangle
Dim w, h, x, y As Integer
getInnerRectSizeAndLocation(w, h, x, y)
InnerRect = New Rectangle(x, y, w, h)
' draw goals string inside inner rect
InnerStringBrush = Brushes.Cyan
Gclock.DrawString(getPercentString(), New Font("ARIAL", InnerStringFontSize, FontStyle.Bold), InnerStringBrush, InnerRect)
' create outer square
OuterSquarePen = New Pen(OuterSquarePen_Color, OuterSquarePen_Width)
Gclock.DrawArc(OuterSquarePen, OuterRect, 1.0F, 360.0F)
' create inner square
InnerSquarePen = New Pen(InnerSquarePen_Color, InnerSquarePen_Width)
Dim sweepAngle As Short = getSweepAngleFromGoalsPercent()
Gclock.DrawArc(InnerSquarePen, OuterRect, -90.0F, sweepAngle)
End Sub
Private Sub getInnerRectSizeAndLocation(ByRef w As Integer, ByRef h As Integer, ByRef x As Integer, ByRef y As Integer)
' values for test
w = 40
h = 40
x = 64
y = 65
End Sub
Private Function getPercentString() As String
Return PercentOfGoals.ToString() & "%"
End Function
Private Function getSweepAngleFromGoalsPercent() As Single
' value for test
Return 0.0F
End Function
End Class
You must subscribe to the panel's Paint event and perform all drawing there. The AddHandler statement is used to dynamically subscribe to events.
The Graphics class will not store any information about what you draw, so when your panel is redrawn everything you previously drew will be gone unless you draw it again. This is where the Paint event comes into play: it will be raised every time your panel is redrawn, passing an instance of a Graphics class in its PaintEventArgs so you can draw your stuff onto the panel again.
Public Sub Create()
' panel
clockPanel = New Panel()
clockPanel.Size = New Size(clockWidth, clockHeight)
clockPanel.BackColor = Color.Beige
' subscribe to the panel's paint event
AddHandler clockPanel.Paint, AddressOf clockPanel_Paint
End Sub
Private Sub clockPanel_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
Dim Gclock As Graphics = e.Graphics 'Local variable only, as the Graphics object might change.
' create outer rectangle
OuterRect = New Rectangle(xPos, yPos, clockWidth, clockHeight)
' create inner rectangle
Dim w, h, x, y As Integer
getInnerRectSizeAndLocation(w, h, x, y)
InnerRect = New Rectangle(x, y, w, h)
' draw goals string inside inner rect
InnerStringBrush = Brushes.Cyan
Gclock.DrawString(getPercentString(), New Font("ARIAL", InnerStringFontSize, FontStyle.Bold), InnerStringBrush, InnerRect)
' create outer square
OuterSquarePen = New Pen(OuterSquarePen_Color, OuterSquarePen_Width)
Gclock.DrawArc(OuterSquarePen, OuterRect, 1.0F, 360.0F)
' create inner square
InnerSquarePen = New Pen(InnerSquarePen_Color, InnerSquarePen_Width)
Dim sweepAngle As Short = getSweepAngleFromGoalsPercent()
Gclock.DrawArc(InnerSquarePen, OuterRect, -90.0F, sweepAngle)
End Sub
As you also might've seen I am constantly redeclaring a new Gclock variable in the Paint event. This is because the Graphics instance used to draw your panel with might change, so you shouldn't store it any longer than the time the Paint event lasts (so I highly recommend you remove the declaration in the top of your class).

Checking to see if Cursor is in rectangle bounds

I have a program that randomly sets the cursor that an x,y coordinate and then clicks. I have this function/sub that creates a rectangle based on certain parameters.
Private Sub drawTitleBarRectangle()
Dim titleBarRectangle As Rectangle = RectangleToScreen(Me.ClientRectangle)
Dim titleBarRectangleHeight As Integer = titleBarRectangle.Top - Me.Top
Dim titleBarRectangleWidth As Integer = Screen.PrimaryScreen.Bounds.Width
Dim titleBarRectangleTop As Integer = Screen.PrimaryScreen.Bounds.Top
Dim titleBarBounds As New Drawing.Rectangle(0, 0, titleBarRectangleWidth, titleBarRectangleHeight)
End Sub
I want to check if when the cursor is at it's x,y position if it is within the bounds of the rectangle created from that function or not. Right now I have this:
drawTitleBarRectangle()
SetCursorPos(x, y)
If titleBarRectangle.Contains(x, y) Then
leftClick(800, 800)
End If
The Private titleBarRectangle is from a global variable that I declare as Private titleBarRectangle As New Drawing.Rectangle I'm not too sure why it's there to be honest...
Any help would be appreciated.
All of the variables in the initial method you've listed are local variables. This means they are simply discarded when that method exits. You need to update the class level variable you've declared by making an assignment instead of a declaration. With that in mind, it should look more like:
Public Class Form1
Private titleBarRectangle As Rectangle
Private Sub drawTitleBarRectangle()
Dim rc As Rectangle = Me.RectangleToScreen(Me.ClientRectangle)
Dim titleBarRectangleHeight As Integer = rc.Top - Me.Top
titleBarRectangle = New Rectangle(Me.Location.X, Me.Location.Y, Me.Width, titleBarRectangleHeight)
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
drawTitleBarRectangle()
Debug.Print(titleBarRectangle.ToString)
ControlPaint.DrawReversibleFrame(titleBarRectangle, Color.Black, FrameStyle.Dashed)
Dim x As Integer = titleBarRectangle.Location.X + titleBarRectangle.Width / 2
Dim y As Integer = titleBarRectangle.Location.Y + titleBarRectangle.Height / 2
Cursor.Position = New Point(x, y)
If titleBarRectangle.Contains(Cursor.Position) Then
Debug.Print("It's in there!")
End If
End Sub
End Class
Notice how the last line in the method will use the class level variable instead of a local since we don't have Dim in front of it.