I'm making an extremely simple app which has multiple screens (as Panels) that are hidden/shown clicking buttons on a left sidebar.
I have a PNLHome, PNLServerSettings, PNLScripts, and PNLSettings as the main panels.
Each of the Panels .Name properties are "PNLHome", "PNLServerSettings", ....etc.
I want to be able to change the "Start Up Screen" based on the selection you make in a ComboBox in PNLSettings, so when you launch the app, the Panel that is immediately showing is based on that ComboBox selection.
I have the ComboBox items in place and I have a function that hides all panels and shows the one you pass into it:
showPanel(PNLHome) for example which will essentially just show the Panel you give it, all this is working fine. This showPanel() is triggered upon clicking one of the main buttons on the left sidebar
What I want is to pass a Control.Name string into my showPanel() func, then I want to store this .Name string into My.Settings via a "Save/Apply" button in the Settings Panel which I can set up easily.
Since I will be passing the .Name String into my showPanel() function, I need to be able to reference the Panel I'm showing by it's name rather than the Object ID itself (I'm not sure if it's called an ID but it's the 'name' declaring the Panel via a "DIM WithEvents PNLHome As Panel" declaration.
To summarize the question; Can I reference the PNLHome by its .Name Property?
Otherwise can I store the ID of PNLHome directly in My.Settings instead? I could easily pass My.Settings.StartPanel into my showPanel() func.
Just use the Controls collection to find the control.
Private Function GetPanel(PanelName As String) As Panel
Dim SomePanel As Panel = CType(Controls(PanelName), Panel)
Return SomePanel
End Function
Related
In different tabs of a tabstrip I have input values which are different in each tab. I need to write a code which takes all these values and do some work like sums up the values of each tab on button click.
Can anyone help me do this? In my code when I input value at a textbox of one tab it also changes the value of all other tabs and hence cannot receive different values of each tab. Any idea, please?
Multipages vs Tabstrips
A multipage is an object with "pages". Each page can hold it's own collection of controls which can then be referenced either directly or through the containing page object.
A tab strip is an object with "tabs". Unlike a "page" object, a tab does not have it's own controls. Instead, there are only the original controls, visible for all "tabs".
Programmatic differences
Since a multipage has a different set of controls for each page, there is very little housekeeping required. The selection of a page affects which controls are visible, and the controls automatically retain values assigned to them (as expected).
For a tabstrip, since there is only the initial set of controls, there is a lot of housekeeping required in the code. The selection of a tabstrip does not have any automatic effect on the values in the controls. Instead the controls act as would be expected if they were not in the tab strip at all.
Tabstrip Solution
Set up a variable (array, collection, dictionary) that can be used to hold the various values of the controls. Then, in the TabStrip_Change() event, store the previous value, and reset the control for the new tab (or fill in the value the new tab last held).
I recommend adding a userform level variable Dim old_tab as Long which you can set to the current page at the end of the TabStrip_Change() event. (This is useful for retrieving previously filled values for the correct tab).
For my sample code, I will be using an array. However, since arrays are not very flexible with changing lengths, you can also look into using either a dictionary or collection, if desired.
For the userform pictured below, the following code causes the single textbox to act as though there is a different textbox per tab. It also saves the values whenever the tabstrip changes. (Note: if you then use the saved values for the calculation, remember to update the value for the current tabstrip first.)
Option Explicit
Dim old_tab As Long
Dim textValues As Variant
Private Sub TabStrip1_Change()
textValues(old_tab) = TextBox1.Value 'Saves the old value
TextBox1.Text = textValues(TabStrip1.Value) 'Updates value to reflect tab change
old_tab = TabStrip1.Value 'updates tab # variable
End Sub
Private Sub UserForm_Initialize()
ReDim textValues(0 To TabStrip1.Tabs.Count - 1) 'tabs are zero-based, so count is always one more than the maximum tab value
old_tab = TabStrip1.Value 'Ensures that the first value will be saved to correct location at the tab change
End Sub
Let's say I have a MainForm in Access.
On that MainForm is a SubForm control.
I also have another form called AnotherForm that I want to plug into the MainForm's SubForm control using it's SourceObject property.
I then want to capture the instance of the newly created AnotherForm in a variable.
I can do that like this:
'Inside the MainForm's code-behind
Me.SubForm.SourceObject = "Form.AnotherForm"
Dim af As Form_AnotherForm
Set af = Me.SubForm.Form
Cool, af now holds the instance of the form created by the SubForm control when I set it's SourceObject property.
Now, instead, let's say I want to reverse that process by first newing up an instance of AnotherForm and then passing it to the SubForm control.
Theoretically it would look something like this:
'Inside the MainForm's code-behind
Dim af As Form_AnotherForm
Set af = New Form_AnotherForm
Me.SubForm.Form = af 'ERROR: Read-only
Any way to accomplish that using some other method?
I want to be able to attach different forms to the SubForm control and switch them out at run-time. However, if I attach AnotherForm to SubForm, then attach YetAnotherForm to SubForm, then I go back and attach AnotherForm to SubForm again, I want to use the same instance of AnotherForm as I used the first time so that any changes on it won't be lost when it reappears.
As I know, it's impossible. You cannot replace instance of subform, you can only change SourceObject, this operation destroys previous instance of subform and creates new one "from scratch".
I would use in your case tab control for holding instances of all subforms you want to switch between: create tab control with tabs style none, make borders transparent, so the appearance of subforms will be the same, as if one subform control used.
For switching between visible subforms just assign required value to tab conrol
Did you try using the Set statement:
Set Me.SubForm.Form = af
I have 4 checkboxes but we need to restrict selection to just a single one, meaning if you check the first, the other 3 will go unchecked. I know we could use ActiveX radio buttons but we'd prefer to avoid ActiveX if possible, plus with check boxes we have more control over the layout.
I've set the name of the checkbox appropriately to Check1:
And then I've put this very basic script into the Visual Basic section:
Private Sub Check1_Click()
Check1.Enabled = True
Check2.Enabled = False
Check3.Enabled = False
Check4.Enabled = False
End Sub
But unfortunately checking the first box doesn't uncheck the next 3.
Any ideas please? Thank you!
If these are Content Controls, as you indicate, then they do not have a CLICK event. Nor can they be identified by VBA by their Title property. The code you show us is for ActiveX controls, which you say you don't want to use...
Working with content control events is not as simple and intuitive as with ActiveX controls. Similar to form fields, Content Controls only have "editing" events that trigger on the user entering and exiting the content control. These events are available in the ThisDocument module, in the Document category.
The same ContentControlOnExit event triggers for ALL content controls in the document, so you need a Select Case or If conditional to query the ContentControl argument in order to know which content control was exited.
In order to address the other checkboxes you need to use the Document.SelectContentControlsByTitle (or ...ByTag) method which returns an array of all content controls with that title (or tag).
If you really want to emulate a "click" event then you need to insert a Custom XML Part in the document with nodes linked to the content controls. When the user changes the state of the control the ContentControlBeforeStoreUpdate event will trigger, letting you take action.
The property you need is Value, not Enabled.
The purpose of property Enabled is to prevent a control from being changed by user.
Additionaly, you need to prevent it from the events cascade. It means that when you change programatically the value of Check2, this will trigger Private Sub Check2_Click() and so on.
In order to make it work you should change your code like that:
Private Sub Check1_Click()
If Check1.Value Then
Check1.Value = True
Check2.Value = False
Check3.Value = False
Check4.Value = False
End If
End Sub
and similarly for the other check boxes.
For your purpose radio buttons will be better choice. Radio buttons have built-in functionality to uncheck currently selected button if other one is checked.
How can you programmatically access controls on Tab Pages?
My code has many properties that I want to allow the user to initially set when the program starts. These properties will be used to define an Excel chart like title name, font colors, font size, chart position and size, X & Y series and many others properties. The program produces many charts each of which have their own set of properties. Each chart has the same set of properties but different values.
My solution using Form1 Designer:
I Placed the TabControl1 control from the VS 2010 toolbox on Form1.
I created 19 TabPages on TabControl1
On each TabPage I created multiple GroupBoxes for organization.
Finally, I placed multiple controls like RadioButtons, TextBoxes, and Spinners in each of the GroupBoxes.
All of the above was done at design time.
Bad Example:
Dim TC1 As TabControl = TabControl1.TabPages.Item(1)
This is a bad example but I’m thinking I need to do something like this, i.e. before I can reach the actual user inputs via the RadioButtons, TextBoxes, or Spinners child controls I need to tell the complier the name of the TabControl1 and each TabPage contained in it. I’m not sure about each of the GroupBoxes that contain groups of RadioButtons and etc.
My Question: How do you programmatically access controls located on multiple GropBoxes located on each of the Tab Pages? I want to retrieve all the user input properties values on each of many TabPages and apply them when I create the Excel Charts.
A TabControl is just a fancy way to organize controls on the same form. The point is that they are on the same form! You probably know that you use Me to access the current form, then .ControlName to access the control. Consider a TabControl with two tabs and on each tab there is a TextBox. These TextBoxes are on the same form, so you still need to use
Me.TextBox1.Text = "Text 1" ' the same as TextBox1.Text = "Text 1", but I like to be explicit
Me.TextBox2.Text = "Text 2"
This you already knew, as your problem stemmed from this behavior. However, there are some tricks to help you. Consider a TabControl with two tabs, and on page 1 there is a control named tc1TextBox1 and on page 2 tc2TextBox1. I'm using the prefix to distinguish between the pages. An additional TextBox on page 1 would be named tc1TextBox2 etc., then say you wanted CheckBoxes, you could have tc1CheckBox1 and tc1CheckBox2 etc. The way I would access them is with LINQ
Define an extension method in a module
Imports System.Runtime.CompilerServices
Public Module ExtensionMethods
<Extension()> _
Public Function ChildControls(Of T As Control)(ByVal parent As Control) As List(Of T)
Dim result As New List(Of Control)
For Each ctrl As Control In parent.Controls
If TypeOf ctrl Is T Then result.Add(ctrl)
result.AddRange(ctrl.ChildControls(Of T)())
Next
Return result.ToArray().Select(Of T)(Function(arg1) CType(arg1, T)).ToList()
End Function
End Module
This method returns you all the Controls inside parent, and all Controls inside those, and so on, recursively. It also gives you an IEnumerable List(Of Control) in contrast to Control.Controls() which is not IEnumerable and only returns controls in the control, not in its containers.
Using LINQ, you can filter the results of ChildControls by Type or Name. For instance, the code
Dim textBoxes = Me.ChildControls(Of TextBox)()
returns all the TextBoxes in Me. Let's take it a step further:
Dim textBoxes = Me.ChildControls(Of TextBox)().Where(Function(tb) tb.Name.StartsWith("tc1"))
returns all the TextBoxes with the prefix "tc1". So you see you can set up the controls on your form to better group them at runtime. Here's an idea specific for Excel sheets
Dim xlBook As Excel.Workbook
For i As Integer = 1 To 20
Dim nameString = "txtSheetName" & i.ToString() ' TextBoxes named txtSheetName1, txtSheetName2, etc.
xlBook.Sheets(i).Name = Me.ChildControls(Of TextBox)().Where(Function(tb) tb.Name = nameString).First().Text
Dim enabledString = "chkEnabled" & i.ToString() ' CheckBoxes named chkEnabled1, chkEnabled2, etc.
xlBook.Sheets(i).Enabled = Me.ChildControls(Of CheckBox)().Where(Function(tb) tb.Name = enabledString).First().Checked
'etc.
Next
which will loop over your tab control indices, and set properties for each corresponding excel sheet. This code should give you a good idea of a direction you can take.
I need to loop through the textboxes of my form, which are located inside a panel. I'm trying to do it like this....
For Each Cntrl As Control In DirectCast(Me.Controls(PanelName), Panel).Controls
next
The reason I need to feed the panel's name to the loop is because I have two panels that exist in 2 different TabPages. A button exists on each TabPage that performs the same function, but the panels and textboxes have different names. I have the loop inside a function that handles both TabPage button clicks, therefore I need the loop to point to the right panel.
The "PanelName" matches the target panel's name, and the panel is of System.Windows.Form.Panel.
I get the "Object reference not set to an instance of an object." message.
Is this not possible?
No need to cast:
For Each Cntrl As Control In PanelName.Controls
Next
If all you have is the name of the panel and not the control reference, then you at least need to know which TabPage has the panel:
For Each ctrl As Control In TabControl1.TabPages(1).Controls("panel1").Controls
Next
Alternatively, you can use:
Me.Controls.Find(PanelName, True)
to return the panel by name while searching child controls recursively.