Using Only One ForEach Loop - vb.net

Please see the following Code:-
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
Parallel.ForEach(Segments.OfType(Of Segment),
Sub(segment)
Try
segment.DrawLine(e.Graphics)
Catch ex As Exception
End Try
End Sub
)
Parallel.ForEach(Ellipses.OfType(Of Ellipse),
Sub(ellipse)
Try
ellipse.DrawEllipse(e.Graphics)
Catch ex As Exception
End Try
End Sub
)
End Sub
Is it Possible to use only One ForEach loop instead of Two as shown above? If No, is there a way to Enhance the Speed of Execution by using async and await?

Here is a complete solution, using the same idea as #OlivierJacot-Descombes answer. Plus some additional inherited functionality. This solution means to test the speed differences between the methods discussed in the question and existing answer.
First, the classes
Public MustInherit Class Shape
Public Property Name As String
Public Property ForeColor As Color
Public Sub Draw(g As Graphics)
drawShape(g)
End Sub
Public Sub DrawSyncLock(g As Graphics)
SyncLock g
drawShape(g)
End SyncLock
End Sub
Protected MustOverride Sub drawShape(g As Graphics)
Public Sub New(foreColor As Color)
Me.ForeColor = foreColor
End Sub
End Class
Public Class Line
Inherits Shape
Public Sub New(foreColor As Color, startPoint As PointF, endPoint As PointF)
MyBase.New(foreColor)
Me.StartPoint = startPoint
Me.EndPoint = endPoint
End Sub
Public Property StartPoint As PointF
Public Property EndPoint As PointF
Protected Overrides Sub Drawshape(g As Graphics)
Using pen As New Drawing.Pen(ForeColor)
g.DrawLine(pen, StartPoint, EndPoint)
End Using
End Sub
End Class
Public Class Ellipse
Inherits Shape
Public Sub New(foreColor As Color, rect As RectangleF)
MyBase.New(foreColor)
Me.Rect = rect
End Sub
Public Property Rect As RectangleF
Protected Overrides Sub Drawshape(g As Graphics)
Using pen As New Drawing.Pen(ForeColor)
g.DrawEllipse(pen, Rect)
End Using
End Sub
End Class
The methods in the abstract class, Draw and DrawSyncLock only need to be one method, but they both exist for testing purposes.
To set up, I declare a list of shape and populate it with 10000 Lines and 10000 Ellipses. And set up the bitmap in PictureBox1
Public Class Form1
Private shapes As New List(Of Shape)()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
PictureBox1.SizeMode = PictureBoxSizeMode.StretchImage
PictureBox1.Dock = DockStyle.Fill
For i As Integer = 0 To 99
For j As Integer = 0 To 99
shapes.Add(New Line(Color.White, New PointF(i + j, i + j), New PointF(j + 10 + 4, j + 10 + 4)))
Next
Next
For i As Integer = 0 To 99
For j As Integer = 0 To 99
shapes.Add(New Ellipse(Color.White, New RectangleF(i + j, i + j, 10, 10)))
Next
Next
PictureBox1.Image = New Bitmap(500, 500)
Using g = Graphics.FromImage(PictureBox1.Image)
Dim r As New Rectangle(0, 0, 500, 500)
g.FillRectangle(New SolidBrush(Color.Black), r)
End Using
End Sub
Then I ran three different methods inside the Paint handler, with a stopwatch to time them
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
Dim sw As New Stopwatch()
sw.Restart()
Parallel.ForEach(shapes,
Sub(s)
Try
s.Draw(e.Graphics)
Catch
End Try
End Sub)
sw.Stop()
Console.WriteLine($"PForEachTry: {sw.ElapsedMilliseconds}")
sw.Restart()
For Each s In shapes
s.Draw(e.Graphics)
Next
sw.Stop()
Console.WriteLine($"ForEach: {sw.ElapsedMilliseconds}")
sw.Restart()
Parallel.ForEach(shapes, Sub(s) s.DrawSyncLock(e.Graphics))
sw.Stop()
Console.WriteLine($"PForEachSync: {sw.ElapsedMilliseconds}")
End Sub
What I found is that the Parallel.ForEach with a Try... empty Catch is extremely slow, not even in the same ballpark as the other two methods. About 10x! By the way, this was your original method, so no wonder you were trying to speed things up. Here is one sample:
PForEachTry: 1188
ForEach: 149
PForEachSync: 177
It's not even worth continuing to test that method, so I'll remove it and test just the other two. Here are the results, generated by resizing the form:
sw.Restart()
For Each s In shapes
s.Draw(e.Graphics)
Next
sw.Stop()
Console.WriteLine($"ForEach: {sw.ElapsedMilliseconds}")
sw.Restart()
Parallel.ForEach(shapes, Sub(s) s.DrawSyncLock(e.Graphics))
sw.Stop()
Console.WriteLine($"PForEachSync: {sw.ElapsedMilliseconds}")
ForEach: 68
PForEachSync: 229
ForEach: 75
PForEachSync: 121
ForEach: 89
PForEachSync: 139
ForEach: 79
PForEachSync: 140
ForEach: 74
PForEachSync: 140
ForEach: 83
PForEachSync: 138
ForEach: 75
PForEachSync: 137
ForEach: 79
PForEachSync: 124
ForEach: 128
PForEachSync: 164
ForEach: 63
PForEachSync: 127
In case the order in which they are called matters, I reversed the order, and checked again:
sw.Restart()
Parallel.ForEach(shapes, Sub(s) s.DrawSyncLock(e.Graphics))
sw.Stop()
Console.WriteLine($"PForEachSync: {sw.ElapsedMilliseconds}")
sw.Restart()
For Each s In shapes
s.Draw(e.Graphics)
Next
sw.Stop()
Console.WriteLine($"ForEach: {sw.ElapsedMilliseconds}")
PForEachSync: 303
ForEach: 189
PForEachSync: 149
ForEach: 89
PForEachSync: 241
ForEach: 79
PForEachSync: 138
ForEach: 86
PForEachSync: 140
ForEach: 77
PForEachSync: 146
ForEach: 75
PForEachSync: 237
ForEach: 159
PForEachSync: 143
ForEach: 97
PForEachSync: 128
ForEach: 69
PForEachSync: 141
ForEach: 86
Order doesn't matter. It's always slower (with 20000 shapes...)
The fact that you have settled on a Parallel.ForEach and SyncLock - the only thing it does is indicate that you shouldn't be running in parallel. Graphics instance methods are not thread-safe, so they won't benefit from multithreading when they are the only operation being performed in the thread. The additional overhead created when using Parallel.ForEach can be ignored when performing long-running tasks, but numerous short-lived operations are so quick that the overhead of delegating 20000 tasks starts to be counter-active. It makes sense when you have a few long-running tasks.
In short, parallel + a single locked operation is code-stink, and it's beneficial to understand why.
Based on the comment that ~500 shapes are used, I changed the code to generate 250 Lines and 250 Ellipses. This is the result:
PForEachSync: 3
ForEach: 4
PForEachSync: 3
ForEach: 1
PForEachSync: 4
ForEach: 2
PForEachSync: 4
ForEach: 1
PForEachSync: 3
ForEach: 2
PForEachSync: 3
ForEach: 2
PForEachSync: 4
ForEach: 1
PForEachSync: 3
ForEach: 1
PForEachSync: 3
ForEach: 2
PForEachSync: 4
ForEach: 2
The execution time is so short it probably doesn't matter much. But the parallel loop still takes a bit longer. Even if you find the parallel loop takes a bit less time, the design of synchronizing the sole operation in a parallel loop is counterintuitive.

It would be possible to use only one loop if all types of shapes were in the same collection. To enable this, you would need to have an abstract base class for shapes (or to let all the shapes implement a common interface).
Public MustInherit Class Shape
Public Property ForeColor As Color
... other common properties
Public MustOverride Sub Draw(g As Graphics)
End Class
Then you can derive the concrete shapes like this (with Line as an example):
Public Class Line
Inherits Shape
Public Property StartPoint As PointF
Public Property EndPoint As PointF
Public Overrides Sub Draw(g As Graphics)
Using pen As New Pen(ForeColor)
g.DrawLine(pen, StartPoint, EndPoint)
End Using
End Sub
End Class
The you can add all the kinds of shapes to a List(Of Shape) and loop it like this
Parallel.ForEach(Shapes,
Sub(shape)
shape.Draw(e.Graphics)
End Sub
)
The Draw methods should not throw exceptions. They would, e.g., if the pen was Nothing. But this would be a programming error that has to be fixed and not something that you should be caught at runtime. So, remove the Try-Catch.
As #djv has demonstrated, using parallelism is counterproductive here. So, use the collection in a single loop like this (and of course remove SyncLock and Try Catch):
For Each shape In Shapes
shape.Draw(e.Graphics)
Next
It is important to understand that you don't need to know whether the shape is a Line or an Ellipse. For lines the Line.Draw method will be called, for an ellipse the Ellipse.Draw will be called automatically. This is called Polymorphism.
If you can identify the type of a shape in the collection in different ways. E.g., with If TypeOf shape Is Line Then or by getting its type name with shape.GetType().Name. However, if you must do this, then you are probably doing something wrong. If you follow the OOP principles, you should be able to do everything in a polymorphic way.

Related

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.

How to Change index value

hello guys i have a problem with index value in my combobox i have these items
(A)
(B)
(C)
(D)
(E)
(F)
these items have index value like 0 1 2 3 4 and so on
i want to start index 83 84 99 45 22 ... ?
ComboBox1.Items.Add(line)
is there way to index start 83 84 99 45 22 ... ?
Or any suggestion from in My.Resources.TextFile1
(A),83
(B),84
(C),99
(D),45
(E),22
(F),10
and so on
Items,Index
can i do that like index=0 to 83 and 1 = 84 son on
sorry for bad english
Not entirely sure what you are trying to achieve, but from what I can gather you want to add "(A)", "(B)" and so on to the combobox, but you want the combobox's index to start from numbers that you choose.Not sure if you can do that as it's like a primary key and there to only keep things in order.With that being said, you could use a Dictionary to achieve this to some extent.Here is how I would do it, think of the key as the index value
Public Class Form1
Dim items As New Dictionary(Of Integer, String) ''your items Dictinary List
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
''add items here.
items.Add(83, "(A)")
items.Add(84, "(B)")
items.Add(99, "(C)")
items.Add(45, "(D)")
items.Add(22, "(E)")
items.Add(10, "(F)")
For Each item As String In items.Keys ''add the items key or value to the list.
ComboBox1.Items.Add(item)
Next
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
Label1.Text = items.Values(ComboBox1.SelectedIndex) ''we can select the value or key based on the items key or value that is allocated to it depending on which way you do it
End Sub
End Class
So now if you select an item from the combobox, the label will display the 'key' of the value that is selected.

How to rotate a line shape 90 Degrees?

I am going to have a form with 60 Line Shapes (Using Visual Studios PowerPack). I would like the user to be able to rotate the shapes 90 Degrees using the left and right buttons on the keyboard.
What would be the best way to do this? I have tried other methods however this amounts to 1000's of lines of code, I am still learning and I want to know the best practices.
Thanks a lot!
I'm assuming for the moment that you've already written the part to handle the geometry once, and are asking about how to re-use the code, without duplicating it for 60 lines. This matters because it's not 100% clear from the question whether you're rotating around the mid-point or around the starting point, as the LineShape type does make a distinction between the Starting and Ending points. Without that information, I can't write the geometry code for you.
The first part isn't so bad. We just setup a few methods that can handle rotating any line:
'Note that rotating a line 90 degrees around it's midpoint
' will give the same result whether you go clockwise or counterclockwise,
' but I figure you'll want to adapt this for other shapes later, or that
' you're rotating around the line's starting point
Private Sub RotateClockwise(ByVal line As LineShape)
'Code to rotate the passed line clockwise here
Dim x1 As Integer = line.X1
Dim y1 As Integer = line.Y1
Dim x2 As Integer = line.X2
Dim y2 As Integer = line.Y2
End Sub
Private Sub RotateCounterclockwise(ByVal line As LineShape)
'Code to rotate the passed line counter-clockwise here
End Sub
Private Sub LineShape_KeyDown(Byval sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs)
'Which line?
Dim line As LineShape = TryCast(sender, LineShape)
If line Is Nothing Then Exit Sub
'Left key?
If e.KeyCode = Keys.Left Then RotateCounterclockwise(line)
'Right key?
If e.KeyCode = Keys.Right Then RotateClockwise(line)
End Sub
This is where it gets tricky. Notice that the event handler above is missing the Handles keyword. We want to hookup the KeyDown event handler for all of your LineShape controls to this one method. This will be a bit repetitive, as it means one additional line of code for each line on your form, but it's better than needing to write the above code for all of your lines:
Dim Lines As New List(Of LineShape)()
Lines.Add(LineShape1)
Lines.Add(LineShape2)
'...
Lines.Add(LineShape60)
For Each Line As LineShape In Lines
AddHandler Line.KeyDown, AddressOf LineShape_KeyDown
Next
That code goes in your form's constructor after the InitializeComponent() method.
I could do better still if the LineShape type were a true control (For EAch Line In Me.Controls.OfType(Of LineShape)()), but the docs show this is actually a Component, and not a Control.
Alternatively, you can subclass LineShape and build "rotatability" into your new class as :
Imports Microsoft.VisualBasic.PowerPacks
Public Class MySmartLine
Inherits LineShape
Private Sub RotateClockwise()
'Code to rotate clockwise here
Me.X1 = ...
Me.X2 = ...
End Sub
Private Sub RotateAntiClockwise()
'Code to rotate anti clockwise here
End Sub
Protected Overrides Sub OnKeyDown(ByVal e As System.Windows.Forms.KeyEventArgs)
MyBase.OnKeyDown(e)
If e.KeyCode = Keys.Left Then
RotateAntiClockwise()
End If
If e.KeyCode = Keys.Right Then
RotateClockwise()
End If
End Sub
End Class
After building your project, a custom MySmartLine component will appear in your toolbox and you can use it in place of LineShape.

Trying to assign pictures to PictureBoxes in VB

I am trying to create a simple game, and first it needs to randomly load 16 PictureBoxes with images. I am not sure where the problem lies in this.
Public Class Form1
Private picArrows() As PictureBox = {pic11, pic12, pic13, pic14,
pic21, pic22, pic23, pic24,
pic31, pic32, pic33, pic34,
pic41, pic42, pic43, pic44}
Private Sub btnNew_Click(sender As Object, e As EventArgs) Handles btnNew.Click
'starts a new game
'declares RNG
Dim randGen As New Random
'uses RNG to determine arrow placement
For intPicBox As Integer = 0 To 15
Select Case randGen.Next(1, 5)
Case 1
picArrows(intPicBox).Image = My.Resources.Up
Case 2
picArrows(intPicBox).Image = My.Resources.Right
Case 3
picArrows(intPicBox).Image = My.Resources.Down
Case 4
picArrows(intPicBox).Image = My.Resources.Left
End Select
Next
End Sub
End Class
I get a NullReferenceException error on the line after Case X. Anyone know what I'm doing wrong?
I get a NullReferenceException error on the line after Case X
You cannot initialize your array like this:
Public Class Form1
Private picArrows() As PictureBox = {pic11, pic12, pic13, pic14,
pic21, pic22, pic23, pic24,
pic31, pic32, pic33, pic34,
pic41, pic42, pic43, pic44}
The Form has not been initialized yet, so it and all the controls on it have not been created yet. As a result, all those control references are going to be Nothing, leaving you with an array full of Nothings. The result is a NullReferenceException because Nothing does not have an Image property.
You can declare the array there, but you can only initialize it after the form's constructor runs (Sub New). Form Load is a good place:
Public Class Form1
Private picArrows As PictureBox()
' for best results you should use the same RNG over and over too:
Private randGen As New Random()
...
Private Sub Form_Load(....
picArrows = New PictureBox() {pic11, pic12, pic13, pic14,
pic21, pic22, pic23, pic24,
pic31, pic32, pic33, pic34,
pic41, pic42, pic43, pic44}
See also NullReference Exception in Visual Basic
Slightly different arrangement without the companion array:
Private Sub btnNew_Click(sender As Object, e As EventArgs) Handles btnNew.Click
With New Random
For col = 1 To 4
For row = 1 To 4
CType(Controls(String.Format("pic{0}{1}", col, row)), PictureBox).Image = {My.Resources.Up, My.Resources.Right, My.Resources.Down, My.Resources.Left}(.Next(0, 4))
Next
Next
End With
End Sub

Verify that an Object has a certain tag/property and if so, perform code based on that property

I am creating program in which your character must make his way along a maze.
How do I stop him from moving through the walls? (Both the walls and the character are picture boxes). i.e. when you press the Up key, if there is a wall in front of him, he doesn't move. At the moment, my code only changes the X and Y values of the picture box, so the character moves on top of the wall/through the wall. This is in Visual Basic 2010. Is there a way to scan the tag of the picture box and if it is of a cerain value, not move?
My code for movement so far is:
Private Sub frmLevel1_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp
If Tag <= 2 Then
Select Case e.KeyCode
Case Keys.Up
picP1.Top -= 36
Case Keys.Right
picP1.Left += 36
Case Keys.Down
picP1.Top += 36
Case Keys.Left
picP1.Left -= 36
Case Keys.W
picP2.Top -= 36
Case Keys.D
picP2.Left += 36
Case Keys.S
picP2.Top += 36
Case Keys.A
picP2.Left -= 36
End Select
End If
End Sub
This moves the character 1 grid space in the direction pressed.
I also have a timer that stops the character from moving past the outer borders:
Private Sub tmrBorders_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrBorders.Tick
If picP1.Left <= 39 Then
picP1.Left += 36
End If
If picP1.Left >= 583 Then
picP1.Left -= 36
End If
If picP1.Top <= 83 Then
picP1.Top += 36
End If
If picP1.Top >= 445 Then
picP1.Top -= 36
End If
If picP2.Left <= 39 Then
picP2.Left += 36
End If
If picP2.Left >= 583 Then
picP2.Left -= 36
End If
If picP2.Top <= 83 Then
picP2.Top += 36
End If
If picP2.Top >= 445 Then
picP2.Top -= 36
End If
End Sub
I'm just having problems for the inner borders. Is there possibly a way to reverse the direction of movement? or maybe record the last space that the character was on?
Link to MSDN inheritance explanation: msdn.microsoft.com/en-us/library/ms973803.aspx
Maybe something like this?
Instead of using a standard picture box, create your own class which inherits from the picturebox.
Public Class MYPICBOX
inherits picturebox
end class
then add someproperties to it to show it as impassable
Public Class MYPICBOX
inherits picturebox
Property IsImpassable as Boolean = true
end class
Populate your world with the MYPICBOX class inplace of picturebox
Now test if you can move through it
Sub MoveIfNoCollision(spriteToMove as mypicbox, Xmovement as integer, YMovement as integer)
'Variable to determine if the actual movement routine should be called
collision = False
'for this next bit of code, you should use something like a rectangle to artificially expand the bounds of the
'sprite to move and perform your check using it instead of the sprite to move object.
'or you could move it, run this check and then move it back if
'a collision is detected.
'check each mypicbox that is not the one targeted for movement
For Each pb as mypicbox In Me.Controls
If pb IsNot spriteToMove AndAlso spriteToMove.Bounds.IntersectsWith(pb.Bounds) andalso pb.impassable Then
'shouldn't move, mark it as failed and end the sub
collision = True
Exit For
end if
Next
IF collision = false
'safe to move. Call the actual move routine
MoveSpriteAndUpdateImage(spriteToMove, Xmovement, YMovement)
end if
end sub
EDIT:
Sub MoveSpriteAndUpdateImage(spriteToMove as mypicbox, Xmovement as integer, YMovement as integer)
'do stuff if needed prior to moving
'Move
spriteToMove.location = New Point(spriteToMove.location.X + XMovement, spriteToMove.location.Y + Ymovement)
'do stuff if needed after moving
endsub