Public Class Form1
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
e.Graphics.TranslateTransform(150, 150)
For x As Integer = 0 To 59
e.Graphics.DrawLine(Pens.Black, 0, 0, 0, -50)
e.Graphics.RotateTransform(6)
Next
e.Graphics.ResetTransform()
End Sub
End Class
Very cool animated gifs work on the forum! BTW the code runs smoother than the gif in this case.
Code:
Public Class Form3
Private WithEvents Timer1 As New System.Windows.Forms.Timer With {.Interval = 30, .Enabled = True}
Private Angle, FrameCount As Double
Private SW As New Stopwatch
Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ClientSize = New Size(300, 300)
DoubleBuffered = True
sw.Start()
End Sub
Private Sub Form3_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
With e.Graphics
Dim x, y As Single
Dim r As Single = 100
.Clear(Color.Black)
.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
Using p As New Pen(Color.Firebrick, 4),
br As New SolidBrush(Color.LightGoldenrodYellow),
f As New Font(Font.Name, 18, FontStyle.Bold)
'calc point on circle
x = CSng(150 + (r * Math.Cos(Angle / 57.3)))
y = CSng(150 + (r * Math.Sin(Angle / 57.3)))
.DrawLine(p, 150, 150, x, y)
.FillEllipse(br, x - 10, y - 10, 20, 20)
.DrawString(Angle.ToString("f1") & " deg", f, Brushes.White, 2, 2)
.DrawString((sw.ElapsedMilliseconds / FrameCount).ToString("f1") & " fps", f, Brushes.White, 2, 30)
End Using
End With
End Sub
Private Sub timer1_Tick(sender As Object, e As EventArgs) Handles timer1.Tick
Angle += 1
FrameCount += 1
If Angle >= 360 Then
Angle -= 360
End If
Refresh()
End Sub
End Class
Last edited by tommytwotrain; Jan 24th, 2019 at 01:41 PM.
1. The Paint event gives you a fresh buffer to draw on so all your previous drawing is gone. Clearing the buffer with a color can be redundant, and of course takes some time. To save a bit of time, set the Backcolor of the object you're painting at initialization and you will be given a buffer filled with that color when you get the paint event, so no reason to clear it again yourself.
2. You may as well use the prebuilt LightGoldenrodYellow brush provided by the Brushes class, rather than creating one in your Using statement.
3. Your frames per second calculation is incorrect. The "frames per second" means "frames / second". You have "milliseconds / frames" which is the inverse and incorrectly scaled.
A easy verification is if you set the Timer Interval to 1000 (once per second), you see you report an fps of around 1000.
So, switch the values, and convert milliseconds to seconds.
Here's the code with those three changes. I set the interval to 1, so that you draw as quick as the timer will fire. On most machines, you'll see the rate settle at 64 fps. That is because the timer is synced to a 64hz clock. I have seen machines where the clock will fire faster, but that is usually because some piece of software adjusted the base timer interval with an api call. Since the base timer interval is a system wide resource, a change through the api affects all processes that use that clock.
Code:
Private WithEvents Timer1 As New System.Windows.Forms.Timer With {.Interval = 1, .Enabled = True}
Private Angle, FrameCount As Double
Private SW As New Stopwatch
Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackColor = Color.Black
ClientSize = New Size(300, 300)
DoubleBuffered = True
SW.Start()
End Sub
Private Sub Form3_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
With e.Graphics
Dim x, y As Single
Dim r As Single = 100
' .Clear(Color.Black)
.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
Using p As New Pen(Color.Firebrick, 4),
f As New Font(Font.Name, 18, FontStyle.Bold)
'calc point on circle
x = CSng(150 + (r * Math.Cos(Angle / 57.3)))
y = CSng(150 + (r * Math.Sin(Angle / 57.3)))
.DrawLine(p, 150, 150, x, y)
.FillEllipse(Brushes.LightGoldenrodYellow, x - 10, y - 10, 20, 20)
.DrawString(Angle.ToString("f1") & " deg", f, Brushes.White, 2, 2)
.DrawString((FrameCount / (SW.ElapsedMilliseconds / 1000)).ToString("f1") & " fps", f, Brushes.White, 2, 30)
End Using
End With
End Sub
Private Sub timer1_Tick(sender As Object, e As EventArgs) Handles timer1.Tick
Angle += 1
FrameCount += 1
If Angle >= 360 Then
Angle -= 360
End If
Refresh()
End Sub
A side note: Because, the base clock is 64hz, then any interval that you choose will have to sync to one of the pulses of the 64hz clock, meaning that the interval you get is some multiple of a 64th of a second, i.e. 1/64, 2/64, 3/64, 4/64, ... of a second, or in other words 64hz, 32hz, 21.33 hz, 16hz, etc...
The true interval in milliseconds then is 15.625, 31.25, 46.875, 62.5, etc...
So, while you might put an interval of 20 in your example, and in theory should get 50 fps, since 20 falls between 15.625 and 31.25, you will get the 31.25 interval (tick fires every 2/64ths of a second) which will be 32 fps instead of 50 fps.
p.s. You're manually calculating the end point of the line using sin, cos to implement the rotation, so is a different example of a way of implementing rotation, rather than taking advantage of the matrix transforms. I modified your code a bit to use the matrix as a study for comparison between the two approaches.
If the lines that did the text were left where they were, they would rotate around the center, so I moved them to before the transforms. Of course another option would have been to reset the matrix before printing the text.
Since the matrix is changing the coordinate system, the line endpoints and the ellipse location can be literal constants, rather than calculated values.
Code:
Private Sub Form3_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
With e.Graphics
' Dim x, y As Single
Dim r As Single = 100
' .Clear(Color.Black)
.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
Using p As New Pen(Color.Firebrick, 4),
f As New Font(Font.Name, 18, FontStyle.Bold)
.DrawString(Angle.ToString("f1") & " deg", f, Brushes.White, 2, 2)
.DrawString((FrameCount / (SW.ElapsedMilliseconds / 1000)).ToString("f1") & " fps", f, Brushes.White, 2, 30)
.TranslateTransform(150, 150)
.RotateTransform(Angle)
'calc point on circle
' x = CSng(150 + (r * Math.Cos(Angle / 57.3)))
' y = CSng(150 + (r * Math.Sin(Angle / 57.3)))
.DrawLine(p, 0, 0, r, 0)
.FillEllipse(Brushes.LightGoldenrodYellow, r - 10, -10, 20, 20)
End Using
End With
End Sub
Last edited by passel; Jan 25th, 2019 at 01:22 AM.
I don't know whether passel's post covered this but I'd rather just write than read and then maybe write anyway. Note that the RotateTransform is relative to the origin of the canvas for the Graphics object. That is why you'll generally see a TranslateTransform first when doing this sort of thing. That will translate the origin to the centre of your drawing first and then any rotation is around the centre of your drawing. For that reason, you then draw from the origin. The origin would normally be at the top, left corner of the control.
I dont know why you guys and gals have to nit pick everything until the original question of the OP is lost.
Sorry I messed up the timer it was giving the answer I wanted for the value set on the timer. I don't think you can prove some of your comments but thats all debatable and off topic.
However I appreciate your comments and agree with them for the most part.
Plus I don't want to argue with you I want to work with you to learn and have fun.
The example works. I wrote it in a few minutes. Stop taking the fun out of everything.
I dont know why you guys and gals have to nit pick everything until the original question of the OP is lost.
Because if you teach people the wrong thing now and they don't know it's wrong, it can mean pain for them and us latter on. A prime example is GDI+ tutorials. There are tons of them around that don't mention the Paint event but rather call CreateGraphics and possibly don't even dispose the Graphics object created. I've lost count of the number of times that people post here with code that does that and have no inkling that they're doing something bad. Often just that is what's causing the issue they're asking about, like drawing disappearing at random times. The people who wrote those tutorials probably thought that it would take all the fun out of it to have to explain raising the Paint event or disposing objects. They probably thought that they were doing the people reading what they wrote a favour when, in fact, they were just being lazy. No offense. Just my opinion.
PS So anyhow... how about improve the animation with your comments (still point them) and etc and then post the gif so we can see it? Or make your own original etc. I love that! Each post is an improvement example of the original question. Its fun!
Here's what I ended up with, it's not going to light up the VB community or win any awards but I'm happy with it and I know a little bit more about graphics.
Code:
Option Explicit On
Imports System.Drawing.Drawing2D
Public Class Form1
Private WithEvents Timer1 As New System.Windows.Forms.Timer With {.Interval = 1, .Enabled = True}
Private Angle, FrameCount As Double
Private SW As New Stopwatch
'Dim drawLine As Boolean = False
Dim R, G, B As Integer
Dim point2 As New Point()
Dim colorPen As Pen
Private circleCenter, lineCenter As Point
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
DoubleBuffered = True
SW.Start()
End Sub
Function getRandom(num As Integer) As Integer
'Initialize the random-number generator.
Randomize()
'Generate random value between 0 and 255.
num = CInt(Int((255 * Rnd())))
getRandom = num
End Function
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Angle += 1
FrameCount += 1
If Angle >= 360 Then
Angle -= 360
End If
If Angle = 265 Then
'Generate 3 random r,g,b values
R = getRandom(R)
G = getRandom(G)
B = getRandom(B)
' Create pen with random color size 3.
PictureBox1.BackColor = Color.FromArgb(255, R, G, B)
End If
PictureBox1.Refresh()
End Sub
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
With e.Graphics
Dim x, y As Single
Dim r As Single = 100
Dim cp As Integer = PictureBox1.Width / 2
' .Clear(Color.Black)
.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
Using p As New Pen(Color.BlueViolet, 6),
br As New SolidBrush(Color.Black),
f As New Font(Font.Name, 18, FontStyle.Bold)
'calc point on circle
x = CSng(cp + (r * Math.Cos(Angle / 57.3)))
y = CSng(cp + (r * Math.Sin(Angle / 57.3)))
p.StartCap = LineCap.RoundAnchor
p.EndCap = LineCap.ArrowAnchor
.DrawLine(p, cp, cp, x, y)
.FillEllipse(br, cp - 10, cp - 10, 20, 20)
' .DrawString(Angle.ToString("f1") & " deg", f, Brushes.White, 2, 2)
'.DrawString((SW.ElapsedMilliseconds / FrameCount).ToString("f1") & " fps", f, Brushes.White, 2, 30)
End Using
End With
End Sub
Private Sub PictureBox2_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox2.Paint
circleCenter = New Point(PictureBox2.Width / 2, PictureBox2.Height / 2)
Dim rc As New Rectangle(circleCenter, New Size(1, 1))
For i = 0 To 90
'Generate 3 random r,g,b values
R = getRandom(R)
G = getRandom(G)
B = getRandom(B)
' Create pen with random color size 3.
colorPen = New Pen(Color.FromArgb(255, R, G, B), 2)
'Draw multi - coloured circle
e.Graphics.DrawEllipse(colorPen, rc)
rc.Inflate(1, 1)
Next
End Sub
Private Sub PictureBox3_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox3.Paint
lineCenter = New Point(70, 70)
e.Graphics.TranslateTransform(PictureBox3.Width / 2, PictureBox3.Height / 2)
For i = 0 To 100
'Generate 3 random r,g,b values
R = getRandom(R)
G = getRandom(G)
B = getRandom(B)
' Create pen with random color size 3.
colorPen = New Pen(Color.FromArgb(255, R, G, B), 6)
colorPen.StartCap = LineCap.ArrowAnchor
colorPen.EndCap = LineCap.RoundAnchor
' Create points that define line x,y
point2.X = 0
point2.Y = 0
' Draw lines to screen.
e.Graphics.DrawLine(colorPen, lineCenter, point2)
e.Graphics.RotateTransform(5)
Next
e.Graphics.ResetTransform()
End Sub
End Class
The picture in the middle moves around and when at 12.00 the background changes color will add more to it as I keep on playing. Will also now look at Passels code.
I reckon that you'll find an example or two if you look back through the 101k+ posts I've made here over the past 15 years. Examples are good but if you only do the stuff that you find fun then you may be doing as much hard as good if you lead a beginner down the wrong path with misinformation. I'm not saying that that happened here (haven't looked at your example so don't know). I was just answering the question you asked... and playing off the end of your own post. I didn't call you lazy but you chose to infer that. Guilty conscience?
@Mallard8, I haven't looked closely at your code but something that jumps out immediately is that, if you want to generate random numbers in VB.NET, you should acquaint yourself with the Random class.
Tried to show it working but it just shows a link to imgure to see it
Mall,
You mean you tried to post an animated gif of the screen? Did you add it to your post as gif image from your system or what? Is that what you mean? What do you mean exactly? There may be rules. Is it too big of a file?
I uploaded it to imgure and copied the url and it just gave the link with an image I don't think they accept imgure, not a problem really.
What I am having a problem with is how to get these two lines on to a Label
Code:
Private Sub Label1_Paint(sender As Object, e As PaintEventArgs) Handles Label1.Paint
Dim x, y As Single
With e.Graphics
Dim r As Single = 100
Using p As New Pen(Color.BlueViolet, 6),
br As New SolidBrush(Color.Black),
f As New Font(Font.Name, 18, FontStyle.Bold)
.DrawString(Angle.ToString("f1") & " deg", f, Brushes.White, 2, 2)
.DrawString((FrameCount / (SW.ElapsedMilliseconds / 1000)).ToString("f1") & " fps", f, Brushes.White, 2, 30)
x = CSng(r * Math.Cos(Angle / 57.3))
y = CSng(r * Math.Sin(Angle / 57.3))
End Using
End With
End Sub
They show but don't update as the line moves around.
What an idiot!
I was placing Refresh() in the label sub, When in fact we need to update the label with each click of the timer.
All sorted now
Thanks
Going to close the thread and have a break.
Function getRandom(num As Integer) As Integer
'Generate random value between 0 and 255.
getRandom = (Rand.Next(256))
End Function
That is VB6 (and earlier) syntax. It works, but with .Net, generally the Return statement is used which mimics other languages.
Code:
Function getRandom() As Integer
'Generate random value between 0 and 255.
Return Rand.Next(256)
End Function
Also, since you're using a fixed value, the function wouldn't be necessary and it would be better to just use Rand.Next(256) instead of calling the function. Also, no need for the num parameter in the function call, since you're not using it.
But perhaps that was only for testing.
I suspect the function should be as follows to return a number between 0 and num, inclusively.
Code:
Function getRandom(num As Integer) As Integer
'Generate random value between 0 and num.
Return Rand.Next(num + 1)
End Function
Last edited by passel; Jan 25th, 2019 at 01:31 PM.
PS So anyhow... how about improve the animation with your comments (still point them) and etc and then post the gif so we can see it? Or make your own original etc. I love that! Each post is an improvement example of the original question. Its fun!
...
Any comments I made in regard to your code wouldn't improve the animation so nothing new to see in a gif of the results. If fact, the point of converting your example from using Sin, Cos to determine the end of the line, and my using the transformation matrix to do it was to specifically not see a difference in the result.
i.e., that my setting the background to black, removing the .Clear method call would not effect the result (so the .Clear is not necessary), and that the rotation can be done with the transformation matrix, which can be easier especially if you have more complicated things to position and rotate, than using sin and cos to rotate the points yourself. I used sin and cos to rotate points for many years, the first time was on an Apple II at the ComputerLand store in Virginia Beach in 1978.
As for other examples of graphics and rotations, I've posted a few of them as well on the forum over the last few years in response to various threads. I thought of posting links to some of them here, but didn't because I thought they might be a bit overwhelming at this point. But, they can be of interest just to see some of the things you can do, even if you don't want to dive into the code at this point. I always think its best to build up from the basics rather than jump into the deep end.
But, I guess I'll post links to three threads, and you can pick what examples you might want to take a look at from them. There is overlap between the threads, but there should be at least four interesting examples between the three of them.
The VB_Drawing_Demo.zip is an interesting example because I embedded a step mode option in it (wrote for a co-worker to help get familiar with some of the graphic methods). I believe that is probably described a bit more in that thread (and in the code as well).
I hope you find these few examples entertaining, out of a larger pool of examples that I've posted, and perhaps can add to your knowledge eventually.
I firmly believe that you shouldn't be learning from one source, so I'm not promoting myself as the authority on all things graphic, far from it. The more sources of input you can find, and compare what you learn from each, will allow you to corroborate what you hear from the various sources, and determine for yourself what you understand and what you practice, and what may not be a good practice.
Also, since you're using a fixed value, the function wouldn't be necessary and it would be better to just use Rand.Next(256) instead of calling the function. Also, no need for the num parameter in the function call, since you're not using it.
But perhaps that was only for testing.
I suspect the function should be as follows to return a number between 0 and num, inclusively.
The function is what I used in an earlier incarnation so is the num, I used a function because it is called from different subs but I'll ditch it and just use the Rand.next(256)
I'll also have a look at the links you suggest.