Grouping form controls using dictionary, collection or class - vb.net

I am trying to categorize/group my form controls in order to be able to apply changes to several of them in one go. For instance, I may want to enable/disable them.
Should I add the form controls to collections, dictionaries or should I create classes?
Ideally I would like to create categories and sub-categories. I would define properties for the categories and the sub-categories. The properties of the categories would be passed to the "child" sub-categories.
For instance, if the font of CategoryA is "arial" the font of subcategories A1, A2 ect would also be "arial".
I would only "store" the objects in the sub-categories. If you have ideas how I could such architecture please make some suggestions.
At this stage I have created a dictionary. I am quite sure that I cannot create the categories/sub-categories I would like to with dictionaries and collections but it's still a first step in the right direction (bulk changes).
The problem I am facing with dictionaries is that the properties/methods specific to the controls do not display in the IntelliSense. How can I make them available?
Public dct As New Dictionary(Of Object, Integer)
Dim ctlr As Control
Dim i As Integer
i = 1
For Each ctlr In Controls
dct.Add(ctlr.Name, i)
i = i + 1
Next
For Each Item In dct
'Enabled is not available
Item.Enabled = False
Next

I would dump the dictionary and use a List(Of T). Add the actual object to the list and the properties should be available. In any case here is the dictionary code. Comments and explanation in-line.
Public dct As New Dictionary(Of String, Integer)
'I changed Object to String because that is what you are
'adding to the dictionary. I don't see where the .Value
'is ever used so I suggest changint to List(Of Button) or List(Of Control)
Private Sub OpCode2()
Dim ctlr As Control
Dim i As Integer
i = 1
'This saves the name of the control to the key of the dictionary
'This is just a string unrelated to a control as far as the dictionary knows
For Each ctlr In Controls
dct.Add(ctlr.Name, i)
i = i + 1
Next
For Each Item As KeyValuePair(Of String, Integer) In dct
'The .Find method returns a control using the control name
Dim ctrl As Control = Controls.Find(Item.Key, True).FirstOrDefault()
'The properties inherited from Control will be available in intellisense
'If you need a property of a particular type of control
'you will need to cast ctrl to the type you need
ctrl.Enabled = False
Next
End Sub
The following code uses List(Of T) and is much shorter and easier to read.
Public lst As New List(Of Control)
Private Sub OpCode2()
Dim ctlr As Control
For Each ctlr In Controls
lst.Add(ctlr)
Next
For Each Item In lst
'The list actually contains a reference to the control
'so properties of Control are available
Item.Enabled = False
Next
End Sub

Related

VB.Net 2010 "Object reference not set to an instance of an object." when declaring a variable from a Dictionary with a list as the value

This is a school project for Programming (not an assessment or assignment, so I'm not cheating) where I have to make a 7-segment displaySource 1. I decided instead of going the traditional way and manually setting each RectangeShape to visible on each button pressed to display a number; store the corresponding number and which RectangleShape(s) to turn on as a key-value pair in a dictionary. I have some knowledge of Python, so this is where I got the idea from. My formSource 2 has 7 RectangleShape(s) and 10 Button(s). Just as an experiment since it's my first time working with Dictionaries and Lists in VB.net, I decided to only try it out for the number 1 for now (shp4 and shp5 should be visible). Here is the dictionary I made:
Dim Numbers As New Dictionary(Of Integer, List(Of PowerPacks.Shape)) From {{1, New List(Of PowerPacks.Shape) From {shp4, shp5}}, {2, New List(Of PowerPacks.Shape) From {shp2, shp3}}}
Here is the code for the button (btn1):
Private Sub btn1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn1.Click
Dim Thing As PowerPacks.Shape = Numbers(1)(0)
Thing.Visible = True
End Sub
When the program gets to the line that says Thing.Visible = True, it throws an error. It's a NullReferenceException that states Object reference not set to an instance of an object. Any ideas on how to fix this?
Sources
Source 1:
Source 2:
Programming is not magic. It works pretty much as you'd expect. If you are getting Nothing out of your List within a Dictionary then you must be putting Nothing in. Did you use the debugger before posting? Did you actually look at the value of shp2, etc, when this line is executed:
Dim Numbers As New Dictionary(Of Integer, List(Of PowerPacks.Shape)) From {{1, New List(Of PowerPacks.Shape) From {shp4, shp5}}, {2, New List(Of PowerPacks.Shape) From {shp2, shp3}}}
Based on your use of that Numbers variable in the second code snippet, it must be a member variable, which means that that first code snippet is outside any method, which means that it is executed before the class constructor, which means that no controls have been created at that time, which means that any fields that refer to controls can't be anything but Nothing. If you had put a breakpoint on that line and used the debugger, as you should have, then you'd have seen that.
The solution is not actually add the Shapes to the collection until they are created. That means after the call to InitializeComponent. That means that you can create your own constructor and do it there if you want, but that you should probably just do it in the Load event handler. Just declare the Dictionary variable without creating an object:
Private numbers As Dictionary(Of Integer, PowerPacks.Shape())
Note that I have provide an explicit access modifier, which you should ALWAYS do for all members, and also started the name with a lower-case letter, which you probably ought to do for all private fields. You then create the object in the load event handler:
numbers As New Dictionary(Of Integer, PowerPacks.Shape()) From {{1, {shp4, shp5}},
{2, {shp2, shp3}}}
I've taken the liberty of simplifying that by using arrays rather than Lists that add no value.
On your 7-Segment Display Form, I would use a List (of String) containing which rectangles are turned on for each dictionary key (number). This would require first programatically adding the 7 shapes onto the form (looks like you already coded that), and then in each button (for numbers) just pick off which rectangles are turned on and change their background color. "Front loading" all the Shapes (rectangles) into the Dictionary is probably unnecessary, since they are already manually fixed on the Form. If they are not, then pull out the code that places them on the form and run that first, one time, when the form loads. Using this approach, you don't have to worry about Shapes any more.
In Form 1, add:
Dim dicRSBackColor As New Dictionary(Of Integer, List(Of String))
Dim SC As New ShapeContainer
In Form1_Load, add the following:
'define each of the 7 rectangles using, e.g.:
Dim myrec4 As New RectangleShape
myrec4.Name = "shp4"
myrec4.Visible = True
myrec4.BringToFront()
'add locations and size to myrec4
myrec4.Top=100
myrec4.Left=100
myrec4.Width=10
myrec4.Height=100
'then add myrec4 to Shape container
SC.Shapes.Add(myrec4)
'add myrec4 to Form1
Me.Controls.Add(myrec4)
'do the above for all 7 rectangleshapes
'For the dictionary, add number 1's rectangleshape (turned on)
Dim mylist As List(Of String)
mylist.Add("shp4")
mylist.Add("shp5")
dicRSBackColor.Add(1, mylist)
'add number 2 rectangles (turned on)
mylist.Clear()
mylist.Add("shp1")
mylist.Add("shp3")
mylist.Add("shp4")
mylist.Add("shp6")
mylist.Add("shp7")
dicRSBackColor.Add(2, mylist)
'continue until all 7 rec's added
Last, in the button _Click for number 1, use the following:
'First change background color of all rectangleshapes to Form1's back color
For Each rs As RectangleShape In Me.SC.Shapes
rs.BackColor = Me.BackColor
Next
'Now pull the list of shapes that are turned on for number 1 using the dictionary
Dim mylist1 As List(Of String)
mylist1 = dicRSBackColor(1) 'you're pulling the value for key=1, which is a list of strings
'use a double loop and find out when the list value is the same as the name of the rectangleshape, and then set the back color to orange
For Each item In mylist1
For Each rs As RectangleShape In Me.SC.Shapes
If item = rs.Name Then rs.BackColor = Color.Orange
Next
Next

Access VBA listing collection items in a class module

Although I'm reasonable experienced VBA developer, I have not had the need to use class modules or collections but thought this may be my opportunity to extend my knowledge.
In an application I have a number of forms which all have the same functionality and I now need to increase that functionality. Towards that, I am trying to reorder a collection in a class module, but get an error 91 - object variable or with block not set. The collection is created when I assign events to controls. The original code I obtained from here (Many thanks mwolfe) VBA - getting name of the label in mousemove event
and has been adapted to Access. The assignments of events works well and all the events work providing I am only doing something with that control such as change a background color, change size or location on the form.
The problem comes when I want to reorder it in the collection - with a view to having an impact on location in the form. However I am unable to access the collection itself in the first place.
The below is my latest attempt and the error occurs in the collcount Get indicated by asterisks (right at the bottom of the code block). I am using Count as a test. Once I understand what I am doing wrong I should be able to manipulate it as required.
mLabelColl returns a correct count before leaving the LabelsToTrack function, but is then not found in any other function.
As you will see from the commented out debug statements, I have tried making mLabelColl Private and Dim in the top declaration, using 'Debug.Print mLabelColl.Count' in the mousedown event and trying to create a different class to store the list of labels.
I feel I am missing something pretty simple but I'm at a loss as to what - can someone please put me out of my misery
Option Compare Database
Option Explicit
'weMouseMove class module:
Private WithEvents mLbl As Access.Label
Public mLabelColl As Collection
'Dim LblList as clLabels
Function LabelsToTrack(ParamArray labels() As Variant)
Set mLabelColl = New Collection 'assign a pointer
Dim i As Integer
For i = LBound(labels) To UBound(labels)
'Set mLabelColl = New Collection events not assigned if set here
Dim LblToTrack As weMouseMove 'needs to be declared here - why?
Set LblToTrack = New weMouseMove 'assign a pointer
Dim lbl As Access.Label
Set lbl = labels(i)
LblToTrack.TrackLabel lbl
mLabelColl.Add LblToTrack 'add to mlabelcoll collection
'Set LblList as New clLabels
'LblList.addLabel lbl
Next i
Debug.Print mLabelColl.Count 'returns correct number
Debug.Print dsform.countcoll '1 - incorrect
End Function
Sub TrackLabel(lbl As Access.Label)
Set mLbl = lbl
End Sub
Private Sub mLbl_MouseDown(Button As Integer, Shift As Integer, x As Single, Y As Single)
Dim tLbl As Access.Label
'Debug.Print LblList.Count 'Compile error - Expected function or variable (Despite Count being an option
'Debug.Print mLabelColl.Count 'error 91
'Debug.Print LblList.CountLbls 'error 91
Debug.Print collCount
End Sub
Property Get collCount() As Integer
*collCount = mLabelColl.Count* 'error 91
End Property
In order to have all the weMouseMove objects reference the same collection in their mLabelColl pointer, a single line can achieve it:
LblToTrack.TrackLabel lbl
mLabelColl.Add LblToTrack
Set LblToTrack.mLabelColl = mLabelColl ' <-- Add this line.
But please be aware that this leads to a circular reference between the collection and its contained objects, a problem that is known to be a source of memory leaks, but this should not be an important issue in this case.

Saving user's DataGridViewColumn widths for multiple forms/controls

Typically, when designing a DataGridView, I try to size the columns so that nothing will need to be resized by the user. This practice works most of the time, but I recently had a user change her Windows settings so that text would display larger than usual.
That single act broke all of the tedious sizing that I worked so hard on. I have looked into saving column width per user and allowing them to be saved to the registry. The issue I run into is having to create a field in the application settings for each and every value that I want to save to the registry.
When saving settings for a single form, that is not a problem, and I do use the application settings for this purpose to save the main window size/location so that users can determine the optimal view of the application.
My question is:
Is there a way to save an array to the registry, or perhaps otherwise save dynamic values into the registry, without adding these values to the application settings in advance? Ideally, I will just have a "ColumnWidths" application setting or something along those lines, and dynamically add column name/width for any column that is resized by the user.
I have the following code, which is fine if I add a new setting for each individual form that I want to save sizing for, but I'm hoping to achieve this with a single setting, that will save column sizing for multiple DataGridView/Forms.
Private Sub SaveColumnSettings()
If My.Settings.CustomColumnWidths Is Nothing Then
My.Settings.CustomColumnWidths = New StringCollection
End If
For Each dc As DataGridViewColumn In grdBackOrderedItems.Columns
My.Settings.CustomColumnWidths.Add(dc.Width.ToString())
Next
End Sub
Private Sub LoadColumnSettings()
For i As Integer = 0 To My.Settings.CustomColumnWidths.Count - 1
grdBackOrderedItems.Columns(i).Width = CInt(My.Settings.CustomColumnWidths(i))
Next
End Sub
Settings allows for a Collection type, however it is a string collection (initial post seemed not to know this).
The current edit is dicey since it assumes one collection will fit all DGVs on all forms (that is, the same number of columns for all of them) and you are only adding to the Collection every time you save - making it larger. Since you want to save the users optimal view you should have one collection per DGV since they might well make col4 on DGVFoo wider than elsewhere.
Rather than adding a collection Setting for each DGV (ie "DGVFooColumns", "DGVBarColumns" etc), which requires each one be hard coded to map to the correct entry, I would use a custom class and serialize it.
Most everything regarding them can be internalized to the class. This is not a finished class, but a rough out of making one class service any and all DGVs (it should be very close):
' for the collection
Imports System.Collections.ObjectModel
Imports System.Runtime.Serialization.Formatters.Binary
<Serializable>
Public Class GridLayouts
<Serializable>
Friend Class GridLayout
Public Property GridName As String
Public Widths As New List(Of Integer)
' Some Serializers will require a simple ctor
Public Sub New()
Widths = New List(Of Integer)
End Sub
Public Sub SaveLayout(name As String, dgv As DataGridView)
' something the code uses to map them
' other than "DataGridView1" of which there may be several
GridName = name
' ToDo: loop thru DGV save widths to Widths
End Sub
End Class
Private mCol As Collection(Of GridLayout)
Private myFile As String
Public Sub New(filename As String)
myFile = filename
mCol = New Collection(Of GridLayout)
End Sub
Private Function IndexOf(grdName As String) As Integer
For n As Integer = 0 To mCol.Count - 1
If grdName = mCol(n).GridName Then
Return n
End If
Next
Return -1 ' not found
' alternatively find by name:
'Dim item = mCol.FirstOrDefault(Function(i) i.GridName = grdName)
'Return item
End Function
Public Sub StoreLayout(name As String, dgv As DataGridView)
Dim ndx As Integer = IndexOf(name)
If ndx <> -1 Then
mCol.RemoveAt(ndx) ' throw away old one
End If
Dim grd As New GridLayout
grd.SaveLayout(name, dgv)
mCol.Add(grd)
End Sub
Public Sub RestoreLayout(name As String, dgv As DataGridView)
Dim ndx As Integer = IndexOf(name)
If ndx = -1 Then
Exit Sub
End If
For n As Integer = 0 To mCol(ndx).Widths.Count - 1
' loop thru grid and set the columns
' maybe check that the col sizes are equal
dgv.Columns(n).Width = mCol(n).Widths(n)
Next n
End Sub
Public Sub Save()
' ToDo: Make a backup (?)
' Add a Try/Catch and convert to function
Dim bf As New BinaryFormatter
Using fs As New FileStream(myFile, FileMode.OpenOrCreate)
bf.Serialize(fs, mCol)
End Using
End Sub
Public Sub Load()
Dim bf As New BinaryFormatter
Using fs As New FileStream(myFile, FileMode.Open)
mCol = CType(bf.Deserialize(fs), Collection(Of GridLayout))
End Using
End Sub
End Class
Notes:
1) The Example has SaveLayout at the Class-Item level and RestoreLayout at the Collection Level. I would pick one way or the other not split it. This is was for illustrative purposes. I would generally favor the ClassItem Level for both.
2) Rather than the BinaryFormatter, you can use the XML Serializer. I personally loathe it.
3) Note that the collection class saves/loads its own data.
4) System.Collections.ObjectModel is required for Collection(of T) and prevent VB from wanting the use the vile VisualBasic Collection (which stores only Object).
5) Code elsewhere could be optimized so that you only store a layout if they actually change the widths and click something (or have an AutoSaveChanges option).
6) A fair amount is riding on the names being unique so that the layouts can be found.
Usage
Dim SaveFile As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
CompName,
ProgramName,
filename)
' e.g C:\Users\Ziggy\MyCompany\ThisProd\GridSettngs.bin
' application-wide collection of grid layouts for this user
Private myGrids As New GridLayouts(SaveFile)
...
myGrids.StoreLayout("Customers", datagridView12)
myGrids.RestoreLayout("Orders", datagridView34)

How do I make a list of list objects In VB.NET?

Hi I know this may sound a little weird but I am trying to have a master list that contains other lists of controls. Here is what I have so far.
'Create master list of control lists. Each member of this list will be a list containing one rows worth of controls
Dim masterList As New List(Of List(Of Control))
Dim rowList As New List(Of Control)
For Each Control As Control In flpExceptionControls.Controls
rowList.Add(Control)
If flpExceptionControls.GetFlowBreak(Control) = True Then
masterList.Add(rowList)
rowList.Clear()
End If
Next
For Each row As List(Of Control) In masterList
MsgBox(row.Count.ToString)
Next
The message box is showing that each of those lists have a count of 0, but I know it is adding all the controls to those objects because it shows it when I step through the code. I'm guessing I am just not accessing the list objects contained in the master list correctly.
Any suggestions would be greatly appreciated.
Your problem is here:
If flpExceptionControls.GetFlowBreak(Control) = True Then
masterList.Add(rowList)
rowList.Clear()
End If
You are clearing the contents of the same list, which you are then adding some new items to, and clearing again, and adding the same reference to the master list. Essentially; all of your items in masterList are the same, empty list.
You need to create a new sub-list for each. Don't clear any lists.
Dim rowList As New List(Of Control)
For Each Control As Control In flpExceptionControls.Controls
rowList.Add(Control)
If flpExceptionControls.GetFlowBreak(Control) = True Then
masterList.Add(rowList)
rowList = New List(Of Control)
End If
Next

Pass Global Variable to custom UserForm

Have the following code that ends up with a .Count variable. I would like to take that integer value and output it to a sentence in a custom UserForm. How do I do this within the Visual Studio 2012 designer. This is really stumping me. Thanks!
Public Shared Property mailItem As Object
Public Shared Property BodyMatchResults As MatchCollection
Public Shared Property SubjectMatchResults As MatchCollection
Public Sub Application_ItemSend(ByVal Item As Object, _
ByRef Cancel As Boolean) Handles Application.ItemSend
Dim mailItem As Outlook.MailItem = TryCast(Item, Outlook.MailItem)
If mailItem IsNot Nothing Then
Dim attachments = mailItem.Attachments
For Each attachment As Outlook.Attachment In attachments
AttachmentQuery(attachment, mailItem, Cancel)
Next attachment
End If
Dim BodyMatchResults As MatchCollection
Dim SubjectMatchResults As MatchCollection
Dim RegexObj As New Regex("\b(?!000)(?!666)(?!9)[0-9]{3}[ .-]?(?!00)[0-9]{2}[ .-]?(?!0000)[0-9]{4}\b")
BodyMatchResults = RegexObj.Matches(mailItem.Body)
SubjectMatchResults = RegexObj.Matches(mailItem.Subject)
If BodyMatchResults.Count > 0 Or SubjectMatchResults.Count > 0 Then
Cancel = True
MessageBox.Show((BodyMatchResults.Count + SubjectMatchResults.Count))
' Access individual matches using AllMatchResults.Item[]
Else
Cancel = False
End If
The UserForm is pretty basic with three buttons and a warning text above it. I would like to have in that text "There were either "BodyMatchResults.count" or "subjectmatchresults.count" in your email.
Well you obviously beging with your .count variable (which for arguments sake I'll assume is of type Integer). Hopefully you also have your Custom UserForm which we'll assume you have called UserForm.
In that UserForm add the following code:
Private _count As Integer
Public Sub New (ByVal count AS Integer)
InitializeCompponent()
_count = count
End Sub
You can then use the _count variable to dipaly the informationyou want displayed.
Now when you call your UserForm you can pass your .count variable to it so that it can be used there like this:
dim frm as New UserForm(NumberOfCountsIWantToDisplay)
This basic principle will work for most situations.
Edit
Clearly I'm not reading things properly. Your question had specifically asked about passing a Global variable. You should simply be able to refer to a publically defined global variable from anywhere within your application (so long as that variable has application scope). However global variables can be more trouble that they are worth and if in reality you simply want to pass one value from form a to form b then I would use the approach that I had originally outlined having misread the subject title, for which I apologise.