Resolved Picturebox events

ideprize

Well-known member
Joined
Oct 19, 2011
Messages
96
Programming Experience
10+
Hi All
Trying to implement a confirmation sequence regarding the loading of an image in a picturebox. I need to know this for once the image is loaded I am going to print it using the printdocument_printpage sub of the printdocument class. I am trying to use the picturebox events namely the picturebox1_loadcompleted event. Below is the code stream I am using. I am thinking that the loadasync(file) should initiate the event stream but that's not happening. Of course I have installed the event handlers but I guess I am missing something like "the fire command". Any insight would be greatly appreciated.

imgin = False 'boolean variable
PictureBox1.Image = Nothing
PictureBox1.WaitOnLoad = False
PictureBox1.LoadAsync("image")
Do While imgin = False
Loop
'print the pricturebox

Private Sub PictureBox1_LoadCompleted(sender As Object, e As System.ComponentModel.AsyncCompletedEventArgs) Handles PictureBox1.LoadCompleted
'signal image loaded
imgin = True
End Sub

As you can see I am trying to use a simple semaphore scheme using the loadcompleted event. I have tried the
Do While PictureBox1.Image Is Nothing
Loop
which does basically does nothing. When I saw the loadcompleted event I thought for sure this would "do the trick". Thanks in advance.
Ideprize
 
Last edited:

ideprize

Well-known member
Joined
Oct 19, 2011
Messages
96
Programming Experience
10+
Thanks for your response JohnH.
The code architecture will not accommodate printing from LoadComplete. There are cases when the Picturebox is not to be printed (only viewed) but more problematic is the picturebox load is "2 levels down" - it is akin to a showdiaglog configuration requirement. I need to print the picturebox (maybe) and then continue on through the code working up through the levels back to "0". I must admit that I don't understand the concept of the blocked UI and therefore no room for LoadComplete to report back. Are we talking about too tight of a cycle time by the loop? I have verified that the LoadComplete event is being reached. The setting of the Class level variable in the LoadComplete event to permit continued execution in the sub that executes the picturebox load and the printdocument function is exactly what I need. In short there is a lot of code to execute AFTER the picturebox print and last but not least the picturebox print is in an outer loop because there may be a number of images to print, i.e. page 1, page 2, page 3, etc. If the LoadComplete event prints the document then it has to "send me" back to a particular entry point in the "calling" routine.
Respectfully,
Ideprize
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
I must admit that I don't understand the concept of the blocked UI
You shouldn't do any processing in UI thread that may take more than a split second, use a background thread for that. Use for example a AutoResetEvent to signal the worker thread that it can continue when LoadComplete happens.
 

ideprize

Well-known member
Joined
Oct 19, 2011
Messages
96
Programming Experience
10+
Again thanks for the response JohnH.
Looks like I have lots of learning to do. I'll be back once I have implemented the UI/Worker thread choreography.
Respectfully,
Ideprize
 

ideprize

Well-known member
Joined
Oct 19, 2011
Messages
96
Programming Experience
10+
To JohnH:
Based upon your suggestion I am trying to implement an AutoResetEvent "handshake". Here is the sample code:
Imports System
Imports System.Data
Imports System.Windows.Forms
Imports System.Windows.Forms.Control
Imports System.IO
Imports System.Threading
Public Class Form1
Dim trfl As String
Dim event_1 = New AutoResetEvent(False)

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load

End Sub

Private Sub btngetpic_Click(sender As Object, e As EventArgs) Handles btngetpic.Click
trfl = "c:\apps\misc\scan\19\1873-191.tif"
PictureBox1.WaitOnLoad = False
PictureBox1.LoadAsync(trfl)

event_1.WaitOne()

MsgBox("pic is loaded - main UI")

End Sub

Private Sub PictureBox1_LoadCompleted(sender As Object, e As System.ComponentModel.AsyncCompletedEventArgs) Handles PictureBox1.LoadCompleted

event_1.set()

End Sub
End Class
All that I have read says this is the simplest implementation of the AutoResetEvent thread. All that happens is the LoadCompleted event never fires and consequently the program hangs???
For clarification I have a button on the form that starts the picturebox1 load sequence. If I should have started a new post then I apologize.
Respectfully,
Ideprize
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
insertcode.png


documentation said:
WaitOne()Blocks the current thread
Do you think it is wise to use this in UI thread?
Based upon your suggestion
No, my suggestion was that you use LoadCompleted event handler to signal your processing that you say need to wait for LoadCompleted to happen. It is your processing thread that needs to wait.
 

ideprize

Well-known member
Joined
Oct 19, 2011
Messages
96
Programming Experience
10+
Again thanks for the response JohnH.
I am confused for in my interpretation this is exactly what I am doing. I halt the UI processing thread with the event_1.waitone() call and then release that "sleep state" once the LoadCompleted event fires by virtue of the event_1.set() call. It is the UI code that continues on and executes the print function. What I am ultimately trying to do is prevent the print code in the UI code from printing a picturebox that is not loaded. Further tests are indicating to me that the LoadCompleted event is a "second order event" relative to the .waitone call and therefore the "connection" between ".waitone()" and ".set()" is broken. Meaning if I could call the LoadCompleted event directly from the "button_click" event prior to the waitone command it would work but of course this would make the LoadCompleted event meaningless. I'm still thinking. Again, thanks JohnH.
Respectfully,
Ideprize
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
It sound really complicated to me, and I can't imaging the code flow here, anyway you must never block UI thread.

I don't see why you would try to print a PictureBox, its purpose is to show an Image, surely it is the image that you would want to print? This is done with DrawImage method of e.Graphics in printing.
 

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
132
Location
UK
Programming Experience
10+
You seem to be missing the point here. Your UI is not for doing work, it is for displaying what your working code has completed doing and displaying it generally from the implementation of an interface.

If you are updating your UI, you should always update your UI from a non UI thread, and not from the UI Thread itself. The reason you should do this, is so -not- to block your UI thread from becoming non responsive. This can happen if you execute long running tasks on the UI thread or by executing an infinite loop on that same thread. It seems to be that you have deliberately over complicated something that could be made so simple.
 

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
132
Location
UK
Programming Experience
10+
What you are looking at stepping into here is multithreading. There is no reason for you to use LoadAsync either. But if you want you could try this, and integrate your logic to fit with what you're doing :
Safe Code:
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        'Lets start off by setting the picturebox image layout :
        PictureBox1.BackgroundImageLayout = ImageLayout.Stretch
        'Next we want to create a new thread to execute.
        'This New Thread will set NonUIThread for threadstart, and pass the parameters of your file to the new thread.
        Dim UodateThread = New Thread(Sub() NonUIThread(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "banner.jpg")))
        'PathCombine combines the path of the desktop folder with the file name. Then we start the thread we created above.
        UodateThread.Start()
    End Sub
    'We need to be able to delegate the invoke method in a thread-safe method
    Public Delegate Sub ThreadSafeCallBack(ByVal text As String)
    Public Sub NonUIThread(ByVal updateImgPath As String)
        'This thread does not operate on the UI thread, and so we need to invoke the control using the delegate to put us
        'back on talking terms with UI thread by passing the new image path to SetNewImagePath where it will be updated.
        PictureBox1.Invoke(New ThreadSafeCallBack(AddressOf SetNewImagePath), New Object() {updateImgPath})
    End Sub
    Private Sub SetNewImagePath(ByVal text As String)
        'With thanks to our delegate, we can safely set the image path on the UI
        PictureBox1.BackgroundImage = Image.FromFile(text)
        TextBox1.Text = "Image set from NONUI Thread" 'This is just for example, you can remove this line.
    End Sub
As you can see, the code is commented for your understanding, but if you need to know something that you feel isn't explained, then let me know. Notice if you call NonUIThread and try to set one of your controls values from within it, it will not allow you and it will throw an exception of System.InvalidOperationException: 'Cross-thread operation not valid if you tried something like :
Non Safe Code:
Public Sub NonUIThread(ByVal updateImgPath As String)
        TextBox1.Text = "Foo"
        'This thread does not operate on the UI thread, and so we need to invoke the control using the delegate to put us
        'back on talking terms with UI thread by passing the new image path to SetNewImagePath where it will be updated.
        PictureBox1.Invoke(New ThreadSafeCallBack(AddressOf SetNewImagePath), New Object() {updateImgPath})
    End Sub
That is because the TextBox1 control was not created on this thread, and since this was a new thread, created in our button click event, we don't have any business trying to access controls on the UI thread from the thread NonUIThread simply because this method is running on an independently created and separate thread to that of the UI. If we wanted to update the TextBox1 control from this method, we would need to invoke it. Just as we have done with the PictureBox control. You would also need to set a new delegate if you are passing anything other than a string, and specify a new method similar to SetNewImagePath with whatever logic you want to apply for the new control you wish to update.

Tested and working :

Screenshot_45.jpg


I think I have covered what you need to know here, but to get back onto your code...
At the top of your file, you need to add these two lines :
VB.NET:
Option Strict On
Option Explicit On
It's just a pity Microsoft didn't make that a mandatory setting for VB.Net. If you get any errors, its your own fault for not learning to write explicit code and if you want to know more about these settings, read : Option Strict Statement (Visual Basic) and Option Explicit Statement (Visual Basic)

Looking at what I've given you, if you wanted to run an infinite loop, which wouldn't affect your UI, you would set to run in this method : Public Sub NonUIThread(ByVal updateImgPath As String) and if any code you write there requires interacting with the UI, you will need to follow the same principle approach as I did in the first block of code above.

Hope this explanation and example helps you understand.
 

ideprize

Well-known member
Joined
Oct 19, 2011
Messages
96
Programming Experience
10+
Thanks for all the feedback Sheepings - I will digest completely. I must admit that my programming style does not conform to the UI paradigm that you have described. I will blame that on 50 years of procedural coding - still learning the OOPS paradigm. I do understand now the philosophy regarding the UI. As it turns out JohnH identified the fix for my predicament in his last post - I was using the picturebox image in the DrawImage function thus the dependency to "know" it was in the picturebox. By using the Image.FromFile(file) function that problem goes away. So JohnH solved the most pressing problem. With regards to writing Explicit code if you are referring to the sanctity of the UI then you are correct it is my fault. The Strict On switch is set by default, however, if I am correct. I do always make sure all variables are defined and used. That has been my practice all my programming life even with the languages of old, Fortran, PL/1, Basic, etc. Of course in assembly it is another world. I will save all that you sent me and again I thank you for all of your input.
 

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
132
Location
UK
Programming Experience
10+
You're welcome. However, you should also take note of the advice regarding printing an image :
I don't see why you would try to print a PictureBox, its purpose is to show an Image, surely it is the image that you would want to print? This is done with DrawImage method of e.Graphics in printing.
I assume John might be talking about PrintDocument and PrintDialog from the Toolbox's list of controls
 

ideprize

Well-known member
Joined
Oct 19, 2011
Messages
96
Programming Experience
10+
Hi Sheepings
I did take JohnH's suggestion. As part of the program mission the user needs to see the "image" before it is printed as confirmation. Since I was using the picturebox control to do this I simply used the picturebox.image as the first augument to the Drawimage method. I have changed that to the actual disk file image. I did state that to you in my other response "as it turns out JohnH identified the fix for my predicament". Simply filling my toolbag. And yes the print function is using the PrintDocument control. Again thanks for sharing your expertise. I am in the process of invalidating the claim that "you can't teach an Old Dog New Tricks"!
Respectfully,
Ideprize
 

ideprize

Well-known member
Joined
Oct 19, 2011
Messages
96
Programming Experience
10+
Thanks JohnH
Actually you solved my problem with your last post when you pointed out I didn't have to print the PictureBox. Printing the PictureBox was what set the issue up in the first place. Some times the PictureBox was not completely "full" and this would result in a "pixelated mindcraft" output. But as you pointed out all I needed to do was refer to the disk file directly by loading it into an image type variable. I still need the PictureBox display for the user but the uncertainty of the printed output is gone! I will look into the Task.Run command, however. I am going to mark the thread as resolved.
Respectfully,
Ideprize
 
Top Bottom