Creating an userform object hierarchy with grouping in vba - vba

First things first. There's a good chance what I want to do should really be done with VB and not VBA. But as long as it is possible I would rather use VBA.
I have a userform of essentially a big diagram made of hundreds of labels. I want to separate these labels into groups. And then separate these groups into subsystems. The idea being I have some form of heirarchy to work with. The label groups need to change color based on what I have selected in a combo box, and if I click on one of these labels I want to bring up a user form showing details of the subsystem using click events.
I'm pretty sure I need to use multiple classes to do what I want but am fairly new to using class modules. Though I get the concept.
Basically I want some functionality that goes subsystem -> label group( or part) -> color with click events for the whole subsystem and combo box events for changing label group colors.
I saw a thread online about grouping labels or text boxes but it only works to trigger the even for a group, not change the properties of the whole group once the event is triggered. I would like to set this up in classes as well so I can export the system for use in other future userforms.
I was able to create groups of labels and change them together like I wanted:
CPart (Class Module 1):
*This is meant to handle the event triggering of the labels and includes some color code that I used to test functionality of the groups changing together and functionality of changing colors.
Public WithEvents trigger As MSForms.Label
Dim pLabels As Collection
Property Set triggers(c As Collection)
Set pLabels = c
End Property
Private Sub trigger_Click()
For Each obj In pLabels
obj.BackColor = RGB(255, 0, 0)
Next obj
End Sub
CTrigger (Class Module 2):
*This took a collection of labels which were passed in through a collection variable in the userform and then stored each label as a trigger in a separate class variable, along with the whole collection of labels in that group. This way when any trigger fires the event, all of the labels change.
Dim CTrigger() As New CPart
Dim pLabels As Collection
Dim i As Integer
Property Set Labels(c As Collection)
Set pLabels = c
For i = 1 To pLabels.Count
ReDim Preserve CTrigger(1 To i)
Set CTrigger(i).trigger = pLabels.Item(i)
Set CTrigger(i).triggers = pLabels
Next i
End Property
Property Get Labels() As Collection
Labels = pLabels
End Property
I really don't like the way it works, partly because I am losing myself in the logic of it constantly, and partly because it means that in order to use this I have to make collections of labels in the userform module anyway just to run it. It is very inefficient code, but I am putting it up so you get an idea of what I am trying to accomplish.
What I would much rather do instead is have one class variable to hold my custom collection of labels (a "LabelGroup"). Another class variable is likely required to hold the labels themselves (I think). And then all I would have to do is go through and write methods for the LabelGroup class such as changecolor, and it could handle that. But I can handle that part, for now what I really need help with is setting up the class framework in a neat way, so that the module I will eventually run could just say things like:
LabelGroup1.Add Label1
LabelGroup2.Add Label2
or
Private Sub button_click()
LabelGroup1.ChangeColor(RGB(...))
End Sub
These two articles have been helping me along:
http://www.databaseadvisors.com/newsletters/newsletter200503/0503usingcustomcollections/using%20custom%20collections%20in%20microsoft%20access.asp
http://j-walk.com/ss/excel/tips/tip44.htm

I was just looking at something similar but not quite so detailed. I'm trying to improve the look of a complex userform by making it look more modern and was going to try to fake mouseOver highlighting or at least active/inactive shading for labels placed overtop of graphical buttons.
Anyway, have you considered just changing the names of the label objects so that they are prefixed/suffixed with some kind of group or subsystem ID?
That way when you pass them to a sub to change their colour, you can check the prefix or suffix.

Related

PowerPoint VBA add shape event/ add shape with tag value

i'am currently trying to add a small function to PowerPoint using VBA, my goal is to create a gadget that works kind of like Photoshop graphics layer.
My plan is to add a layer name Tag on each shape that is drawn by user, so later I can parse through every item by loop and preform lock/unlock, show/hide shapes based on it's tag value, such as:
Sub add_shape_with_layer_tag()
Set islide = ActivePresentation.Slides(1)
Set ishape = islide.Shapes.AddShape(msoShapeRectangle, 5, 5, 80, 60)
ishape.Tags.Add "Layer", "1"
End Sub
Sub show_hide_layer_one_shapes()
Dim active_slide As Slide
Set active_slide = ActiveWindow.View.Slide
For Each ishape In active_slide.Shapes
If ishape.Tags("Layer") = "1" Then
ishape.Visible = Not (ishape.Visible)
End If
Next ishape
End Sub
However, I couldn't found a way to achieve this, so I would like to ask whether there's a method that provides following functions?
override add shape function so I can sneak tag value in to shape every time user drawn a shape
catch add shape event (if this thing did exist) so i can add tag to the last added item
a way to set a default tag value to shape, just like setting default shape color/line width
or is there any better options that is also viable?
thanks.
20200728
To John, thanks for the advice, I did found some interesting event that might be helpful for some of my other projects, however I couldn't found events that able to trigger after custom function while shape is added.
To Steve, my plan is to add a modeless userform with list UI that manage layers, the shape tag and shape fill texture/color will be determined based on what list item that currently selected.
As to saving settings, I'll use VBComponents.CodeModule to dump existing settings in userforms to a VBA module and store as text, so in theory I should able to make this function self contained in one file.
Not totally an answer, sorry SO, but comments don't allow enough scope for this. So ...
To Steve, my plan is to add a modeless userform with list UI that manage layers, the shape tag and shape fill texture/color will be determined based on what list item that currently selected.
Ah, so you're creating your own "pseudo-layers". That wasn't clear. Thanks for the add'l info. As it happens, I have a selection manager add-in that works very similarly to what you're proposing.
To John, thanks for the advice, I did found some interesting event that might be helpful for some of my other projects, however I couldn't found events that able to trigger after custom function while shape is added.
The SelectionChange event should get you there. When it fires, you'll need to check first to see if the current selection is a shape or something else. If a shape, check to see if its .Index = current slide's .Shapes.Count and also to see if you've already tagged it. If no tag AND if it's the correct index, it'll be a newly added shape. If you're tagging ALL shapes, you may only need to check to see if the .Tag(name) is blank.
As to saving settings, I'll use VBComponents.CodeModule to dump existing settings in userforms to a VBA module and store as text, so in theory I should able to make this function self contained in one file.
Why not save any needed slide or presentation level info as further slide- or presentation-level tags? PPT can absorb quite a lot of info as tags w/o getting cranky.

Using List(Of Interface) rather than a Collection doesn't populate DataGridView properly

I have a screen which can display three sets of data, from three different classes. However, it should only display one at a time depending on the mode it is set to upon initialization.
Naturally, these three classes (I have only written one so far) implement an interface (IExportSelectionStructure) which defines their shared property: Selected. There are no other properties these classes share which can be put in the interface.
The screen has the following code:
Private _BaseStructure As IExportStructure
Private _DataSet As List(Of IExportSelectionStructure)
Public Sub New(exportMode As ExportRunMode)
'BaseStructure setup goes here
_DataSet = _BaseStructure.GetDataSet(_Session, _Settings)
With ExportDataGridView
.AutoGenerateColumns = False
.Columns.AddRange(_BaseStructure.GenerateExporterColumns.ToArray)
.Datasource = _DataSet
End With
End Sub
I'm running in Customer export mode.
At this point,_DataSet has a three ExportCustomerSelectionStructure classes inside, along with the expected data inside each class.
However, when it gets set as the datasource for the gridview, the gridview displays three records, but the columns are empty, except the "Selected" column - which is a CheckBoxCell.
In terms of debugging this, I tried switching _DataSet to a Collection, and also got IExportStructure.GetDataSet to return a Collection - lo and behold, the data appears on screen absolutely fine, but I don't want to use a Collection, I want to use a List(Of IExportSelectionStructure). I can't go any more specific than the Interface, and I don't really want to go any more generic than that.
What am I doing wrong, and have I missed anything obvious?
Okay, so this was a strange one.
After struggling for two days with this I realised I had to cast the list of generics back to the specific class I am working with dependent on the mode of the screen, otherwise the DataGridView doesn't know what class of data you're working with! As such, the code around this for me is:
_DataSet = _BaseStructure.GetDataSet(_Session, _Settings)
Select Case _ExportRunMode
Case ExportRunMode.Customers
ExportDataGridView.Datasource = _DataSet.Cast(Of ExportCustomerSelectionStructure).ToList
'Etc...
End Case

Is there a way to consolidate iteration through form controls for different activities

I just started rewriting an application from vba (Access) to vb.net + SQLServer so not very experienced in .net.
I am creating custom controls (Form + form controls) with a number of extra properties PrevValue, Modified (similar then the one of Textbox), Dirty, DirtyEnabled, SQLColumnName, SQLTableName to enable AutoUpdating and undoing in my forms the form exposes IsDirty, Initialising and Isready properties and an undo method.
Doing so it occurs that I have to write 3 times the same iteration code in different places:
For each Ctrl as Control in frm.Controls ' frm being a reference to the form
if typeOf Ctrl is MyTextBox
with DirectCast(Ctrl, MyTextBox)
' here comes the variable code depending what needs to be done
end with
elseif TypeOf Ctrl is MyComboBox
' etc.... for MyListBox, MyCheckBox etc....
I also have a number of custom controls MyNumBox and MyDateBox that inherit from MyTextBox but with some modified behavior (Formula evaluation, date manipulation, calendar...) how do I avoid doing an extra test on them.
One version of this Iteration is in the SQLProcessClass where the modified controls are added as SQLParameter and after iteration calling the SQLProcessClass Update or Insert, but ... after successful SQL activity I need to iterate through the controles again to reset the modified flag for each control. Elsewhere I need it to implement a form undo to reset all the controls to their previous values.
It seems to me I have two options
1. repeating that iteration code everywhere I need to iterate through the forms controls. I don't like it as every time I would need to create a new custom control I have to add some lines X times in different modules/classes ... very bad programming
2. Creating one form iteration procedure containing all the different activities that normally belong to another class within that "centralised" procedure, that could be better then (1) but I don't like it that much either.
Is there a better way of doing it using some .net functionality I don't master yet ?
Thanks for any advise.
Iterating through from controls can be tricky since controls are often nested. A more controlled approach would be to add another collection object to your form where you keep references to your added controls.....
e.g
Dim My_Widgets as New List(of Your-Control-Class-Name)
Then when you create the controls to the form also add them to that list.
My_Widgets.Add(Widget_Object)
After that it is a simple matter to iterate through that list.
For Each Widget as My_Widget_CLass in My_Widgets
' do what you need to do to Widget
Next
If you need to reference individual controls directly, use a dictionary object instead..
e.g.
Dim My_Named_Widgets as new Dictionary(of String, Your-Control-Class-Name)
Then add your control references to the dictionary by name
My_Named_WIdgets.add("<Whatever_You_USe_To_Identify_It>", Widget_Object)
You can then reference the specific control by the ID or name
My_Names_Widgets("ID").Property = Whatever '... etc
You seem to be indicating you have other controls for other purposes, as such it would be prudent to create similar collections for each type.

Passing Values from Multiple UserControls

Note: Similar Question can be found here.
How can I pass values from multiple User Controls? The question in the link above provides an answer however I find the answer very tedious in my situation and there is a delay in passing of the values. (I have to cycle to and from UserControl1 and UserControl2 multiple times while in UserControl1 committing a change of a textbox or label to see any passing of values in UserControl2.)
Either way, since I have multiple UserControls in which each has many textboxes, labels, and comboboxes, I would very much not like having to create separate Sub Routines and EventHandlers for each and every control with a value that I would like to pass.
Is there a better way? I was thinking something like...
'In UserControl10
Dim UserControl1 As New UserControl1
Dim UserControl2 As New UserControl2
Dim UserControl3 As New UserControl3
UC10Label1.Text = UserControl1.Label1.Text
UC10TextBox1.Value = UserControl2.TextBox1.Value
UC10ComboBox1.Text = UserControl3.ComboBox1.SelectedItem
The code above obviously does not work the way I would imagine, how can I achieve something similar with the least amount of code?
Edited: I have multiple custom UserControls in which I use as 'views'. In each UserControl there are labels, textboxes, & comboboxes. I have a Panel1 in which on a triggered event, will display a UserControl(1-9) in the panel; each UserControl is displayed one at a time and is contingent on an event. I want to be able to pass values from each UserControl(1-9) to UserControl10's labels, textboxs, or comboboxs etc..
I'm guessing that the last three lines aren't in a Sub. They're probably not working because they're executing before the form has been shown.
If you want to update them automatically in your program, you should put them in a sub, but suspend the form layout while they're updating and then resume layout when the code has finished. Like this
Private Sub UpdateUserControls()
Me.SuspendLayout()
UC10Label1.Text = UserControl1.Label1.Text
UC10TextBox1.Value = UserControl2.TextBox1.Value
UC10ComboBox1.Text = UserControl3.ComboBox1.SelectedItem
Me.ResumeLayout()
End Sub
Depending on when you want to update these controls, you can do it each time the form is shown by placing the above Sub in the Form's .Shown event.
Or you could do it automatically every so often by placing it in a Timer's .tick event.
Or you could choose to update them at certain points in your program by placing the sub somewhere in your code.

Duplicating control action

In Vb.net I have a button on one form (call it button_abc), changes color, the text changes, has a click event, etc. I want to have the SAME button duplicated on another form so that it can be used from 2 different places (one of the forms might not be visible). When the text or color gets changed on one, it need to appear in both. So if both forms are open, the buttons always appear to match exactly in appearance and action. is there a way to "link" them together automatically?
Copying & pasting a button, simply creates a new (separate) button---not what I want.
I would be inclined to go down the path of using an adapter to allow you to update the buttons.
Something like this:
Public Class ButtonList
Inherits List(Of Button)
Public Property Color() As Color
Get
Return Me.Select(Function (b) b.Color).FirstOrDefault()
End Get
Set(ByVal Value As Color)
For Each b In Me
b.Color = Value
Next
End Set
End Property
' + all other relevant properties.
End Class
Since it inherits from List(Of Button) you just add all the buttons you need to this class using .Add(button) and then put in all of the properties that you want to update. Now the code appears very much the same, but you now will update many buttons at once.
This is along the lines of what I'm describing, except I'm using VB
Duplicate WebControls at Runtime
Since you always want the buttons to match, it doesn't matter if one form is open/visible or not. You simply create 2 buttons, 1 on each form and make them do the same thing on both forms. So some pseudo-code may look like this:
Form 1:
Button.ClickEvent
Me.Color = UglyGreen
Form2.Button.Color = UglyGreen
End Button.ClickEvent
Form 2:
Button.ClickEvent
Me.Color = UglyGreen
Form1.Button.Color = UglyGreen
End Button.ClickEvent
It's not a very pretty solution, but it works for the specified task.