Passing two similar forms as the same type and still being able to access its objects - vb.net

I am trying to implement another form into already complete routine. Basically all the code is there, all I need to do is manipulate the data in a different manner.
I have a routine that looks like this for instance.
This is a paraphrase example:
Private Sub getReportValues(ByRef fr As frmCustomReport, ByRef ReportInfo As ReportValues)
ReportInfo.eHeaderColor = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Software\FE Jupiter\MSSMonitor").GetValue("Report Equipment Header Color", "DCDCDC") 'Gainsboro
ReportInfo.mHeaderColor = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Software\FE Jupiter\MSSMonitor").GetValue("Report Monitor Header Color", "FFF8DC") 'Cornsilk
fr.btnEquipColor.PickedColor = System.Drawing.ColorTranslator.FromHtml("#" & Microsoft.VisualBasic.Conversion.Hex("&HFF" & ReportInfo.eHeaderColor))
The problem lays with the (fr as frmCustomReport) I want to make it a system.windows.forms.form but then I would lose the ability to use its objects. I should also mention that fr is a modal dialog and that I don't want a really hacky controlcollection work around for this. Does anyone have a good direction on this?
Note Also!!! The controls I want to access on both forms are almost identical. The only diffrence is layout and some added functionality.

Without a little more information,it is a little hard to give a concrete example. This will work depending on the amount of interaction you need to do. Create a subroutine that accepts the Base Class as an argument, take a look at the Name value and base your conditional logic off of that using CType to cast the Form to the proper type.
Private Sub clickOtherFormsButton(Value As Form)
If Value.Name = "Form3" Then
Dim formObject As Form3 = CType(Value, Form3)
formObject.Button1.PerformClick()
ElseIf Value.Name = "Form2" Then
Dim formObject As Form2 = CType(Value, Form2)
formObject.Button1.PerformClick()
End If
End Sub

Related

VBA Settings Dialog using MVP - do I need a model?

I've been reading up on many examples of MVP (Model-View-Presenter) and their variations (Passive view, Supervising view) to try and make my solutions more robust (and reusable) in VBA (using Excel as the host in this instance). The problem I've found is finding good, simple examples in VBA that are not complete overkill for the (hopefully) simple examples I need.
I'm attempting to create a "settings" dialogue that stores certain configuration in a worksheet (this is my "repository").
Here's my main procedure, triggered by the user:
Private Sub ShowImportSelector()
Dim importPresenter As DataImportPresenter
Set importPresenter = New DataImportPresenter
importPresenter.LoadConfig
If importPresenter.Show = -1 Then Exit Sub
importPresenter.SaveConfig
' begin processing...
If (CStr([Settings.SelectedVersion].Value2) = "QQ") Then
' ...
End If
End Sub
Here is my "presenter" (here I use range names for the source, and config destination):
Option Explicit
Private m_importForm As FImport
Private Sub Class_Initialize()
Set m_importForm = New FImport
End Sub
Public Sub LoadConfig()
m_importForm.SetAvailableVersions "tblVERSION"
m_importForm.SetAvailableSalesOrgs "tblSALESORG"
m_importForm.SetAvailableCategories "tblCATEGORY"
m_importForm.ToolName = "Forecast"
End Sub
Public Sub SaveConfig()
[Settings.SelectedVersion].Value2 = m_importForm.SelectedVersion
[Settings.SelectedSalesOrg].Value2 = m_importForm.SelectedSalesOrg
[Settings.SelectedCategory].Value2 = m_importForm.SelectedCategory
End Sub
Public Function Show() As Integer
m_importForm.Show vbModal
Show = m_importForm.Result
End Function
And now the "View" (a VBA Form):
Option Explicit
Private m_selectedVersion As String
Private m_selectedSalesOrg As String
Private m_selectedCategory As String
Private m_toolName As String
Private m_dialogueResult As Long
Public Property Get ToolName() As String
ToolName = m_toolName
End Property
Public Property Let ToolName(ByVal value As String)
m_toolName = value
ToolNameLabel.Caption = value
End Property
Public Property Get Result() As Long
Result = m_dialogueResult
End Property
Public Property Get SelectedVersion() As String
SelectedVersion = m_selectedVersion
End Property
Public Property Get SelectedSalesOrg() As String
SelectedSalesOrg = m_selectedSalesOrg
End Property
Public Property Get SelectedCategory() As String
SelectedCategory = m_selectedCategory
End Property
Public Sub SetAvailableVersions(ByVal value As String)
VersionSelector.RowSource = value
End Sub
Public Sub SetAvailableSalesOrgs(ByVal value As String)
SalesOrgSelector.RowSource = value
End Sub
Public Sub SetAvailableCategories(ByVal value As String)
CategorySelector.RowSource = value
End Sub
Private Sub SaveSelections()
m_selectedVersion = VersionSelector.value
m_selectedSalesOrg = SalesOrgSelector.value
m_selectedCategory = CategorySelector.value
End Sub
Private Sub CloseButton_Click()
m_dialogueResult = -1
Me.Hide
End Sub
Private Sub ImportButton_Click()
SaveSelections
m_dialogueResult = 0
Me.Hide
End Sub
At this point, I have become confused with the possible directions I could go in terms of adding a model to the above - question is: is this even needed for this simple example?
MVP architecture makes cleaner code, but cleaner code isn't the primary purpose of MVP; achieving loose coupling, higher cohesion, and testability is.
If loosely-coupled components and unit-testable logic isn't a requirement, then full-blown MVP is indeed overkill, and having the "model" exposed as properties on the "view" is definitely good enough, as it already helps making your "presenter" not need to care about form controls. You're treating the form as the object it's begging to be, and pragmatically speaking this could very well be all you need. I'd make the Show method return an explicit Boolean though, since it's implicitly used as such.
On the other hand, if you are shooting for decoupling and testability, then extracting the model from the view would only be step one: then you need to decouple the presenter from the worksheet, and maybe introduce some ISettingsAdapter interface that abstracts it away, such that if/when the configuration needs to go to a database or some .config file, your presenter code doesn't need to change in any way... but this requires designing the interfaces without having any particular specific implementation in mind, i.e. something that works without changes regardless of whether the data is on a worksheet, in some flat file, or in some database table.
MVP demands a paradigm shift: MVP isn't procedural programming anymore, it's OOP. Whether OOP is overkill for your needs depends on how much coupling you're willing to live with, and how frail this coupling is making your code in the face of future changes. Often, abstraction is enough: using named ranges instead of hard-coded range addresses is one way of improving the abstraction level; hiding the worksheet behind an adapter interface implemented by a worksheet proxy class (whatever you do, never make a worksheet module implement an interface: it will crash) is another - depends where your threshold for "overkill" is, but if you do achieve full decoupling and write the unit tests, nobody can blame you for going overboard: you're just following the industry best-practices that every programmer strives for, improving your skills, and making it much easier to later take that code and rewrite it in .NET, be it VB or C#. I doubt anyone would argue that full-blown MVP is overkill in .NET/WinForms.

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).

VB.NET Call Sub of another form

I know, that there are many questions related to this, but still I cannot find a workable solution.
Usually, it would work like this: A form creates an instance of another form in it's container like this:
Dim PolInstIn As New SubForm1
Private Sub LoadDetail()
PolInstIn.TopLevel = False
PolInstIn.Name = "Sub From"
PolInstIn.FormBorderStyle = Windows.Forms.FormBorderStyle.None
PolInstIn.Dock = DockStyle.Fill
Me.GroupBox6.Controls.Add(PolInstIn)
PolInstIn.Show()
End Sub
Then it's easy to call a Public Sub from the sub form like this:
Call PolInstIn.MyPublicSubInSubForm1()
However, this doesn't work for me in this case. When I run MyPublicSubInSubForm1() it doesn't throw any error, but does no action. If I write a value to SubForm1 textbox and read it back, it reads, but I don't see it on the screen, so I suspect it is written to some other accidental instance.
I suspect it is because my parent form is also an instance of an form created in very similar way like SubForm1. Basically the ParentForm is a form loaded into tabPage and SubForm1 is a module loaded into ParentForm. It can exist in many copies (tabs).
Could you point to any simple solutions?
Regards,
Libor
I see this question got a lot of views, so here is an answer.
1) No visual response of child form (only results) - this could have happened if I created more then 1 instances of the form. The example is just an example, but if one use it (accidentally) this way, it may result in new definition of a child form every time (and consequent symptoms like the ones described). In practice, I split form loading from loading data into to the form (done by a public sub in that form).
2) If you want also a back reference (to i.e. parent grid form), define a Public ParentFormGrid as GridName (note ParentForm is a reserved name) and on loading a child form, set
PollInstIn.ParentFormGrid = Me
This way you can alway asccess the parent form, i.e. reload the grid when you save changes on a line edited in child form.
make Private Sub LoadDetail() to a public :
Public Sub LoadDetail()
It work on my project. Hopely its what you want
Dim PolInstIn As New SubForm1
Private Sub LoadDetail()
PolInstIn.Name = "Sub From"
PolInstIn.Show()
PolInstIn.TopLevel = False
PolInstIn.FormBorderStyle = Windows.Forms.FormBorderStyle.None
PolInstIn.Dock = DockStyle.Fill
PolInstIn.Update()
PolInstIn.Refresh()
Me.GroupBox6.Controls.Add(PolInstIn)
End Sub

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

Restrict type in a Collection inside a class module

I have a collection inside a class module. I'd like to restrict the object type that is "addable" to this collection, i.e. collection should only ever accept objects of one given type and nothing else.
Is there any way to enforce the type of objects added to a collection?
From what I can tell, there is no built-in way to do this. Is the solution then to make this collection private, and build wrapper functions for the methods usually accessible for Collections, i.e. Add, Remove, Item, and Count?
I kinda hate having to write 3 wrapper functions that add no functionality, just to be able to add some type enforcement to the Add method. But if that's the only way, then that's the only way.
There is no way to avoid wrapper functions. That's just inherent in the "specialization through containment/delegation" model that VBA uses.
You can build a "custom collection class", though. You can even make it iterable with For...Each, but that requires leaving the VBA IDE and editing source files directly.
First, see the "Creating Your Own Collection Classes" section of the old Visual Basic 6.0 Programmer's Guide:
http://msdn.microsoft.com/en-us/library/aa262340(v=VS.60).aspx
There is also an answer here on stackoverflow that describes the same thing:
vb6 equivalent to list<someclass>
However, those are written for VB6, not VBA. In VBA you can't do the "procedure attributes" part in the IDE. You have to export the class module as text and add it in with a text editor. Dick Kusleika's website Daily Dose of Excel (Dick is a regular stackoverflow contributer as you probably know) has a post from Rob van Gelder showing how to do this:
http://www.dailydoseofexcel.com/archives/2010/07/04/custom-collection-class/
In your case, going to all that trouble - each "custom collection" class needs its own module - might not be worth it. (If you only have one use for this and it is buried in another class, you might find that you don't want to expose all of the functionality of Collection anyway.)
This is what I did. I liked Rob van Gelder's example, as pointed to by #jtolle, but why should I be content with making a "custom collection class" that will only accept one specific object type (e.g. People), forever? As #jtolle points out, this is super annoying.
Instead, I generalized the idea and made a new class called UniformCollection that can contain any data type -- as long as all items are of the same type in any given instance of UniformCollection.
I added a private Variant that is a placeholder for the data type that a given instance of UniformCollection can contain.
Private mvarPrototype As Variant
After making an instance of UniformCollection and before using it, it must be initialized by specifying which data type it will contain.
Public Sub Initialize(Prototype As Variant)
If VarType(Prototype) = vbEmpty Or VarType(Prototype) = vbNull Then
Err.Raise Number:=ERR__CANT_INITIALIZE, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__CANT_INITIALIZE) & _
TypeName(Prototype)
End If
' Clear anything already in collection.
Set mUniformCollection = New Collection
If VarType(Prototype) = vbObject Or VarType(Prototype) = vbDataObject Then
' It's an object. Need Set.
Set mvarPrototype = Prototype
Else
' It's not an object.
mvarPrototype = Prototype
End If
' Collection will now accept only items of same type as Prototype.
End Sub
The Add method will then only accept new items that are of the same type as Prototype (be it an object or a primitive variable... haven't tested with UDTs yet).
Public Sub Add(NewItem As Variant)
If VarType(mvarPrototype) = vbEmpty Then
Err.Raise Number:=ERR__NOT_INITIALIZED, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__NOT_INITIALIZED)
ElseIf Not TypeName(NewItem) = TypeName(mvarPrototype) Then
Err.Raise Number:=ERR__INVALID_TYPE, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__INVALID_TYPE) & _
TypeName(mvarPrototype) & "."
Else
' Object is of correct type. Accept it.
' Do nothing.
End If
mUniformCollection.Add NewItem
End Sub
The rest is pretty much the same as in the example (plus some error handling). Too bad RvG didn't go the whole way! Even more too bad that Microsoft didn't include this kind of thing as a built-in feature...
I did almost the same code of Jean-François Corbett, but I adapted because for some reason wasn't working.
Option Explicit
Public pParametro As String
Private pColecao As New Collection
Public Sub Inicializar(ByVal parametro As String)
pParametro = parametro
End Sub
Public Sub Add(NewItem As Object)
If TypeName(NewItem) <> pParametro Then
MsgBox "Classe do objeto não é compatível à coleção"
Else
pColecao.Add NewItem
End If
End Sub
Public Property Get Count() As Long
Count = pColecao.Count
End Property
Public Property Get Item(NameOrNumber As Variant) As Variant
Set Item = pColecao(NameOrNumber)
End Property
Sub Remove(NameOrNumber As Variant)
pColecao.Remove NameOrNumber
End Sub
Then, when i want to create an instance from CCollection I make like the code bellow:
Set pFornecedores = New CCollection
pFornecedores.Inicializar ("CEmpresa")
Where CEmpresa is the class type from the object I want
Yes. The solution is to make your collection private and then make public wrapper functions to add, remove, getitem and count etc.
It may seem like hassle to write the additional code but it is a more robust solution to encapsulate the collection like this.