gdi and graphics

false74

Well-known member
Joined
Aug 4, 2010
Messages
76
Programming Experience
Beginner
I am completely confused onto using GDI/graphics; and I hope someone can help me out with this one. I have a form with 1 picturebox (picturebox1) and 1 button (button1)...

I am trying to create a simple program that drawstrings the current time, and keeps redrawing it; while having a button that lets you save it to a .png file.

this is the jumble of code i have made currently; but i do not know how to put it together and make it work; help, please and thankyou!
VB.NET:
        Dim graphics As Graphics = PictureBox1.CreateGraphics
        Dim font As New Font("Arial", 15, FontStyle.Bold)

        'draw time
        graphics.DrawString(Now, font, Brushes.Red, 0, 0)

        'draw graphics to bitmap
        Dim img As New Bitmap(720, 100, graphics)

        'save to file
        img.Save("C:\img.png", System.Drawing.Imaging.ImageFormat.Png)

        'dispose objects
        PictureBox1.CreateGraphics.Dispose()
        img.Dispose()
        graphics.Dispose()
        font.Dispose()
 
You should pretty much NEVER be calling CreateGraphics. If you want to draw on a control then you ALWAYS do it on that control's Paint event, either in the Paint event handler or in the OnPaint method of a custom control. There you use the Graphics object provided by the control via the PaintEventArgs. Whenever you want to change what's drawn on the control you call Invalidate one or more times to specify what part(s) of the control need to be repainted and then you call Update to raise the Paint event. If you want to repaint the entire control you can just call its Refresh method.
 
Thank you, I wasn't aware that it was bad to call upon CreateGraphics. So I've done this:
VB.NET:
 Private Sub Painter(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
        Dim fontbold As New Font("Arial", 15, FontStyle.Bold)

        'draw time
        e.Graphics.DrawString(Now, fontbold, Brushes.Red, 0, 0)

        'draw graphics to bitmap
        img = New Bitmap(720, 100, e.Graphics)
        PictureBox1.Image = New Bitmap(720, 100, e.Graphics)

        'save to file

        'dispose objects
        fontbold.Dispose()

    End Sub

But How do I save the graphics as an image on a Button click? I've tried doing this:
VB.NET:
 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        img.Save("C:\img.png", System.Drawing.Imaging.ImageFormat.Png)
    End Sub

but it just saves a blank image. What's up with that?
 
You're going about it all wrong. You shouldn't be using any Image object in the Paint event handler. You're drawing on the PictureBox itself, not on an Image. Only when you want to save should you be using an Image object. Think of a PictureBox like a photo frame. You can draw on the glass cover as much as you like without affecting the photo inside. Check out this example:

Very Simple Drawing Program
 
Thanks for the reply. I have looked at the code you linked to, its quite nice. However (unless I missed something) it does not completely help me with saving what has been draw onto the picturebox.
I cannot figure out how to draw the graphics to the picturebox and a to bitmap. You say I
shouldn't be using any Image object in the Paint event handler
. But I do not know any other way of transferring the graphics made, into a bitmap used for saving. I am sorry if I am missing something, I am relativity new to vb net and especially GDI with bitmaps and graphics ect ect...

VB.NET:
    Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
        'drawing here
        e.Graphics.DrawString(Now, New Font("Arial", 15, FontStyle.Bold), Brushes.Red, 0, 0)

        'keep image updated
        Me.PictureBox1.Invalidate()
    End Sub

And then transfer what has been made there to a button click event
to which then would probably have to convert the graphics to bitmap, then save. correct?
 
You are missing something. In that code of mine, the DrawLines method takes a Graphics object and uses it to draw lines. That Graphics object can come from anywhere. In my Paint event handler I call DrawLines to draw onto the PictureBox, while in the Save method I call DrawLines to draw onto an Image. You can do the same thing, i.e. use one method to draw on both PictureBox and Image. Once you've drawn on an Image you can then call its Save method to save it to a file.
 
Ok, I've looked at your code, and I understand what its doing. You have a method thats creating the graphics, then you call that method on the picturebox paint and have it draw onto its graphics. So far so good. Your save method then pretty much takes what has been drawn and adds that to the graphics drawn permanently, so it will be drawn every time. Got it. I did something similar to that:
VB.NET:
   Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
        Me.previewudt(e.Graphics)
    End Sub

    Private Sub previewudt(ByVal g As Graphics)

        'drawing here
        g.DrawString(Now, New Font("Arial", 15, FontStyle.Bold), Brushes.Red, 0, 0)

        'keep image updated
        Me.PictureBox1.Invalidate()

    End Sub


I am still looking at your code in th save method and I'm trying to understand exactly what is happening here; I'm not quite sure what 'Using' means or does.

VB.NET:
Using g As Graphics = Graphics.FromImage(Me.PictureBox1.Image)
            'Draw each line on the image to make them permanent.
            Me.DrawLines(g)
        End Using
 
'keep image updated
Me.PictureBox1.Invalidate()
This is not true, and it should not be called from Paint event handler. The responsibility of the Paint event handler is to paint when it is needed, this may be caused by things happening in your application or in system. When you have a need to force the control to redraw you use a combination of Invalidate/Update or Refresh methods, which then causes the Paint event to be raised and handled by your event handler. This is basically the same jmcilhinney explained in post 2. The need to force control to Paint is when what is to be painted is changed, for example if you are drawing the time string every second you can use a Timer with a second interval and in its Tick event handler you call Refresh.

Here are the help topics for Invalidate, Update and Refresh:
Control.Invalidate Method (System.Windows.Forms)
Control.Update Method (System.Windows.Forms)
Control.Refresh Method (System.Windows.Forms)

And you were wondering about Using statement: Using Statement (Visual Basic)
 
Timer with a second interval and in its Tick event handler you call Refresh.

Ah, I didn't know that either. Thanks mate! By doing that it significantly cut cpu usage a lot (since it updates less frequently than in the paint handler?)
My only problem is just getting the graphics draw from my graphics sub to a bitmap to be used in a save sub for a button click.

this doesn't work: I honestly don't know what I am doing at this point, as I've never tried this before.:confused:

VB.NET:
Private Sub Save()
        Dim g As Graphics = Graphics.FromImage(Me.PictureBox1.Image)
        Dim previewimg As Bitmap = New Bitmap(720, 100, g)
        previewimg.Save("C:\img.png", System.Drawing.Imaging.ImageFormat.Png)
    End Sub
 
You do what I said before: you write a method that takes a Graphics object and draws the time:
VB.NET:
Private Sub DrawTime(ByVal g As Graphics)
    g.DrawString(...)
End Sub
You can then call that method from the Paint event handler:
VB.NET:
Me.DrawTime(e.Graphics)
and from wherever you want to save an image:
VB.NET:
Using g As Graphics = Grapgics.FromImage(myImage)
    Me.DrawTime(g)
End Using
You need to create myImage first, then save it afterwards.
 
Right, you saying do this, correct?

VB.NET:
    Dim tempimg As Image
    Private Sub Save()

        Using g As Graphics = Graphics.FromImage(tempimg)
            Me.previewudt(g)
            tempimg.Save(imgpath, System.Drawing.Imaging.ImageFormat.Png)
        End Using
    End Sub

but i get an 'argumentnullexception was unhandled' error from "Graphics.FromImage(tempimg)" that since it is empty/null.

I'm, sorry for putting you guys through hell over this, but I'm trying to figure it out too.
 
You can't create a Graphics object from an Image that doesn't exist. As I said in my last post, you have to create the Image first, in whatever way is appropriate.

That said, there is a DrawToBitmap method that all controls have. If you call that on the PictureBox then you may find that that gives you just what you need without having to draw on the Image yourself.
 
OK; here what I have done:
VB.NET:
 Private Sub Save()

        Dim bmp As New Drawing.Bitmap(PictureBox1.Width, PictureBox1.Height)
        PictureBox1.DrawToBitmap(bmp, PictureBox1.ClientRectangle)
        bmp.Save(imgpath, System.Drawing.Imaging.ImageFormat.Png)

    End Sub

It works sweet! but this is the problem I had a long time ago that I am trying to avoid; it's that when it creates the image from the picturebox, it loses the alpha data, thus instead of a transparent png with the text on it, its a white background with text on it. I cannot have this.
The only way I can avoid this (that I know of) is getting the graphics originally (not after they are painted onto the picturebox) and then drawing to bitmap.
 
Ok. I re-read what you guys said. I and tried to do this:
VB.NET:
Private Sub previewudt(ByVal g As Graphics)
        'drawing here
        g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit
        'g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
        g.DrawString(Now, boldarial, Brushes.Black, 0, 0)

    End Sub
    Private Sub Save()
        Dim bmp As New Bitmap(PictureBox1.Width, PictureBox1.Height)
        Dim gfx As Graphics = Graphics.FromImage(bmp)
        previewudt(gfx)
        bmp = New Bitmap(PictureBox1.Width, PictureBox1.Height, gfx)
        ' gr.DrawToBitmap(bmp, PictureBox1.ClientRectangle)
        bmp.Save(imgpath, System.Drawing.Imaging.ImageFormat.Png)

    End Sub

However, its not drawing the graphics onto the bitmap and just creates an empty image file.
 
It IS drawing the text onto the Bitmap. You just aren't saving the right Bitmap. Programming doesn't exist in a vacuum. It mimics real-world processes. Let's think of a Bitmap as a piece of paper and a Graphics as a pen. What does this code do?
VB.NET:
        Dim bmp As [B][U]New[/U][/B] Bitmap(PictureBox1.Width, PictureBox1.Height)
        Dim gfx As Graphics = Graphics.FromImage(bmp)
        previewudt(gfx)
        bmp = [B][U]New[/U][/B] Bitmap(PictureBox1.Width, PictureBox1.Height, gfx)
        bmp.Save(imgpath, System.Drawing.Imaging.ImageFormat.Png)
The first line gets a new piece of paper. That's fine. The second line gets a new pen that can draw on that paper. The third line draws the current time onto that paper with the pen. That's great because that's exactly what we want. Now the problems start. The next line throws away the piece of paper that we just draw on and gets another new piece of paper. We then save that second piece of paper, which we didn't draw anything on. Not surprisingly, the 'New' keyword is used to create a new object. If you already have a Bitmap and you just drew on it, you obviously don't need another new Bitmap. You want to save the one you've already got.

On an unrelated note, is 'previewudt' really an appropriate name for that method. The name of a method should reflect EXACTLY what that method does. There's no previewing going in that method. All it does is draw the current date and time, so that's what the name should indicate.
 
Back
Top