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.
Related
Good Wednesday All.
I am running into a brick wall (easy for a shade tree coder to do) I have a Listbox that i populated with a datatable. I want to get the all LicenseID's from the selected items. In other words, if the user selects 3 out of 8 of the list box, I need to get the LicenseID for each of those 3.
Below is how I populated the listbox
Using cmd As New OleDbCommand(cmdText, conn)
conn.Open()
Dim reader As OleDbDataReader = cmd.ExecuteReader()
dt.Load(reader)
ListBox1License.DataSource = dt
ListBox1License.DisplayMember = "InstitutionTypeAbrev"
ListBox1License.ValueMember = "LicenseID"
End Using
I need to get the selected items from the listbox to use later.
I am thinking of adding the selected Items to an array.
I have searched around STackOverflow for some examples but none seem to work for me.
Any Help Appreciated
I'll show you how to derive the answer for this yourself:
I've set up a form:
Really simple; the listbox is like your listbox. The button is just there to give me an easy way to stop the code and examine what is going on.
I wrote some code to populate some things into my listbox. It's a screenshot because it doesn't matter that you have exactly this code, so you don't need to write this code (hence why I'm making it hard to copy paste):
I've double clicked my button to make a click handler. I haven't written any code, but I have put a breakpoint on the method declaration - see it's red? Click the margin where the dot is, to put breakpoints in your code. When you hit them, the code stops and waits for you to inspect:
I've run my app and clicked my button. The code has stopped and VS has switched to showing me the code, not the app:
I can now point to some variable that is in scope (like ListBox1) and see a tooltip, or I can open the Locals/Autos windows and see variables that are in scope and drill into them:
Expand you ListBox in the Autos/Locals window. It has a lot of properties. Scroll to SelectedItems:
SelectedItems is a collection of things.. We can tell partly because Microsoft is good at naming collections of things with a plural name, and because the inspector says "enumerate the enumerable" .. it means that it is a bunch of things that we can ForEach to look through
Expanding it we see that my selecteditems has only one thing selected (i truly did only have one selected item in my list when I clicked the button)
We can see that an entry in the SelectedItems collection is a DataRowView type of object. We can see that a DataRowView has a Row property that is a DataRow.. This Row is the DataRow in the DataTable to which the list is bound (you set the DataSource to a DataTable; this is a row from that table).
Every time you dig into the tree another level, that's like using either a dot or an indexer in your code. At this level we've gone listbox1.SelectedItems(0).Row..
So from this we can see that we need a code like:
' we will "enumerate the enumerable"
For Each drv as DataRowView in listbox1.SelectedItems
Dim originalRow = drv.Row 'we could do this to get the row...
Dim selectedAnimaId = row("AnimalID") ' ..and then index the row to get the animal ID ..
Dim selectedAnimalId = drv("AnimalID") ' ... or it's actually possible to index a DataRowView directly, so you can skip the row part
Next drv
It can be handy to write code while you're stopped on a breakpoint so you can look at the values of things as you're writing, and check you're going in the right direction. You might need to use F10 (or whatever key is associated with "step over"/"step into") to move the yellow bar along and execute code lines one by one:
You can only move the code execution along if you've written complete, legal code, but it doesn't have to be logically correct. You can back up and execute again by dragging the yellow arrow in the margin (or right clicking and choosing Set Next Statement). Here I've put some dummy statement in to move along to, so i can check that my animalID is correctly set in X like I expect . I point to X to see the value:
The standard ListBox won't help you with that, past getting the DataRowView objects from the SelectedItems collection. As an alternative, here's a custom control that you can use in place of a standard ListBox that will help you:
Public Class ListBoxEx
Inherits ListBox
Public Function GetItemValue(item As Object) As Object
Dim index = Me.Items.IndexOf(item)
If (index <> -1 AndAlso Me.DataManager IsNot Nothing) Then
Return Me.FilterItemOnProperty(Me.DataManager.List(index), Me.ValueMember)
End If
Return Nothing
End Function
End Class
You can then call GetItemValue and pass any item and get the same value back as you would if that was the SelectedItem and you got the SelectedValue. To get all the values in an array:
Dim licenseIDs = myListBoxEx.SelectedItems.
Cast(Of Object)().
Select(Function(o) CInt(myListBoxEx.GetItemValue(o)).
ToArray()
For more information, see here.
In case you're unaware, if you add a class to your project and it is a control or component, once you build, it will appear automatically at the top of the Toolbox window.
If you already have a standard ListBox in place and you don't want to have to delete it and add a new control, you can edit the designer code file by hand to change the existing control. To do that, open the Solution Explorer and select a node within your project, click the Show All Files button, expand the node for your form, double-click the designer code file and then replace ListBox with ListBoxEx (or whatever you call it) in the relevant places. I'd advise creating a backup copy or syncing with source control first, in case you mess it up.
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
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.
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.
okay... How do I explain this without being totally confusing?... Alright, I have this form that has MenuScripts (top-levels and second-levels). The problem that I am having is one of the second-levels is "Add" which brings you to another form when clicked. This other form has a button ("Record") and text boxes. This other form allows the user to input data and when the record button is clicked, the inputted data is written into a text file. Ok, so back to the first form. Another second-level MenuScript is "Update" which also brings the user to the other form; but first, the user has to click an item within a listbox to proceed. How do I get the data from the selected item to appear in the appropriate textboxes and how do I get the record button to update data instead of being confused and thinking it is only a add-data button?
Is there a way to use an "if" statement to say something like "if mnuAdd is clicked then" "elseif mnuUpdate is clicked then". Would something like that work for giving the record button multiple uses?
Also, if someone can give me some pointers on making sure the user selects an item within the listbox would definitely be a plus! Thanks, guys!
Unfortunately, I cannot add images since my reputation is too low.
Here is a visual representation of my ultimate goal
Easiest way: before displaying the second form set it's Tag property to something distinct – say "Add" or "Update" – depending on which menu item is selected. Then you just test the Tag value in the button's Click event and proceed accordingly.
As for determining whether a list item is selected: well if there isn't the ListBox's SelectedIndex property will be set to -1.
You need to put a public property on the second form (Details) which specifies which mode it is in. For instance, you could create a mode enumeration like this:
Public Enum EntryModes
AddBook
UpdateBook
End Enum
Then, define a public mode property on the second form, like this:
Public Property EntryMode As EntryModes
Get
Return _entryMode
End Get
Set(ByVal value As EntryMode)
_entryMode = value
End Set
End Property
Private _entryMode As EntryMode
Then, when you show the second form from the menu, just set the property first, before showing it:
Private Sub mnuAdd_Click(sender As Object, e As EventArgs)
Dim dialog As New DetailsDialog()
dialog.EntryMode = EntryModes.AddBook
dialog.ShowDialog()
End Sub
Private Sub mnuUpdate_Click(sender As Object, e As EventArgs)
Dim dialog As New DetailsDialog()
dialog.EntryMode = EntryModes.UpdateBook
dialog.BookToUpdate = ListBox1.SelectedItem
dialog.ShowDialog()
End Sub
As you can see, in the Upate menu click, I also added a line that passes the information for which book should be updated.