Results 1 to 18 of 18

Thread: [RESOLVED] Passing particular control to subroutine

  1. #1

    Thread Starter
    New Member
    Join Date
    Jul 2017
    Posts
    15

    Resolved [RESOLVED] Passing particular control to subroutine

    I'm using VB2012 and have been looking for an answer for this for some time without any luck.

    I have several datagridview controls on my main form. On a module I have routines to load the data from a database and display it on the individual grids. The problem arises when there is no data to display - row count is 0. I do not want to have to write an entire subroutine for each gridview to say there is no data available. Instead I would like to send a single routine some parameters and have it used on any particular datagridview on my form.

    The calling routine is:

    Code:
    If TABLE.Rows.Count = 0 Then
                NoData("Vehicle", frmMain.grdVehicles)
                End
    End If
    Then (I've enhanced the lines where the problem seems to lie):

    Code:
     Private Sub NoData(Col As String, ctrl As DataGridView)
            Dim table As DataTable = New DataTable("TABLE")
            ' Declare variables for DataColumn and DataRow objects. 
            Dim column As DataColumn
    
            ' Create new DataColumn, set DataType, ColumnName  
            column = New DataColumn()
            column.DataType = Type.GetType("System.Int32")
            column.ColumnName = Col + "ID"
            ' Add the column to the table.
            table.Columns.Add(column)
    
            column = New DataColumn()
            column.DataType = Type.GetType("System.String")
            column.ColumnName = Col
            ' Add the column to the table.
            table.Columns.Add(column)
    
            table.Rows.Add(0, "No " + Col + " On File")
    
            With ctrl
    
                .DataSource = table
                .Columns(Col + "ID").Visible = False
                .RowHeadersVisible = False
                .Font = New Font("CourierNew", 10)
    
                'selection mode
                .SelectionMode = DataGridViewSelectionMode.FullRowSelect
                .MultiSelect = False
                .ReadOnly = True
    
                'background color
                .BackgroundColor = My.Settings.GridBackColor
                .GridColor = My.Settings.GridColor
    
                'header color
                .EnableHeadersVisualStyles = False
                .ColumnHeadersDefaultCellStyle.ForeColor = My.Settings.GridHeaderForeColor
                .ColumnHeadersDefaultCellStyle.BackColor = My.Settings.GridHeaderBackColor
    
                .Columns(Col).SortMode = DataGridViewColumnSortMode.NotSortable
    
                .AllowUserToResizeRows = False
                .AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
    
                .Columns(Col).HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
                .Columns(Col).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
    
            End With
    
        End Sub
    This, of course, fails. It doesn't seem to recognize which datagridview I want to use. Is it possible to pass a particular control to a subroutine where the data is to be displayed? Seems like it should be possible but I'm at a loss.

    Thanks in advance,
    Ken

  2. #2
    PowerPoster
    Join Date
    Nov 2017
    Posts
    3,116

    Re: Passing particular control to subroutine

    Code:
        If TABLE.Rows.Count = 0 Then
            NoData("Vehicle", frmMain.grdVehicles)
            'End <- Why is this here?
        End If
    I think the problem is likely arising from what "frmMain" is. If that is simply the name of your main form, then that is likely the root of the problem.

    I'm assuming the above 4 line code block is inside of a Sub or Function in your Module. And I'm also assuming that you are calling that Sub or Function from frmMain.

    So, modify the function that those 4 lines of code reside in to accept an instance of frmMain as a parameter. Example:

    Module code:
    Code:
      Public Sub GetData(frm As frmMain)
        'Other code
        If TABLE.Rows.Count = 0 Then
          NoData("Vehicle", frm.grdVehicles)
          End
        End If
    Then modify the code in frmMain that calls GetData to pass a reference to itself:
    Code:
      Call GetData(me)

    I *think* that should work. If not, you are going to need to include more details about where the code you posted resides, and post more code from your form and your module.

  3. #3
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,989

    Re: Passing particular control to subroutine

    I think more data seems like a good idea to begin with. That's a pretty strange function, as it stands. For one thing, you talk about passing in a variety of DGV, but you show an example passing in just one specific one from the default instance of a form. You may have similar lines passing in other DGV from other forms, but that wasn't seen.

    In any case, what do you mean when you say it fails? Since you appear to be using default instances, one possibility is that you just think it doesn't work, but it actually IS working. You just aren't passing in the DGV that you thought you were passing in. Another possibility is that it is throwing an exception, in which case we'd need to know the exception and the wording of it, but it seems more likely to be the former case. Default instances have been causing confusion since 2005, so it seems like the more likely problem here, as well.

    However, what you are doing in the function is also quite peculiar. You say that there is no data to display, but how do you know that? The reason I ask is that the function creates a datatable and sets it as the source of the DGV. That's pretty reasonable. In fact, it is by far the most common way to use a DGV, but the function suggests that the DGV didn't already have a datatable as the datasource. If it did, you could just use that rather than creating a new one and adding columns to it. If there wasn't already a datasource, then where did the data come from? Making a datatable and adding columns to it is not exactly rare, but it isn't common, because when you have a datatable, there is usually some kind of storage mechanism behind it (either a database, text file, or XML file), in which case you'd use some other means to create the datatable with the data in it.
    My usual boring signature: Nothing

  4. #4
    Fanatic Member
    Join Date
    Feb 2013
    Posts
    985

    Re: Passing particular control to subroutine

    have you tried adding 'ByRef' on the NoData routine parameters?

    Code:
     Private Sub NoData(Col As String, ByRef ctrl As DataGridView)
    ....
    ....
    ....
    End Sub
    Yes!!!
    Working from home is so much better than working in an office...
    Nothing can beat the combined stress of getting your work done on time whilst
    1. one toddler keeps pressing your AVR's power button
    2. one baby keeps crying for milk
    3. one child keeps running in and out of the house screaming and shouting
    4. one wife keeps nagging you to stop playing on the pc and do some real work.. house chores
    5. working at 1 O'clock in the morning because nobody is awake at that time
    6. being grossly underpaid for all your hard work


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

    Re: Passing particular control to subroutine

    Quote Originally Posted by GBeats View Post
    have you tried adding 'ByRef' on the NoData routine parameters?

    Code:
     Private Sub NoData(Col As String, ByRef ctrl As DataGridView)
    ....
    ....
    ....
    End Sub
    The ByRef keyword has one and only one purpose when it comes to reference types: allowing you to set the parameter within the method and have that change affect the original variable. If you're not assigning something to the parameter in the method then ByRef can not be of use. Note that that is specifically assigning to the parameter, not to a property/field of the object referred to by the parameter. If you're doing the latter then using ByRef makes absolutely zero difference and should therefore not be used.

  6. #6
    Fanatic Member
    Join Date
    Feb 2013
    Posts
    985

    Re: Passing particular control to subroutine

    but he needs to access the object directly (the datagridview), by not creating the ByRef pointer hes just changing a copy of it and if that does indead get sent back to the origional object isnt it taking up more resources? im a bit behind on my .net right now but in the past if you want to pass an object to a helper function you need to get a pointer to it and not a copy of it.
    Yes!!!
    Working from home is so much better than working in an office...
    Nothing can beat the combined stress of getting your work done on time whilst
    1. one toddler keeps pressing your AVR's power button
    2. one baby keeps crying for milk
    3. one child keeps running in and out of the house screaming and shouting
    4. one wife keeps nagging you to stop playing on the pc and do some real work.. house chores
    5. working at 1 O'clock in the morning because nobody is awake at that time
    6. being grossly underpaid for all your hard work


  7. #7
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Passing particular control to subroutine

    Quote Originally Posted by GBeats View Post
    but he needs to access the object directly (the datagridview), by not creating the ByRef pointer hes just changing a copy of it
    No, he's not. You need to learn the difference between value types and reference types. DataGridView is a class so it is a reference type. That means that a DataGridView variable contains a reference to an object, not the object itself. When you declare a parameter ByVal, an argument is passed by value, i.e. the contents of the variable is passed. If the contents of the variable is a reference then a reference is passed. The code doesn't create a copy of a DataGridView. The only way to create a copy of an object when passing an argument is to have a value type (i.e. structure) parameter declared ByVal.

  8. #8
    Fanatic Member
    Join Date
    Feb 2013
    Posts
    985

    Re: Passing particular control to subroutine

    so the default behavior when creating a method is to treat all objects as if they are byref?....

    im a little confused..... the OP wants to pass and object to a method and manipulate it from within the method. the object in question is the datagridview......if he doesnt declare and pass the object By Ref, isnt he still just working on a copy of his original object?

    ByRef = pointer to the memory position of the data/object in question
    ByVal = a new copy of the original data

    i always use ByRef when passing objects to helper functions maybe by habit but im using methods pretty similar to the OP right now and the only difference is the declaration on the method.... (i havent tested anything regarding this thread)



    -------------------------------------------------------------------------------------------------------
    UPDATE

    ok so i just went back on m yown current project and i am also indeed using methods that are not using ByRef parameters on objects/classes........

    i do also have 1 or 2 that are but it seems JMC you are right (still wandering why )

    so back to the OP then... if i have time to look over this tomorrow i will try, so busy right now ...
    Last edited by GBeats; Dec 26th, 2017 at 09:25 AM.
    Yes!!!
    Working from home is so much better than working in an office...
    Nothing can beat the combined stress of getting your work done on time whilst
    1. one toddler keeps pressing your AVR's power button
    2. one baby keeps crying for milk
    3. one child keeps running in and out of the house screaming and shouting
    4. one wife keeps nagging you to stop playing on the pc and do some real work.. house chores
    5. working at 1 O'clock in the morning because nobody is awake at that time
    6. being grossly underpaid for all your hard work


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

    Re: Passing particular control to subroutine

    Quote Originally Posted by GBeats View Post
    so the default behavior when creating a method is to treat all objects as if they are byref?....
    No. No, no, no. There are reference types (which generally means classes) and value types (which generally means structures). Instances of both value types and reference types are objects. A method parameter has a type and that type can be a value type or a reference type. A method parameter can also be declared ByVal (which is the default) or ByRef. So you can pass a value type by value, a value type by reference, a reference type by value or a reference type by reference. The ONLY time that a copy of an object is made is when you pass a value type by value. As value types are (or should be) always small, copying them is not an issue. There are only two reasons to declare a parameter ByRef:

    1. You want to assign a new object to the parameter inside the method and have that change affect the original variable. This applies to both value types and reference types.

    2. The parameter is a value type and you want to set a field or property of the parameter inside the method and have that change affect the original variable.

    There's information about this stuff around the web but I think that it's time that I wrote a couple of blog posts on it, which I've been putting off for several years now.

  10. #10
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,989

    Re: Passing particular control to subroutine

    Another way to see it is that when you pass a variable ByVal, you copy the contents of the variable into the argument. If you pass ByRef, then you pass a reference to the variable into the argument. This is consistent behavior for all variables. However, the contents of a variable isn't always what you are thinking it is. A variable of reference type holds a reference (essentially the address in memory of the actual object), not the object. So, if you pass such a variable ByVal, you copy what the variable holds, but that's just the reference to the object. The object is sitting out there in memory somewhere, and the variable just holds the address of the memory where it is found. You're just copying the address, which doesn't copy the object.

    If you were to pass it ByRef, you'd be copying the address of the variable, but the variable still only holds the address of the actual object, so you are just passing in the address to a spot in memory that simply holds the address of some object that is somewhere else in memory. References aren't memory pointers....but it doesn't hurt to think of them as such.

    Consider the advantage of this: A DataGridView could be absolutely HUGE. It might take a few bytes, but it might take thousands or millions of bytes. If the variable actually held the DGV itself, then the variable would have to be HUGE, as well. Furthermore, if you passed that ByVal to a method, the amount of memory that would have to be copied onto the stack (how arguments get passed) would be enormous. Potentially, it could be larger than the stack itself. The cost of moving all that memory around would become significant. By just passing around the reference, you ensure that all those arguments are tiny, and tend to be fixed size. This is one of the issues that MS talks about when discussing whether to use a Structure or a Class. A Class will always be a reference type, so the size of the class doesn't matter, since you're just passing around a reference. A structure can be a bit faster (no dereferencing step), but if it gets above some modest size, then the cost of passing it around starts to increase badly, since the structure is a value type, so the variable really does hold the data, not just the address of the data.
    My usual boring signature: Nothing

  11. #11
    Member
    Join Date
    Nov 2011
    Posts
    52

    Re: Passing particular control to subroutine

    Adding to SH and jmcilhinney ..

    Class are reference types, but any value type within class, is turned to reference as well.

  12. #12
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,989

    Re: Passing particular control to subroutine

    In what way? A value type within a class would still be accessed through a reference, because the class itself is accessed through a reference, but if a value type is returned by a method from the class, that would be just a value type.
    My usual boring signature: Nothing

  13. #13

    Thread Starter
    New Member
    Join Date
    Jul 2017
    Posts
    15

    Re: Passing particular control to subroutine

    I see I did not give adequate information about how the program is designed to function. My error ... sorry.

    First of all, the entire program section that I'm referring to is working perfectly on it's own. Let me explain all that first before getting to what I am trying to accomplish.

    The frmMain is what I call the Main windows form - I always use a lowercase abbreviation first to remind me of exactly what the control actually is. For instance, frm??? for form, txt??? for textbox, cbo??? for combobox, grd??? for datagridview, etc. In it's simplest explanation there are 2 datagridviews on the main form - frmMain.grdVehicles and frmMain.grdFuel although in actuality there are around 10.

    I also use individual modules for various jobs throughout my programs so I can easily just copy the file from program to program without the need to rewrite them each time. So I have modRoutines for public routines, modVariables to hold all public variables, modGridRoutines to handle loading and displaying datagridviews. This last module (modGridRoutines) is obviously unique to each program but it makes the code section of the form on which the grid resides somewhat smaller. It's also easier to find the sub doing the work if they are all in 1 place. Lastly here I also have a separate class, clsDatabase which includes classes to interact with the database being used.

    clsDatabase contains 3 functions. One to check connection and be sure the database is in the right place, A second (GetData) to load a variable TABLE with the desired data from the database. And lastly ExecuteNonQuery for updating and other such procedures.

    dbPath and SQL are public variables that are set prior to calling:

    Code:
     Earlier in the class: Imports System.Data.OleDb
    
    Public Function GetData() As Boolean
    
            Dim DBConnString As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath
            Dim cnn As OleDbConnection = New OleDbConnection(DBConnString)
            Try
                cnn.Open()
                OLECmd = New OleDbCommand(SQL, cnn)
                OLEDA = New OleDbDataAdapter(OLECmd)
                TABLE = New DataTable
    
                OLEDA.Fill(TABLE)
    
                cnn.Close()
                Return True
    
            Catch ex As Exception
                If cnn.State = ConnectionState.Open Then cnn.Close()
                Return False
            End Try
        End Function
    The procedure to call the above function is done on the modGridRoutines module. There are of course, subs to load each of the datagridviews because the data is different for each. Here is the one for loading grdVehicles ...

    Code:
    Earlier in the class: ReadOnly db As New ClsDatabase
    
    Public Sub LoadVehicleGrid()
            SQL = "SELECT VehicleID, YearMade as [Year], Make, Model, Type, Vin, Tag, Mileage " &
                    "FROM Vehicles " &
                    "ORDER BY YearMade, Make, Model"
    
            db.GetData()
    
            If TABLE.Rows.Count = 0 Then
                NoVehicle()
                End
            End If
    
            With frmMain.grdVehicles
    
                .DataSource = TABLE
    
    ... from here it displays the data contained in TABLE ...
    If the datatable variable TABLE is empty then there are no vehicles in the database as yet so the grid should display this instead:

    Code:
    Private Sub NoVehicle()
    Dim table As DataTable = New DataTable("TABLE")
            ' Declare variables for DataColumn and DataRow objects. 
            Dim column As DataColumn
    
            ' Create new DataColumn, set DataType, ColumnName  
            column = New DataColumn()
            column.DataType = Type.GetType("System.Int32")
            column.ColumnName = "VehicleID"
            ' Add the column to the table.
            table.Columns.Add(column)
    
            column = New DataColumn()
            column.DataType = Type.GetType("System.String")
            column.ColumnName = "Vehicle"
            ' Add the column to the table.
            table.Columns.Add(column)
    
            table.Rows.Add(0, "No Vehicles On File")
    
            With frmMain.grdVehicles
    
                .DataSource = table
    
    ... from here it displays the data in TABLE ...
    Now all this works fine as is. However each grid requires a routine to display an empty Table datatable and each of these routines is essentially the same with very minor changes. Each will display a different announcement ("No Vehicles on File" or "No Fuel Charges on File") etc. Now this would not be a problem outside of an intellectual exercise if there were only 2 datagrids on the frmMain. However there are in actuality about 10 so the repetitive code becomes quite long.

    Therefore the idea is to write a single NoData() subroutine to handle all grids needing to display "No" Somethings" On file". My thought had been to do this:

    Code:
     Private Sub NoData(Col As String, ctrl As DataGridView)
            Dim table As DataTable = New DataTable("TABLE")
            ' Declare variables for DataColumn and DataRow objects. 
            Dim column As DataColumn
    
            ' Create new DataColumn, set DataType, ColumnName  
            column = New DataColumn()
            column.DataType = Type.GetType("System.Int32")
            column.ColumnName = Col + "ID"
            ' Add the column to the table.
            table.Columns.Add(column)
    
            column = New DataColumn()
            column.DataType = Type.GetType("System.String")
            column.ColumnName = Col
            ' Add the column to the table.
            table.Columns.Add(column)
    
            table.Rows.Add(0, "No " + Col + " On File")
    
            With ctrl
    
                .DataSource = table
    ...
    And call it thusly:
    Code:
    NoData("Vehicles", frmMain.grdVehicles)
    
    Or:
    
    NoData("Fuel", frmMain.grdFuel)
    With Col as the string I wish to display in the grid and ctrl being the specific grid I want to use - grdVehicles for example. When I say it fails I'm not giving enough information. The code completes however the information is not shown in the grid desired leading me to conclude that I am not passing the control information to the subroutine correctly. In addition the program simply stops with this complaint which I don't understand:
    Warning 1 Could not read state file "obj\Debug\AutoTrakII.vbprojResolveAssemblyReference.cache". Unable to find assembly 'Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. AutoTrakII
    So I downloaded VS 2017 and ran the program there. No errors or warnings but the program simply stopped the same as 2012. ???

    So I would like to know the correct way to get a single subroutine to display the text that I want in the datagridview on frmMain that I wish to use.

    I hope that came off clearly without too much confusion and I gave sufficient data this time. I didn't want to give all the code as most of it is irrelevant to my question and would only confuse the matter. If I haven't explained something adequately let me know and I'll try it a different way.

  14. #14
    PowerPoster
    Join Date
    Nov 2017
    Posts
    3,116

    Re: Passing particular control to subroutine

    So, based on your response, frmMain is simply the name of your main form, which I mentioned (but unfortunately didn't expound on why it could be the issue in my first reply), which then brings up the Default Instance issue that Shaggy Hiker explained more thoroughly.

    So, try this. In one of your modules, add this to make a global variable:

    Code:
    Public frmMainRef As frmMain
    Then, in your frmMain_Load procedure, add this:

    Code:
    frmMainRef = Me
    Then, take one of your existing routines that is accessing frmMain.dgrdGridName and replace it with frmMainRef.dgrdGridName and report back the results.

  15. #15
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,989

    Re: Passing particular control to subroutine

    The naming convention you mention has various names. It is a version of Hungarian Notation and is more generally called warting. MS advocated that for a decade or so, and now advocates against it. It's a matter of preference, though, as it has little or no impact. I do something like that for controls on forms because I want them to cluster together in intellisense. I've been known to use the same convention on forms, too, but I have no justification for it, and I'm not consistent with it. Do as you like and don't be afraid to change your habits, too.

    What you shouldn't be doing is trying to guess at what the code is doing. When it didn't do what you thought it would do, the first thing you should do is put a breakpoint on the function call line, then, when execution stopped on the breakpoint, use F11 to step into the method and see what happens. With no error, it is almost certainly the case that execution is stepping into the method. If it steps on through the method without issue, then it is doing exactly what you have told it to do, and the data IS showing up....just not where you thought it would. On the other hand, there is one other possibility: If the whole chain of functions is called in the Load event handler, then you may be getting an exception. On certain systems, exceptions thrown in the Load event, if not also caught in the load event, will simply vanish. The code will just stop working at that point and move on. This is ONLY with the Load event handler, though, so you can know pretty quickly whether or not it is possible.

    Essentially, there is nothing fundamentally wrong with what you are trying to do. If it isn't doing what you expect (and obviously it is not), then there's a reason why. Stepping through the code may show that to you. If it doesn't, then the control passed in is not the control you are looking at on the screen. You are passing in the default instance of the form. If you aren't showing the default instance, then you aren't changing the form you are seeing, but changing a different form that isn't shown. If you don't know what default instances are, then that becomes considerably more likely. Default instances have been sowing confusion for over a decade now.

    You showed how you call NoData, but not where you called it from. If that's called from a module, then the odds of this being a default instance issue goes up considerably. If you called it from the form itself, then rather than passing in frmMain.grdVehicles, you could just pass in Me.grdVehicles.
    My usual boring signature: Nothing

  16. #16
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,989

    Re: Passing particular control to subroutine

    Also, your GetData routine has a subtle flaw. If an exception is thrown, you'll never know. An exception in there would act like no records were returned. You do return False in that case, but since you don't appear to be checking the return from the method, it doesn't matter what is returned. Since you're using OleDB, it seems likely that the database is Access. Getting an exception against an Access database is relatively easy, in my experience, because that can happen if somebody has a table open in design view. So, you probably want to either check the return value, or log the exception somehow, or both.
    My usual boring signature: Nothing

  17. #17

    Thread Starter
    New Member
    Join Date
    Jul 2017
    Posts
    15

    Re: Passing particular control to subroutine

    Thank you all for your suggestions and input. I found the mistake - it was me. If I changed the NoData sub to a public one and called it from the main form everything worked fine. So I revisited my call from the subroutine that filled the TABLE variable and found out why the program simply stopped without showing the grid as filled. It was a stupid typo that caused the entire problem.

    Code:
    If TABLE.Rows.Count = 0 Then
      NoData("Vehicle", frmMain.grdVehicles)
      End
    End If
    Changing that to Exit sub which I had meant from the beginning fixed the entire problem. I feel like an idiot for not spotting that in the first place but the post did at least spark information about byref which I found useful. I'm sorry for all this.

    I will also add error checking to my GetData calls.

    Ken
    Last edited by KenB; Dec 27th, 2017 at 09:13 PM.

  18. #18
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,989

    Re: [RESOLVED] Passing particular control to subroutine

    That's why breakpoints and code stepping are so useful. You can ALWAYS overlook simple things like that, but you won't overlook it if you are stepping through the code.
    My usual boring signature: Nothing

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