How to generate a three level dependable dropdown list - excel

I am a complete VBA beginner and this is the first time I have had to deal with VBA. My project is simple- a user form which heavily relies on dependent drop down lists. I watched a ton of videos and wrote (more like copy-pasted) code which actually works just fine. My issue is that I need to edit part of my code to add a feature which I have trouble finding a video on (trial and error editing only took me this far).
In it's current state, my form has two dropdown lists drawing information from a sheet where data is arranged in columns as follows:
ITEM ID | ITEM | CATEGORY
The user picks a category and then the item list if filtered based on the previous selection. I now need to rearrange those columns are add another one, making it the 1st tier selection as follows:
LOCATION | CATEGORY | ITEM ID | ITEM
Just rearranging the columns alone breaks my code. On top of that I need to add the Location combobox, which would filter the Categories, which in turn filter the Items.
This is the code which handles the CATEGORY and ITEM list:
Private Sub cmbEquipCategory_Change()
Dim sh As Worksheet
Dim lastBlankRow As Long
Me.cmbEquipment.Clear
Set sh = Sheets("Equipment_List")
lastBlankRow = sh.Cells(Rows.Count, 3).End(xlUp).Row
For i = 2 To lastBlankRow
If sh.Cells(i, 3) = Me.cmbEquipCategory.value Then
Me.cmbEquipment.AddItem sh.Cells(i, 2)
End If
Next i
End Sub
It is my impression that I need to alter this code to draw data from columns 2 and 4 (it currently does so from 3 and 2) and write another almost identical block of code which handles LOCATION and CATEGORY. Any advice, resources or help would be greatly appreciated. Thanks!

The way I do this is to used named ranges. So selecting your ITEM ID would lead to one of several ITEM ranges (I name them according to the ITEM ID options) which would lead to one of several CATEGORY ranges (I name these according to the ITEM options). The more options you have the more ranges you need. Named ranges aren't broken by adding in columns.

Related

VBA Range.End(xlDown) stops at last visible row

I am doing a simple VBA script in Microsoft Excel which iterates a list of cars and a list of information about when the cars were refueled to provide an overview of how many kilometers each car is driving each month.
I make use of the Range.End property to calculate the number of rows with data and then loop through the indicies.
Set Data = Worksheets("Tankninger") ' Danish for refuellings
NumRows = Data.Range("A1", Data.Range("A1").End(xlDown)).Rows.Count
For x = 1 To NumRows
' Process data
Next
Everything seemed to be working fine, however I found that if someone applied a filter to e.g. the sheet with refuelling data - e.g. only showing data related to car A, then NumRows would be assigned the index of the last visible row.
Example: if the refuling sheet contains 100 records and the records related car A are located on row 50-60, then NumRows would be assigned the value 60 - resulting in my script ignoring the last 40 records.
Is there a way to make the Range.End property ignore any filter applied to sheet, or will I have to change the implementation to use a while-loop instead?
I ended up replacing the for-loop with a while-loop. This allowed me to access every cell regardless of any filtering applied to the sheets.
Set Data = Worksheets("Tankninger") ' Danish for refuellings
r = 2
While Not IsEmpty(Cars.Cells(r, 1).value)
' Process data
Wend
What you can do is add the following in your code to remove filters before you find the last row with data.
'Remove all filters
Worksheets("Sheet1").Activate
On Error Resume Next
ActiveSheet.ShowAllData

Multiple Listboxes showing ranges from active sheets - Excel VBA

This is my First Ever Post:
I am creating a user form that will function similarly to a school management system.
One of the elements that are part of it is a Pupil Profile, which shows all information relating to the specific child.
Currently, each pupil has its own sheet. When searching for the child, the sheet matching the name of the child will be set as the active sheet.
This sheet contains sets of columns that store data about Parents meetings, rewards and sanctions, concerns and meetings with key staff members.
Example Pupil Sheet
I have a multipage setup with 4 tabs, each tab has a list box that I want to show the data from the set of columns on the active sheet.
I don't know a great deal about VBA, so I followed some tutorials and managed to make one of the sets of columns work perfectly. But to be honest, I don't understand how the code works, and I can't make it work for the other rows:
Private Sub cmdSearch_Click()
Dim X As Long
Dim Y As Long
Dim i As Long, G As Integer
Dim C As Integer, M As Integer
For G = 2 To Sheets.Count
If Sheets(G).Name = txtSearch Then
For i = 2 To Sheets(G).Range("A2000").End(xlUp).Row
Me.ListBox1.AddItem
For C = 0 To 6
Me.ListBox1.List(ListBox1.ListCount - 1, C) = Sheets(G).Cells(i, C + 1)
Next C
Next i
End If
Next G
Private Sub AddParentMeeting_Click()
TgtSheet = txtSearch.Value
If TgtSheet = "" Then
Exit Sub
End If
Worksheets(TgtSheet).Activate
LastRow = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
ActiveSheet.Cells(LastRow + 1, 1).Value = PM1.Value
ActiveSheet.Cells(LastRow + 1, 2).Value = PM2.Value
ActiveSheet.Cells(LastRow + 1, 3).Value = PM3.Value
ActiveSheet.Cells(LastRow + 1, 4).Value = PM4.Value
ActiveSheet.Cells(LastRow + 1, 5).Value = PM5.Value
ActiveSheet.Cells(LastRow + 1, 6).Value = PM6.Value
End Sub
Basically, my two key issues are:
How do I populate list boxes from a range of data on the active sheet so that it changes with each new profile search? E.g
ListBox1 shows A2:F2000 on active sheet
ListBox2 shows H2:K2000 on active sheet
ListBox3 shows M2:Q2000 on active sheet
ListBox4 shows S2:X2000 on active sheet
How do I add information to just those columns without it adding the information to the next fully clear row? e.g)
I don't want this to happen:
Picture
I want it to look like this:
Picture
Sorry for the lengthy post, and any questions or help in the right direction would be really appreciated
You may not like this reply and for that I apologize but your approach ignores all database basics. It will give you more work than need be and fail before the project is finished. The first mistake is to have a separate sheet for each pupil.
You need one sheet with a row for each pupil, columns like name, first name, middle name, birthday (not age because you can calculate the age from the birthday any time you need it), address, parent's phone etc. The first column is the most important. In db parlance it's call the "Key". It contains a unique identifier for everything in the row, a pupil number, if you like or an ID code. The point of the "key" is to be unique. By this ID you can retrieve the entire row and all information in it.
Then you need 4 more similar data bases: Parent Meetings, Staff Meetings, Concerns, Rewards/Consequences. Each of these has the columns you already designed plus one more which is called the "Foreign Key". In each row of these tables you enter the "Key" from the Pupils sheet, linking each entry - Meeting, Concern or Reward - to one particular pupil. Perhaps you have a validation drop-down with the pupils' names to avoid typos in the Keys. But that is already step 2.
You would be able to work with this setup immediately. Simply filter any of the lists on a pupil's ID to get a list of all meetings, concerns or rewards for any one pupil. With the help of the "Key" you would be able to easily and quickly create a sheet with the pupil's details at the top, meetings, concerns, rewards listed - much like your existing pupil sheets but with changing data as you select a pupil from a validation drop-down. All done with VLOOKUP and worksheet functions. No user form yet. That would be the de-luxe version but you would have 80% of the functionality while you work on it.

How to get cell value from another excel sheet based on multiple list conditions

I apologise if this comes across as a newbie query, but I have been stumped by this for some time.
I am building an excel spreadsheet for my staff to be able to compare stock listings easily and automatically based on reference data held in a different sheet within the same workbook.
The difficulty is that I want to pull this reference data based on the values chosen from a total of 6 conditional drop down lists I have created.
Based on what is selected, the values on say Sheet 1 will display the relevant values from Sheet 2 automatically (i.e. if one of the list options is changed, the "results" will update with the associated data automatically).
I have tried using lookup functions, arrays and nested if functions, but I have been unable to get it to work properly - probably due to my own lack of depth of knowledge for this type of coding. As such I am looking for advice about the best way to code this function requirement.
The format of what I am trying to do is this:
Sheet 1 - The 'GUI'
This sheet has the dropdown lists and the "displayed results" section
dd1 - State (6 options)
dd2 - Product Type (4 options)
dd3 - Product Brand (3 options)
dd4 - Product variation 1 (eg model) (say, 2 options)
dd5 - Product variation 2 (eg colour) (5 options)
dd6 - Product variation 3 (eg size) (3 options)
Sheet 2 - Reference data
Table 1 - ACT products and variations
Table 2 - VIC products and variations
Table 3 - NSW products and variations etc and so on...
Based on choices above in Sheet1, I want the product & price to be listed in cells below. Currently I have conditional functions listing the avail options based on the choice made for the prev list working fine.
I need it so that:
IF dd1 = "ACT" AND dd2 = "Mobile" AND dd3 = "Apple" AND dd4 = "iPhone" AND dd5 = "white" AND dd6 = "128Gb"
Then
The results cell, say Sheet1!C10, will reflect the price of the item based on a table of data lists in Sheet2 (say for eg the listed value is in Sheet 2, Cell A5)
But if say the list choices change, eg dd3 was changed to "Samsung", then the lists will auto update because of the conditional formatting already; but I then want the results to be updated accordingly (eg the price listed based on the new choices will then reflect Sheet2!B3).
Because of the many options and variations, there are many tables in Sheet 2 for reference, so the 'IF' statement to cover all these options would be massive and convoluted.
Is there a relatively concise way of writing vb code for this function to suit?
My current code under the worksheet_change sub
If Range("D10").Value = "ACT" And (Range("D11").Value = "Mobile" And (Range("D13").Value = "Samsung" And (Range("D14").Value = "S7") And (Range("D15").Value = "Black") And (Range("D16").Value = "128"))))) Then
Sheets("Sheet2").Range("F20").Value = Sheets("Sheet4").Range("C5").Value2
Sheets("Sheet2").Range("F29").Value = Sheets("Sheet4").Range("C6").Value2
ElseIf Range("D10").Value = "ACT" And (Range("D11").Value = "Mobile" And (Range("D13").Value = "Samsung" And (Range("D14").Value = "S7") And (Range("D15").Value = "Black") And (Range("D16").Value = "256"))))) Then
Sheets("Sheet2").Range("F21").Value = Sheets("Sheet4").Range("C8").Value2
Sheets("Sheet2").Range("F22").Value = Sheets("Sheet4").Range("C9").Value2
Sheets("Sheet2").Range("F29").Value = Sheets("Sheet4").Range("C10").Value2
'and so on. With all the variations, there's prob going to be approx 20+ elseif statements.
End If
Ive only pasted 2x sections for the purpose of this request. I can copy it down or adjust the code accordingly for each option.
Ive not used nor written arrays before (successfully i.e.) so I have no clue/understanding if that is a better way, but i have written vba for basic functions and macros before; but im open to learn.

How to merge two (or more) rows using VBA in Excel?

I am trying to merge two rows in Excel using VBA in order to create a new row with the combination values of selected rows using a factor x.
alpha 5 6 8 3 ...
beta 10 1 5 7 ...
With alpha and beta I want to create row ab70 (x=.7)
ab70 6.5 4.5 7.1 4.2 ...
(5*.7+10(1-.7)) ...
I would like to create this from a GUI in VBA selecting from a list the materials and chosing the factor x.
Thanks :D
L
The first version of this answer was more concerned with clarifying the requirement than answering the question. The second version is closer to an proper answer. Questions in the first version which were answered in comments have been deleted.
First version after removal of questions
This is not a site which can teach you to create a userform although you could get help with the code for a control. Try searching the web for “excel vba userform tutorial”. There are a number to choose from. I have not tried any so cannot make a recommendation.
A List box allows the program to provide a list from which the user can select one or more items. A Combo box allows the program to provide a list from which the user can select one item or enter a new value that is not within the list. You do not want the user to be able to specify their own material so you need a List Box. By default the user can only select one item which is what you want.
Second version
This will not be a complete answer. I will give you design ideas which you can then develop to meet your exact requirement or you can clarify your requirement and I will develop them a little more. I will give you some useful code but not all you will need for the complete solution.
You say that combining two materials would meet your immediate needs but in the longer term you wish to combine more. There are different approaches to addressing such a situation:
Design and implement a solution for the immediate need now. Redesign for the longer term later.
Design and implement a solution for the long term need now.
Design a solution for the long term then implement as much of the long-term design as seems appropriate.
None of these approaches will be correct in every case. If you are working to a deadline, approach 1 many be the only choice. Approach 1 may also be appropriate if you lack experience with the technology and wish for a simple implementation as a training exercise. When I was young, distributing a new version of an application to multiple users could be very expensive and approach 2 would often be the preferred approach. These days, approach 3 is normally my preference.
From your comments I deduce you are thinking of something like:
The two list boxes are filled with the names of the materials so the user can click one row in the first list box and one in the second to specify the two materials. Text boxes allow the user to enter the Proportion and the Name. I have used the blue “Rem” to represent the remainder (1 – x) which you may wish to display as a comment. You may not have thought of buttons. There should always be an Exit button in case the user has started the macro unintentionally. Clicking a button to save the mixture allows the user to check the four values first.
I think this could be an excellent design for the two material version. If we ignore the actual merging of the rows, there would be little code behind this form.
I do not know how long your material names are but I assume this design could be expanded for three or four materials by adding extra list boxes to the right with a Proportion text box under all but the last list. However, this arrangement would have a low maximum number of materials in a mixture. This will be acceptable if you do have a low maximum. You might also allow the user to mix mixtures thereby allowing an unlimited number of base materials in a mixture.
The code behind a form that allowed three or four materials in a mixture would be only a little more complicated than that behind the two material version.
I have two alternative designs that would perhaps be better with a higher maximum number of materials but it will not outline then unless you indicates that this first design is unacceptable.
I would expect any good tutorial to explain the various methods of loading a list box with values to I will not repeat them.
However you decide to handle the selection of materials and their proportions, you will need a routine to generate the new row.
I have created a worksheet “Material” and have set the first few rows and columns so:
I appreciate you have many more rows and columns but my data is adequate for a test and demonstration. Note in the heading line "Prop" is short for "Property".
You need to tell the routine which merges rows, which rows to mix. The user will select material B2 say. You could pass “B2” to the routine and let it discover the row from which it had come but this would make the routine more difficult to code than necessary. When loading the list boxes from this worksheet, values will be taken from column A of rows 2 to 12. I would expect your user form tutorial to explain that your code can identify the value selected by the user either by value (B2) or by index (4th row). You know the 1st row of the list box was loaded from row 2 of the worksheet so you can calculate that the 4th row of the list box was loaded from row 5 of the worksheet.
You need to tells the routine the proportions entered by the user and the name of the mixture.
Above I listed three possible approaches to deciding how much to implement. An addition to any of these approaches is the inclusion of flexibility that is not required but is as easy or is easier to include than exclude.
The declaration for my routine is:
Sub RecordNewMixture(ByVal WshtName, ByRef RowSrc() As Long, ByRef Prop() As Single, _
ByVal MaterialNameNew As String)
You will only have one worksheet holding materials and its name is unlikely to change so I could hardcode that worksheet’s name into the routine. However, it almost as easy to make the worksheet name a parameter and I think it makes the code tidier so I have make it a parameter.
The routine requires the array Prop() hold all the proportions including the last. So, for example, (0.7, 0.3) or (0.3, 0.3, 0.4). The user form will have to calculate the last proportion so it might as well pass the last proportion. I have made Prop() an array of Singles which I assume will give you adequate precision. If you do not understand the last sentence I can explain. Note that here "Prop" is short for proportion. Sorry for using "Prop" as an abbreviation for both "Property" and "Proportion". I did not notice until I the final checking of this text.
I needed a routine to test Sub RecordNewMixture so I have provided it as a demonstration. Note that I have coded and tested this routine without any involvement of the user form. It is always a good idea to develop and test your routines in isolation before combining them into the finished product.
After running the macro, worksheet “Material” has two new rows:
If you duplicate the new rows with formulae, you will find that the values are as you require.
Option Explicit
Sub Test()
Dim RowSrc() As Long
Dim Prop() As Single
ReDim RowSrc(0 To 1)
ReDim Prop(0 To 1)
RowSrc(0) = 2: Prop(0) = 0.7!
RowSrc(1) = 4: Prop(1) = 0.3!
Call RecordNewMixture("Material", RowSrc, Prop, "Join24")
ReDim RowSrc(1 To 3)
ReDim Prop(1 To 3)
RowSrc(1) = 3: Prop(1) = 0.3!
RowSrc(2) = 6: Prop(2) = 0.3!
RowSrc(3) = 9: Prop(3) = 0.4!
Call RecordNewMixture("Material", RowSrc, Prop, "Join369")
End Sub
Sub RecordNewMixture(ByVal WshtName, ByRef RowSrc() As Long, ByRef Prop() As Single, _
ByVal MaterialNameNew As String)
' * RowSrc is an array containing the numbers of the rows in worksheet WshtName
' that are to be mixed to create a new material.
' * Prop is an array containing the proportions of each source material in the new
' mixture.
' * Arrays RowSrc and Prop must have the same lower and upper bounds.
' * MaterialNameNew is the name of the mixture.
' * Each data row in Worksheet WshtName defines a material. Column A contains the
' name of the material. The remaining columns contain numeric properties of the
' material.
' Each data row in Worksheet WshtName must have the same maximum number of
' columns. Call this value ColLast.
' * This routine creates a new row below any existing rows within worksheet
' WshtName. Call this row RowNew. The values in this new row are:
' * Column A = MaterialNameNew
' * For ColCrnt = 2 to ColMax
' * Cell(RowNew, ColCrnt) = Sum of Cell(RowSrc(N), ColCrnt) * Prop(N)
' for N = LBound(RowSrc) to UBound(RowSrc)
Dim ColCrnt As Long
Dim ColLast As Long
Dim InxRowSrc As Long
Dim RowNew As Long
Dim ValueNewCrnt As Single
Application.ScreenUpdating = False
With Worksheets(WshtName)
' Locate the row before the last row with a value in column A
RowNew = .Cells(Rows.Count, "A").End(xlUp).Row + 1
' Store name of new material
.Cells(RowNew, "A") = MaterialNameNew
' Locate the last column in the first source row. Assume same
' last column for all other source rows
ColLast = .Cells(RowSrc(LBound(RowSrc)), Columns.Count).End(xlToLeft).Column
For ColCrnt = 2 To ColLast
' If Single does not give adequate precision, change the declaration of
' Prop() and ValueNewCrnt to Double. If you do this, replace "0!" by "0#"
ValueNewCrnt = 0!
For InxRowSrc = LBound(RowSrc) To UBound(RowSrc)
ValueNewCrnt = ValueNewCrnt + .Cells(RowSrc(InxRowSrc), ColCrnt).Value * Prop(InxRowSrc)
Next
.Cells(RowNew, ColCrnt) = ValueNewCrnt
Next
End With
Application.ScreenUpdating = True
End Sub

Adding several elements to a listbox menu in vba

I am trying to create a menu with list boxes in order to be able to select a number of customers from a list in an excel sheet. There are two list boxes, one with all the (default) data and one with the selected customers.
There is no problem adding one customer but when I add a second customer the graphic interface shows nothing, but after some debugging, the SelectedCustomers.RowSource still have the two rows in its data:
?SelectedCustomers.RowSource
$8:$8,$11:$11
This would have me believe there is some error with how I save the data or some limitations to Excel that I am not aware of. This is the code I use to save the data:
Private Sub CommandButton5_Click()
Dim temp As Range
For i = 0 To DefCustomers.ListCount - 1
If DefCustomers.Selected(i) = True Then
If temp Is Nothing Then
Set temp = Range(Rows(i + 4).Address)
Else
Set temp = Application.Union(temp, Range(Rows(i + 4).Address))
End If
End If
Next i
SelectedCustomers.RowSource = temp.Address
End Sub
Has someone experienced this before or know what the problem might be?
Instead of RowSource use AddItem method:
For i = 0 To DefCustomers.ListCount - 1
If DefCustomers.Selected(i) Then
SelectedCustomers.AddItem DefCustomers.Selected(i)
End If
Next i
There are known issues with ListBox.RowSource property in Excel VBA.
[EDIT]
After the discussion...
No matter of number of columns, you can copy rows from source sheet into another sheet, then bind SelectedCustomers listbox to that data

Resources