Results 1 to 15 of 15

Thread: Accessing Controls from a different class....

  1. #1

    Thread Starter
    Addicted Member
    Join Date
    Sep 2011
    Location
    Seattle
    Posts
    218

    Accessing Controls from a different class....

    Alright I have 3 forms - The load up form "QoE" and two public classes. I also have an progress bar on the main form "QoE." Ive been able to access controls in other classes in numerous other projects, but for some reason this project is giving me fits. I have no idea why I can not control the progress bar from my class "Utils."

    The classes kind of look like this:

    Code:
    Public Class QoE
    End Class
    
    Public Class Utils
        public shared sub ProgressBar
            Dim f1 as New QoE()
            f1.ProgressBarMain.Increment(+1)
            f1.ProgressPercent.Text = f1.ProgressBarMain.Value.ToString() & "%"
        end sub 
    End Class
    
    Public Class Tests
        public shared sub DoWork
            Utils.Progressbar()
        End Sub 
    End Class

    Now, I was always under the impression that I could simply just call the controls like this:

    QoE.ProgressBarMain.Increment(+1)

    I actually have other projects where i did this, but now I am getting the error - "reference to a non shared member requires an object reference."

    So Ive been tinkering with the above code because I have seen other people do it around the net. I don't receive an error, but it is also not doing anything.

    I have all the controls set to public of course, so why is this not working?

    Thanks Guys

    Edit* I went back through my old projects and I definitely have called form1 controls before using something like form1.listbox.add() from form2. I cant figure out what is different though between that project and this project.
    Last edited by Zmcpherson; Oct 23rd, 2013 at 07:47 PM.

  2. #2
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    Re: Accessing Controls from a different class....

    Think about this. Let's say that you have a notepad. I want to write something on that notepad. I go out and get a new notepad and write something on it. Would you expect what I write to magically appear on the notepad in front of you? Of course not. In that case, if the user has a QoE form in front of them and you then create a new QoE form and change its ProgressBar, why would you expect that to change the ProgressBar of the QoE form in front of the user? Just because two things are the same type does not mean that changing one will change the other. It doesn't work that way for objects in real life and so it doesn't work that way for objects in OOP, which are modelled on real-life objects.

    Now, when you use just the type name to refer to a form you are actually using the default instance. To learn what that means, I suggest that you follow the Blog link in my signature and check out my post on Default Form Instances. If it's the default instance of QoE that you displayed in the first place then updating the default instance will have the desired effect. Why you apparently can't use the default instance in this case I don't know. That error message indicates that the class name is being interpreted as the class rather than the default instance of the class. That would generally only happen if the form didn't have a parameterless constructor, but the code you posted indicates that it does.

    If this information doesn't help you solve the issue, I'd be interested to see the project. Please ensure that you delete the bin and obj files before zipping the solution and attach the ZIP file to a post.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  3. #3
    PowerPoster techgnome's Avatar
    Join Date
    May 2002
    Posts
    34,687

    Re: Accessing Controls from a different class....

    It might be just me, but personally I don't think classes have any business updating forms like that in the first place. If they want to notify a form about progress, fine, raise and event, let the form handle the event and update the progressbar from there.

    Just my two cents.

    -tg
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  4. #4

    Thread Starter
    Addicted Member
    Join Date
    Sep 2011
    Location
    Seattle
    Posts
    218

    Re: Accessing Controls from a different class....

    How can I check to make sure QoE is the default instance? Is it Project Properties > Application > Startup Object?

    I get what you are saying about the notepad, Ive been sitting at my desk since 9am pst reading and trying to figure out why this works in one project and not another.

    I dont mind sending you the project, but it is pretty large (Im trying to include a progress bar because sometimes it takes 3-4 minutes to run through all the scripts). Do you have anything off the top of your head I can try?

  5. #5
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    Re: Accessing Controls from a different class....

    Quote Originally Posted by techgnome View Post
    It might be just me, but personally I don't think classes have any business updating forms like that in the first place. If they want to notify a form about progress, fine, raise and event, let the form handle the event and update the progressbar from there.

    Just my two cents.

    -tg
    I concur. If you check out the Multiple Forms link in my signature, you'll see that part 3 says that only the form on which a control resides should access any controls on that form. It says that other forms should not access those controls directly but that goes for any class at all. If an object is doing work for which the progress will be displayed on a form, as you say, that object should raise an event to indicate that progress has changed and it's up to whoever is listening to do with that what they will. That makes such a class more useful because it can be used in various places. The form should know about the object doing the work but not the other way around.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  6. #6
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    Re: Accessing Controls from a different class....

    Quote Originally Posted by Zmcpherson View Post
    How can I check to make sure QoE is the default instance? Is it Project Properties > Application > Startup Object?

    I get what you are saying about the notepad, Ive been sitting at my desk since 9am pst reading and trying to figure out why this works in one project and not another.

    I dont mind sending you the project, but it is pretty large (Im trying to include a progress bar because sometimes it takes 3-4 minutes to run through all the scripts). Do you have anything off the top of your head I can try?
    What version of VB are you using? Default instances have definitely existed since VB 2008 at least but I can't recall whether they were introduced in 2008 or 2005. In the unlikely event that you're using a really old version, that would be the reason.

    Is Startup Object an option? If so then that means either you are using a really old version or you've turned the Application Framework off, because you can only select a Startup Form with the Application Framework enabled. Default instances still work without the Application Framework though. It's just that the startup form is always the default instance of its type if the Application Framework is enabled but it's up to you either way if you write your own Main method.

    As tg and I said, ideally, you shouldn't be updating the ProgressBar from anywhere but in the form anyway. It's legal to access it from outside but it's not good practice.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  7. #7

    Thread Starter
    Addicted Member
    Join Date
    Sep 2011
    Location
    Seattle
    Posts
    218

    Re: Accessing Controls from a different class....

    The way that I had it initially was that the ProgressBar() was a public shared sub on QoE.vb (the initial form)

    so...

    Code:
    public class QoE
    public shared sub ProgressBar()
    progressbarmain.increment(+1)
    end sub
    end class
    
    public class tests
    public shared sub dowork()
    QoE.ProgressBar()
    end sub 
    end class
    but with this I keep getting the error "Cannot refer to an instance member of a class from within a shared method or shared member initializer without an explicit instance of the class."

    If I take the shared out, I dont get an error, but I cant call on it from the class "tests"

    Im currently trying to use RaiseEvents to get me there.

  8. #8

    Thread Starter
    Addicted Member
    Join Date
    Sep 2011
    Location
    Seattle
    Posts
    218

    Re: Accessing Controls from a different class....

    Well this is how I ended up solving it, let me know if there is an easier way...

    Code:
        Public Shared Event ProgressBarEvent(ByVal mynum As Integer)
    
        Private Sub ProgressBar(ByVal mynum As Integer) Handles Me.ProgressBarEvent
            ProgressBarMain.Value = mynum
            ProgessLabel.Text = ProgressBarMain.Value.ToString
        End Sub
    
        Public Shared Sub ProgressEventRaiser(ByVal percent As Integer)
            RaiseEvent ProgressBarEvent(percent)
        End Sub
    and then I call it using: QoE.ProgressEventRaiser(value)

  9. #9
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    Re: Accessing Controls from a different class....

    Quote Originally Posted by Zmcpherson View Post
    The way that I had it initially was that the ProgressBar() was a public shared sub on QoE.vb (the initial form)

    so...

    Code:
    public class QoE
    public shared sub ProgressBar()
    progressbarmain.increment(+1)
    end sub
    end class
    
    public class tests
    public shared sub dowork()
    QoE.ProgressBar()
    end sub 
    end class
    but with this I keep getting the error "Cannot refer to an instance member of a class from within a shared method or shared member initializer without an explicit instance of the class."

    If I take the shared out, I dont get an error, but I cant call on it from the class "tests"

    Im currently trying to use RaiseEvents to get me there.
    The issue there is fairly clear. You are trying to affect an instance of QoE from within a Shared method of QoE but a Shared method doesn't know anything about any instance so of course it can't affect one. You need to start thinking about programming objects the same way you think about real-world objects. If this QoE form was a paper form that you had to fill out with a pen, would you be able to write something on one instance of that form if you didn't have that instance in your hand? Again, of course not, so how can you affect an instance of the form in your app if you don't have access to that instance?

    Basically, if you did this the right way in the first place then you wouldn't have to try dodgy workarounds to make it work. Here's an example of an instance of a class doing work and notifying a form of progress via an event and the form then updating its own ProgressBar. The worker object doesn't even know that the form exists, which is how it should be.
    vb.net Code:
    1. Imports System.Threading
    2.  
    3. Public Class Worker
    4.  
    5.     Public Property Progress As Integer
    6.  
    7.     Public Event ProgressChanged As EventHandler
    8.  
    9.     Public Sub DoWork()
    10.         For i = 1 To 100
    11.             Thread.Sleep(100) 'simulated work
    12.             Progress = i
    13.             RaiseEvent ProgressChanged(Me, EventArgs.Empty)
    14.         Next
    15.     End Sub
    16.  
    17. End Class
    vb.net Code:
    1. Public Class Form1
    2.  
    3.     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    4.         Dim worker As New Worker
    5.  
    6.         AddHandler worker.ProgressChanged, AddressOf Worker_ProgressChanged
    7.         worker.DoWork()
    8.         RemoveHandler worker.ProgressChanged, AddressOf Worker_ProgressChanged
    9.  
    10.         MessageBox.Show("Work complete.")
    11.     End Sub
    12.  
    13.     Private Sub Worker_ProgressChanged(sender As Object, e As EventArgs)
    14.         Dim worker = DirectCast(sender, Worker)
    15.  
    16.         ProgressBar1.Value = worker.Progress
    17.         Refresh()
    18.     End Sub
    19.  
    20. End Class
    That's a bit of a dodgy example because everything is happening on the UI thread so the form will freeze but it demonstrates the principle nonetheless. The Worker class does the work but knows nothing about who it's doing the work for. It simply does the work and tells the universe where it's up to. It's up to whoever wants to listen to do something about that progress as it changes. In this case, the form that created the Worker object listens to those notifications and updates its own ProgressBar accordingly.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  10. #10
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    Re: Accessing Controls from a different class....

    Quote Originally Posted by Zmcpherson View Post
    Well this is how I ended up solving it, let me know if there is an easier way...

    Code:
        Public Shared Event ProgressBarEvent(ByVal mynum As Integer)
    
        Private Sub ProgressBar(ByVal mynum As Integer) Handles Me.ProgressBarEvent
            ProgressBarMain.Value = mynum
            ProgessLabel.Text = ProgressBarMain.Value.ToString
        End Sub
    
        Public Shared Sub ProgressEventRaiser(ByVal percent As Integer)
            RaiseEvent ProgressBarEvent(percent)
        End Sub
    and then I call it using: QoE.ProgressEventRaiser(value)
    That's horrible and still dodgy. Stop using Shared to try to get around doing things the right way. Also, as I have demonstrated after having said several times, it's the object doing the work that raises the event, not the form.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  11. #11

    Thread Starter
    Addicted Member
    Join Date
    Sep 2011
    Location
    Seattle
    Posts
    218

    Re: Accessing Controls from a different class....

    I should of said that the progress bar already has its own thread

    how the progress bar is being updated is something like this (my project is huge already, so these snippets are rather small):

    Code:
    dim somefile = file.readalllines("C:\thisfile.txt") 'Im just writing this freehand 
    dim percent as integer = somefile.length / 100
    
    for x as integer = 0 to somefile.length - 1
         if x mod percent = 0 then QoE.EventRaiser(x/percent)
         'do rest of work 
    next
    So the background worker is a better option?

  12. #12
    Bad man! ident's Avatar
    Join Date
    Mar 2009
    Location
    Cambridge
    Posts
    5,401

    Re: Accessing Controls from a different class....

    What do you mean by the progress bar has it's own thread?
    My Github - 1d3nt

  13. #13
    PowerPoster techgnome's Avatar
    Join Date
    May 2002
    Posts
    34,687

    Re: Accessing Controls from a different class....

    um... yeah... not sure what's meant by that either... but... um... it doesn't have its own thread. It is on the same thread as all of the other UI stuff... which is the main thread. Creating an instance of something doesn't make it on its own thread, it just makes a new instance of it. Same thread. You have to either use a BGW or create a new thread and spin it up. If you're not doing that (and judging by your last question there, I'm guessing not) then you're in a single thread.


    -tg
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  14. #14
    Bad man! ident's Avatar
    Join Date
    Mar 2009
    Location
    Cambridge
    Posts
    5,401

    Re: Accessing Controls from a different class....

    Quote Originally Posted by techgnome View Post
    um... yeah... not sure what's meant by that either... but... um... it doesn't have its own thread. It is on the same thread as all of the other UI stuff... which is the main thread. Creating an instance of something doesn't make it on its own thread, it just makes a new instance of it. Same thread. You have to either use a BGW or create a new thread and spin it up. If you're not doing that (and judging by your last question there, I'm guessing not) then you're in a single thread.


    -tg
    I'm actually going to go all out and say the user creates a secondary thread to invoke back to the UI thread and calls this multi threading. I see this alot :/
    My Github - 1d3nt

  15. #15
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    Re: Accessing Controls from a different class....

    Yeah, controls don't have threads; threads have controls. Controls are objects like any other and there is absolutely no restriction on accessing objects from any thread at all. The only issue is scope: whether you're using one thread or multiple, if you can see an object then you can access its members.

    The issue with controls is that they have an affinity for the thread that their handle was created on and, while you can access them on other threads, if that access makes use of the handle, there is no guarantee that it will work. It may do something unexpected or it may do nothing. That's why VS prevents you accessing controls on threads other than the ones that created them by default. A .NET app built for Release actually can do so freely, but there's no guarantee that it will work. A .NET app built for Debug will throw an exception by default.

    Now, in the case of your ProgressBar, its handle must have been created on the same thread as the form it is on, otherwise trying to add it to the form would have failed with a cross-threading exception. As such, it's the UI thread that owns the ProgressBar, just as the UI thread owns the form. What you think is a thread owned by the ProgressBar is no such thing. It's just a thread doing some work.

    If you want to create a single extra worker thread in a WinForms app then the easiest way is to use a BackgroundWorker. It already has the multi-threading built in and it also has events that automatically cross the thread boundary back to the UI thread so there's no need to use delegates. You can create the BackgroundWorker in a form or you can do it in some other class. It still works the same way. You call RunWorkerAsync on the UI thread and the DoWork event handler is executed on a worker thread. You call ReportProgress on the worker thread and the ProgressChanged event handler is executed on the UI thread. The work finishes and the RunWorkerCompleted event handler is executed on the UI thread.

    If you need multiple worker threads then you can create multiple BackgroundWorkers but that's unusual. In older versions of .NET, you could create Threads explicitly or use the ThreadPool but .NET 4.0 introduced the Tasks Parallel Library (TPL) that makes using threads in general a bit more like using a BackgroundWorker. As such, you can look into using the Task class and associated types in .NET 4.0 and later. VB 2012 also introduces the Await and Async keywords, which make use of the TPL under the hood.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width