Link to home
Start Free TrialLog in
Avatar of lep1
lep1

asked on

Multiple Picturesboxes or Instantiated UserControls to Show Multiple Anumated Gifs

I am currently using the code below to create dynamically new pictureboxes with an animated gif, so they can be shown near controls whose processes are running.   The method (watigifimg) is called before instantiating an object, which starts a threaded method from within) that does the work.   A hashtable is used to keep a directory of the current pictureboxes.  

The question is: Is there any advantage over using a usercontrol, which could simply contain the animated gif, and then be instantiated as an object and placed on Form1?

First things first, I have not finished how to dispose of each picturebox (via its name) and need a recommendation at the line below with "***-->" in it.  



Private Sub waitgifimg(ByVal wgname As String, ByVal onoff As Boolean, ByVal left As Integer, ByVal top As Integer)
        If onoff = True Then
            If htWaitGifs.ContainsKey(wgname) = True Then
                Cnt = htWaitGifs(wgname)
                htWaitGifs(wgname) = Cnt + 1
            End If
            If htWaitGifs.ContainsKey(wgname) = False Then
                htWaitGifs.Add(wgname, 1)
            End If
            Dim gifpb As New PictureBox
            gifpb.Image = My.Resources.ajax_loader
            gifpb.SizeMode = PictureBoxSizeMode.AutoSize
            gifpb.Visible = True
            gifpb.Left = left - 10
            gifpb.Top = top - 55
            ' gifpb.Width = 17
            ' gifpb.Height = 17
            gifpb.BringToFront()
            gifpb.Name = wgname
            Me.Controls.Add(gifpb)
        End If
        If onoff = False Then
            If htWaitGifs.ContainsKey(wgname) = True Then
                htWaitGifs.Remove(wgname)
                '***-->Need way to dispose of picturebox with name "wgname" here
            End If
        End If
    End Sub

Open in new window

Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

"Need way to dispose of picturebox with name "wgname" here"

As simple as:
       If onoff = False Then
            If htWaitGifs.ContainsKey(wgname) = True Then
                htWaitGifs.Remove(wgname)
                
                Dim ctl As Control = Me.Controls(wgname)
                If Not IsNothing(ctl) Then
                    ctl.Dispose()
                End If

            End If
        End If

Open in new window

I think a UserControl would be beneficial if it encapsulated BOTH the PictureBox and the Class (along with its contained Thread).  Then each UserControl has access to its own associated PictureBox (since it is contained within), and also has access to the associated Class and Thread.
Avatar of lep1
lep1

ASKER

Something that would also be tricky would be to set the threadname of the current instantiated class (represented by the waitgif) to the unique name of the waitgif.  

Is there a way to send a thread name to the threadpooler to perform an action when the thread (method) with a given name is automatically aborted?   I could dispose of the animated gif (pb) at the same time.
That's where the UserControl would come in really handy.  You could make your custom class raise a custom event when the thread is complete.  Make the UserControl subscribe to the custom event of the class and then it can dispose (or hide) the picturebox accordingly.
Avatar of lep1

ASKER

At present, let me go through the flow that starts a class (method), and spawns the animated gif.   I don't think I want to start the worker class in the class that starts the animated gif.

1. Instantiate worker class (call this "workerobject").   Load the waitgif and send the waitgifname as parameter to the object.

2. In the object, thread the method (of choice) and send the waitgifname to the method that's threaded.  

3.  When the method is done, that is, at the end of the method, call Form1's public waitgifimg method to dispose of the picturebox using waitgifname.


Question: if I make "waitgifname" a global string and update the name by adding waitgifname = waitgifname & NumWaiGifs, and the users starts another class (worker method) and changes the global waitgifname while the first method is running, will the running method see the change -- such that when you try to dispose of the waitgif it will use the wrong name?
Avatar of lep1

ASKER

Keep in mind, each worker class has a lot of methods, and I would rather not send a "waitgifname" to each as a parameter, and then go to the end of each method and paste the code to kill its waitgif.  

This is why I was asking for a way to send the waitgifname as a threadname for a given method (in an object) to the threadpooler so that the threadpooler can kill the waitgif when the thread automatically ends.  

In summary, at present there are hundreds of methods in classes.   I don't want to modify all these methods because of a waitgif.   Hence, I need to stay away from methods, and start animated gif based on where the user control is for the generic worker class, and then kill the waitgif when the thread is aborted.
Using global values is definitely NOT a good design decision.

I still say the UserControl is the way to go.  You can do #s 1 thru 3 from within the UserControl.  In fact, it might make sense to encapsulate a BackgroundWorker() control inside the UserControl.  You threaded work will be done in the DoWork() handler.  When all the threaded work is done the RunWorkerCompleted() event would fire and from there you can turn off the GIF associated (and encapsulated) within that same UserControl.
Avatar of lep1

ASKER

I can't see the picturebox in Me.Controls after it is added using Me.Controls.Add(gifpb).  

Also, I am sending the currentwaitgif name when turning off the pb.

I can see the pb on Form1 at run-time and know it has a valid name before adding to Me.Controls, but cannot see it in Me.Controls when I try to dispose it.

I will go back to your IsNothing method, but that didn't work since it is not appearing in Me.Controls --> but is on the Form1(????)


        If onoff = True Then
            Dim gifpb As New PictureBox
            gifpb.Image = My.Resources.ajax_loader
            gifpb.SizeMode = PictureBoxSizeMode.AutoSize
            gifpb.Visible = True
            gifpb.Left = left - 10
            gifpb.Top = top - 55
            gifpb.BringToFront()
            NumWaitGifs += 1
            CurrentWaitGifName = wgname + CStr(NumWaitGifs)
            htWaitGifs.Add(CurrentWaitGifName, 1)
            gifpb.Parent = Me
            gifpb.Name = CurrentWaitGifName
            Me.Controls.Add(gifpb)
        End If
        If onoff = False Then
            For Each pb As PictureBox In Me.Controls.OfType(Of PictureBox)()
                If pb.Name = wgname Then pb.Dispose()
            Next
            For Each cntl As Control In Me.Controls
                If TypeOf (cntl) Is PictureBox Then
                    MsgBox(cntl.Name)
                    If cntl.Name = wgname Then cntl.Dispose()
                End If
            Next
            For Each de As DictionaryEntry In htWaitGifs
                If de.Key = wgname Then
                    htWaitGifs.Remove(wgname)
                End If
            Next
        End If

Open in new window

At your line #10 and #13, you set the name to:

            CurrentWaitGifName = wgname + CStr(NumWaitGifs)
            ...
            gifpb.Name = CurrentWaitGifName

But at line #18, you're only using "wgname", therefore you'll never get a match:

            If pb.Name = wgname Then pb.Dispose()
*Also, you can't Dispose() of the control that you currently have in a For Each loop (lines #20 thru 25).  That modifies the collection being iterated by the For Each and will thus throw an exception.

**All your loops suffer from this problem...
Avatar of lep1

ASKER

I actually send the value of the global currentwaitgifname to the method when turning it off, so it comes through as wgname, and I confirmed it is validly getting through.

Interestingly, I checked the InnerList count in Me.Controls right after adding a gifpb, and can see the gifpb and the count is 26.

However, the second time I come into this "waitgifimg" method, the InnerList count is 13, (much lower), so I think there is a property of controls that prevents them from being registered in Me.Controls --> although they can still be seen on Form1.

After attempting to remove the single animated gif (gifpb), that is, after going through this method, that can't detect it in Me.Controls, the gif is sitting on the form working.

What causes a drop in the Innerliist count of Me.Controls?
It's hard to tell where the problem lies as the design is hard to visualize without seeing a good portion of the code.  The addition of global variables being used by multiple methods and/or threads only makes matters even more complicated...

Can you boil the problem down into a test project we can play with?  It could be uploaded to ee-stuff:
http://www.ee-stuff.com/
*This is a site run by Experts-Exchange as well.
Avatar of lep1

ASKER

The reduction in the number of controls in Me.Controls is occurring during the invoke from the threaded method.   I tried running the method 2 and 3 times and when I come into "waitgifimg" to add a picture box there were 26,27 and 28 controls each time and I can see all newly added gifpb's.  

However, when I enter waitgifimg from the threaded method using the invoke, the number of controls in Me.Controls is half and the gifpb's are not there.

I am defining the global delegate in a Declarations Module as

 Public Delegate Sub ShowWaitGifDelegate(ByVal wgname, ByVal onoff, ByVal left, ByVal top)
 

Open in new window


Then inside the threaded method, at the end when done, I am using:

   Dim deleg = New ShowWaitGifDelegate(AddressOf Form1.waitgifimg)

   deleg.Invoke(CurrentWaitGifName, False, 0, 0)

Open in new window

Is Form1 the startup object?
Avatar of lep1

ASKER

Yes, it's the "Starting Form" in the Application tab of Project-->Properties
I'm not sure where the problem lies.  The overall design is "messy" to say the least.  I'd really need to see more complete code and/or be able to play with it to figure it out.  Sorry!...  =\
Avatar of lep1

ASKER

I was able to use a usercontrol to show the animated gif.  Question now is when I Dim the new usercontrol should I exploit the name or tag of the object, since I can't use the git name as the name of the object.

How do you close a user control (instantiated object ) if you know its name or tag?
Avatar of lep1

ASKER

Igloo led at that.  The only question is how do you make a class subscribe to the existence of another class?
You either use the AddHandler() command, or declare the variable as "WithEvents" so you can later use "Handles xxx.yyy" on the end of an appropriately typed method.

Do you need an example?  I think we're talking about the UserControl subscribing to the Class events, with the Class being contained within the UserControl?...or something else?
Avatar of lep1

ASKER

At present it's almost working.  The code runs fine and before threading the worker method in the worker class, the user control is instantiated.  Thus, the user control is visible above the UI form control.  I am setting the name and tag of the user control but don't know how to kill it using its name or tag.  The usercontrol is also not listed in Me.Controls due to threading and instance differences.  

I wonder, since the user control (with animated gif) is an object from the UserControlLibrary Namespace, is there a way to enter active components in a namespace to kill it?
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of lep1

ASKER

Below is the full example code.  If you can spawn and then dispose of a user control with an animated gif without cross-threading then it will work.   I can't use BackgrounderWorkers because a user cant start another worker thread while a backgroundeworker is busy.   The method below uses straighforward threading.  

The apartment state on the thread needs to be STA and the invoke method at the end of the method needs to stay the same or the invoke won't work.   It's actually taken several days just to piece this together so that the invoke works correctly.  

Module Declarations
    Public Delegate Sub ShowWaitGifDelegate(ByVal wgname, ByVal onoff, ByVal left, ByVal top)
    Public NumWaitGifs as Integer
    Public CurrentWaitGifName as String
End Module

Public Class Form1

  Public Sub Button1_Click_1(sender As System.Object, e As System.EventArgs) Handles   Button1.Click
            waitgifimg("methodA", True, 200, 200)
            Dim work As New WorkerClass("methodA")
  End Sub

  Public Sub waitgifimg(ByVal wgname As String, ByVal onoff As Boolean, ByVal left As Integer, ByVal top As Integer)
        If onoff = True Then
            Dim wg As New MyUserControlLibrary.mywaitgif
            NumWaitGifs += 1
            CurrentWaitGifName = wgname + CStr(NumWaitGifs)
            wg.Tag = CurrentWaitGifName
            wg.Name = CurrentWaitGifName
            wg.Top = top - 55
            wg.Left = left - 10
            wg.BringToFront()
            Me.Controls.Add(wg)
         End If
        If onoff = False Then
            Dim form As Form1 = CType(Application.OpenForms(0), Form1)
            For Each cntl As Control In form.Controls
                MsgBox(cntl.Name)
                If cntl.Name = wgname Then
                    cntl.Hide()
                End If
            Next
        End If
  End Sub

End Class

Public Class WorkerClass
    Sub New(method)
        If method = "methodA" Then
            Dim t As New Thread(AddressOf worker1)
            t.TrySetApartmentState(ApartmentState.STA)
            t.Start()
        End If
    End Sub
    Sub worker1()
        Thread.Sleep(2000)
        Dim deleg = New ShowWaitGifDelegate(AddressOf Form1.waitgifimg)
        deleg.Invoke(CurrentWaitGifName, False, 0, 0)
    End Sub
End Class

Open in new window

Did Controls.Find() fix the problem?  I hadn't looked closely at your last post yet...
Avatar of lep1

ASKER

No, you have to cast the form1 before the invoke.   Once this is done, recasting is not needed before disposal of the wait gif in waitgifimg (onoff=false).
Glad you figured it out.  I'm not seeing which part of the code that applies too though.  Is that posted somewhere above?
Avatar of lep1

ASKER

Move line 27 to after line 48.  In line 49 change form1 to form.  You might then have to play with various forms of beginivoke if the "deleg" doesn't work.
Avatar of lep1

ASKER

Did you get your version of the code above to work?

 Just thought of something.  The animated waitgif that appears above the moveable control that a user right clicks on needs to be added to the control's controls and not Me.Controls.   This is because a user may move the moveable control at-run time, which would leave the animated gif by itself -- not good.

Thus at line 24, i changed adding the waitgif usercontrol (wg) to Me and am instead trying to add it to the moveable pb control, using, for example:
 
 
Dim taskcontrolpb As PictureBox = Me.Controls(CurrentTaskControlName)
  taskcontrolpb.Controls.Add(wg)
 

Open in new window


But the waitgif doesn't show above the control like it does with adding it to Me.Controls.  FYI - I confirmed that CurrentTaskControlName is true because when I used .visible=false it disappeared.  

Is the wg really there but maybe behind to pb due to default positioning when adding a control to a pb's controls?