Emit Reflection (vb.net) Dynamically Reflect dropbox results in property grid - vb.net

trying to add a dropdown into a property grid
Im using VS2010 VB.net with reflection
For my full solution - Download it here
https://www.nyvault.com/files/reflection/xml_propgrid_reflect_sk.zip
Password is
1
The point of this project is to populate a propertyGrid
I dont want to create a custom grid, i just want to populate the already made out of the box generic microsoft thing.
basically, I get the class to be created with reflection. using data from XML
Some of the fields use a custom type (this will be used for the dropdown)
This is what happens when I run the solution:
So i make the class, and it looks fine
It creates everything and sets it up. then when I make an instance of this class in my MAIN()
in calls a default constructor [new()] for the type(which is hardcoded dropdown items) instead of the custom constructor I wanted [new(byval test as integer)]
basically here are the class constructors for the custom type (located in customlist.vb)
Public Sub New()
' Gather all the localized strings currently loaded
' Gather all the strintTables from the current project.
For i As Integer = 0 To 4
myStringCollection.Add(New MyString(100 + i, "Test " & i))
Next
End Sub
Public Sub New(ByVal val As Integer)
For i As Integer = 0 To val
myStringCollection.Add(New MyString(100 + i, "Testy " & i))
Next
End Sub
it calls the Public Sub New()
but i want to call the Public Sub New(ByVal val As Integer)
please help, this has been making me rip my hair out for two months.

Related

Can you put VBA code in a bare module, outside of Function or Sub?

I'm trying to keep a Collection Class of Classes persistent while a Userform is running so that the form objects they create can still have event handlers.
But if I create any classes for these in subs or functions, their respective classes and event handlers would be cleared at the end of whatever subroutine created it.
I should specify that user input determines how many classes there will be, so I can't just hard code the event handlers into the userform module.
You can use a publicly declared dictionary to hold instances of your class that will be available to your project. You declare variables outside of a function or sub and declare them as Public for other modules and their subs/functions to be able to use them. They stay resident in memory between calls while the application is open.
Consider a class called c_gumball:
Public color As String
Public diameterInches As Double
Public Function getSize(unit As String) As Double
Select Case unit
Case "mm"
getSize = diameterInches * 25.4
Case "cm"
getSize = diameterInches * 2.54
Case "yd"
getSize = diameterInches / 36
End Select
End Function
And then a new module called m_gbmachine:
Public gumballMachine As Dictionary
Public Sub createGumbalMachine()
gumballMachine = New Dictionary
End Sub
Public Sub addGumball(color As String, sizeInInches As Double, nameKey As String)
Dim gb As c_gumball
Set gb = New c_gumball
gb.color = "green"
gb.diameterInches = 1.2
gumballMachine.Add Key = nameKey, gb
End Sub
Public Sub removeGumball(nameKey As String)
gumballMachine.Remove (nameKey)
End Sub
Any module can now use m_gbmachine.gumballMachine dictionary and see what's in it. They can add gumballs using it's functions.
Perhaps in your userform you create a gumball called "gumball2" in your dictioanry and then want to get the color property of "gumball2" in the gumballMachine dictionary, you could do:
Public Sub button_Click()
'add gumball 2 to the machine
m_gbmachine.addGumball "green", 1.2, "gumball2"
End Sub
Public Sub someFormRoutine()
'msgbox the color of gumball 2
MsgBox m_gbmachine.gumballMachine("gumball2").color
End Sub
You can go deeper and change this module over to a class of it's own and have many gumball machine instances as well.

Pass value from form to button text vb.net

I am learning vb.net and I'm having issues searching for what I need. I want to create a button that is "re-usable" throughout my application without needing to write code for each instance. So, what I would like to start with is take a variable in a form, example, public integer value and when this value changes I want to write to the text of a button. I know I can easily do this by writing code in the form btn_xxx.text = variable, but what if I have several buttons and each button looks at the same variable? Currently what I do is create a component which inherits a button and have a timer that on tick will look at the variable and write to the text. I'm sure there is a better way. Can anyone point me in the right direction? I know part of my problem is I don't know the nomenclature on what things are called, so hopefully I asked my question without too much confusion.
I saw this, https://www.daniweb.com/programming/software-development/threads/124842/detect-variable-change, but I don't see how to adapt that to my situation.
Here is what I have:
Private WithEvents Active_Alarm As New Nav_Active_Alarm
Then inside of a sub that calculates the count:
Active_Alarm.Count = CInt(dt_Active_Alarms.Rows.Count)
The user control:
Public Class Nav_Active_Alarm
Private mActive_Alarm_Count As Integer
Public Event Active_Alarm_Count_Changed(ByVal mvalue As Integer)
Public Property Count() As Integer
Get
Count = mActive_Alarm_Count
End Get
Set(ByVal value As Integer)
mActive_Alarm_Count = value
If Not Me.DesignMode Then
RaiseEvent Active_Alarm_Count_Changed(mActive_Alarm_Count)
test()
End If
End Set
End Property
Private Sub test()
If Not Me.DesignMode Then
If mActive_Alarm_Count = 0 Then
Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Static
'console or msgbox will work but updating the image will not
Else
Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Animation
'console or msgbox will work but updating the image will not
End If
End If
End Sub
End Class
If I write to console or add a msgbox I will see the event working. But, the image will not change. If I call the test sub from a timer it will work. Why won't the button update (by the way, I did try refresh and update in the code)?
Observer pattern is what you probably looking for.
This is quick and dirty.
Create a class to hold the variable value. Add a method that adds a button instance to a list.
Then a button that needs to know about the variable calls the register method.
When the value of the variable changes, it iterates through the list of buttons and sets the Text property of each one.
You might have jumped in a bit too deep too quick here. Google Custom data binding in .net, there's loads of built in stuff you can use. Though do it yourself is a good exercise.
A simple method to do this might be:
Create a form level list to hold the buttons you are interested in
Add the buttons you are interested in, into the list (maybe in form load or some other place where you have initialization code)
Create a private property in your form with a backing variable to hold the value you want to have applied to the buttons. In the setter portion spin through the list and set each buttons text.
Dim lstButtons As New List(Of Button)
Sub SetupButtons()
'call from form load or other init code
lstButtons.Add(btnPopulate)
lstButtons.Add(btnPopulate)
End Sub
Private _buttonText As String
Private Property ButtonText As String
Get
Return _buttonText
End Get
Set(value As String)
_buttonText = value
For Each b As Button In lstButtons
b.Text = value
Next
End Set
End Property
When you set the property - which now acts as your variable - it will update all of your textboxes for you.
I realize you mentioned without having to write code - but something has to tie things together. Even if you used the observer pattern (which is an elegant solution for this - so props to those who suggested it) you'd probably end up creating a class to hold the property and have that class implement the INotifyPropertyChanged from System.ComponentModel, and then you'd also have to have each button have a databinding for its text property to the property in the object of your class. There isn't really a way (that I can think of) to get around having to write some code for each form you do this in (though the class part you'd only have to write once of course).

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 to access to the properties of an UserControl from code side?

make my own UserControl and I can aggregate new TabPages to a TabControl and then, inside of then TabPage, I add my own UserControl using the following code.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim TabX As New Windows.Forms.TabPage("Tab " & TabCount.ToString) '(ConfiguracionTabPage)
Dim MyControl As New ClientesEmpresa
MyControl.Name = "Control" & TabCount.ToString
If ClientesTabControl.TabPages.Count = 10 Then
ClientesTabControl.TabPages.RemoveAt(9)
End If
TabX.Controls.Add(MyControl)
TabX.Name = "Tab" & TabCount.ToString
TabX.Text = "Tab" & TabCount.ToString
MyControl.TitularLbl.Text = "Coca Cola"
Me.ClientesTabControl.TabPages.Insert(0, TabX)
Me.ClientesTabControl.SelectedIndex = 0
TabCount += 1
End Sub
My user control have several Labels, TextBox and TabPages(inside of a TabControl).
Now I want to change some properties dynamically from the source code, but I don't know how to access them.
The most similar theme that I found is this How to Acces of an User control in c#, but, as the title says, is in C#, how I can do it in VB.NET?
Sorry, I just notice that the Enter key post the comment. :(
Thanks for your feedback, I understand what are you saying but I missing something in the middle.
When I create the control in running time in the above code I can access easily to the properties of the created object, in this case my UserControl, but I don't understand how to reach the properties of a particular instance of that control from outside of Button_Click; ie. another button_click event(second button)
I was thinking to use something like
Dim ControlList As Windows.Forms.Control() = Me.ClientesTabControl.TabPages(0).Controls.Find("ModeloLbl", True)
or
ClientesTabControl.TabPages(0).Controls.OfType(Of AlarmasVehiculo)()
But I'm stuck here.
------------------------------------- 3th post ---------------
Thanks Steve, I was resolved using "Control.Find" and a For Each but your solution is easier.
There's any way to get the name of the selected tab or I must to create an Array when I create the New TabPage?, the idea is to update the text of the controls inside of the selected tab only when is selected by the user or every 5 seconds but just the in selected one.
Thanks.
To borrow M4N's answer from the C# question, and translate it to VB:
Cleanest way is to expose the desired properties as properties of your usercontrol, e.g:
Public Class MyUserControl
' expose the Text of the richtext control (read-only)
Public ReadOnly Property TextOfRichTextBox As String
Get
Return richTextBox.Text
End Get
End Property
' expose the Checked Property of a checkbox (read/write)
Public Property CheckBoxProperty As Boolean
Get
Return checkBox.Checked
End Get
Set (value As Boolean)
checkBox.Checked = value
End Set
End Property
'...
End Class
In this way you can control which properties you want to expose and whether they should be read/write or read-only. (of course you should use better names for the properties, depending on their meaning).
Another advantage of this approach is that it hides the internal implementation of your user control. Should you ever want to exchange your richtext control with a different one, you won't break the callers/users of your control.
To answer your second question, if you need to access your dynamically created controls, you can do so easily using their names, for instance:
Dim c As ClientesEmpresa= CType(Me.ClientesTabControl.TabPages("Tab1").Controls("Control1"), ClientesEmpresa)
c.CheckBoxProperty = True

Dynamic properties for classes in visual basic

I am a vb.net newbie, so please bear with me. Is it possible to create properties (or attributes) for a class in visual basic (I am using Visual Basic 2005) ? All web searches for metaprogramming led me nowhere. Here is an example to clarify what I mean.
public class GenericProps
public sub new()
' ???
end sub
public sub addProp(byval propname as string)
' ???
end sub
end class
sub main()
dim gp as GenericProps = New GenericProps()
gp.addProp("foo")
gp.foo = "Bar" ' we can assume the type of the property as string for now
console.writeln("New property = " & gp.foo)
end sub
So is it possible to define the function addProp ?
Thanks!
Amit
It's not possible to modify a class at runtime with new properties1. VB.Net is a static language in the sense that it cannot modify it's defined classes at runtime. You can simulate what you're looking for though with a property bag.
Class Foo
Private _map as New Dictionary(Of String, Object)
Public Sub AddProperty(name as String, value as Object)
_map(name) = value
End Sub
Public Function GetProperty(name as String) as Object
return _map(name)
End Function
End Class
It doesn't allow direct access in the form of myFoo.Bar but you can call myFoo.GetProperty("Bar").
1 I believe it may be possible with the profiling APIs but it's likely not what you're looking for.
Came across this wondering the same thing for Visual Basic 2008.
The property bag will do me for now until I can migrate to Visual Basic 2010:
http://blogs.msdn.com/vbteam/archive/2010/01/20/fun-with-dynamic-objects-doug-rothaus.aspx
No - that's not possible. You'd need a Ruby like "method_missing" to handle the unknown .Foo call. I believe C# 4 promises to offer something along these lines.