Parts of an unsorted list into a drop down - validation

I am trying to create a data validation drop down cell that displays a list of values pulled from a much larger list, but only the ones where the lookup value meet certain requirements. This would be like the SUMIF function that only adds the values where the lookup value meet certain requirements. Here is an example of my list:
V F
Apples x
Bananas x
Tangerines x
Tomatoes x x
Broccoli x
Pears x
Kiwis x
Plums x
Water melon x
Squash x x
I want only the ones with an "x" in the first column to display in the drop down.
Tomatoes
Broccoli
Squash
Also the original list can't be sorted. I am fine with using macros if that would work. I am using Excel 2010.

If you want a range of valid entries without blanks to use as a list for data validation, I suggest something like:
=INDEX($A$2:$A$11,SMALL(IF($B$2:$B$11<>"",ROW($A$2:$A$11)-ROW($A$2)+1),ROWS(C$2:C2)))
entered with Ctrl+Shift+Enter
There is about 20 minutes of explanation at https://www.youtube.com/watch?v=6PcF04bTSOM.

Without using VBA, you could create a copy of the list that is filtered. You can then reference the cells in that copy when you use data validation.
For example, you could do the following steps for your example above:
Apply a filter to the list where only those showing an x in the first column are showing. Copy the filtered list, then paste to another spot on the worksheet. Turn off the filter on the list, so it returns to normal. Go to the cell that you want to add a validation drop down to, and select data validation. Select list, then reference the copied list.

Using VBA, you could use this as a starter. The key is the Range.Validation method, which is explained in detail here. This reads your list in column A, finding those with an "x" in column B, and puts that in a validation list in cell E1.
Dim myvalidation_list As String
Dim last_row As Long, current_row As Long
last_row = Cells(Rows.Count, "A").End(xlUp).Row
For current_row = 1 To last_row
If LCase(ActiveSheet.Cells(current_row, 2).Value) = "x" Then
'put in the delimiting "," if the list already has an entry
If myvalidation_list <> "" Then
myvalidation_list = myvalidation_list & ","
End If
'add to the validation list
myvalidation_list = myvalidation_list _
& ActiveSheet.Cells(current_row, 1).Value
End If
Next
With ActiveSheet.Range("E1").Validation
.Delete
.Add Type:=xlValidateList, Formula1:=myvalidation_list
End With

Related

Filtering in VBA

I am stuck on something and can't see where I have gone wrong.
I have a spreadsheet and will use a command button to filter the spreadsheet based on selected criteria. I have added data validation dropdowns in cells D2 and E2 for the end user to select the options to filter. When selected, the code should hide all rows that do not meet both criteria. Below is the beginning of my code (there will be more steps to follow with summing and format, but this is the first and critical step). When I have tested this so far, I have gotten an "Argument Not Optional" error. I have gone down a rabbit hole trying to figure out where I have gone wrong, but no luck. Any ideas?
Sub Button14_Click(ByVal Target As Range)
ActiveSheet.Unprotect Password:="1234"
If Target.Row = 2 Then
Dim list1 As String, list2 As String
list1 = Range("D2")
list2 = Range("E2")
If list1 = "Construction Contingency" And list2 = "Open" Then
Sheets("UseLog").Range ("A12:J1010")
.AutoFilter Field:=5, Criteria1:=list1
.AutoFilter Field:=6, Criteria2:=list2
End With

Modifying an Excel VBA Script to Work in Word

I have the following VBA code:
Sub test2()
Dim w1 As Worksheet
Dim w2 As Worksheet
Dim k As Long
Dim c As Range
Dim d As Range
Dim strFA As String
Set w1 = Sheets("Sheet1")
Set w2 = Sheets("Sheet2")
w2.Cells.Clear
k = 1
With w1.Range("A:A")
Set c = .Cells.Find("FirstThing", After:=.Cells(.Cells.Count), lookat:=xlWhole)
strFA = ""
While Not c Is Nothing And strFA <> c.Address
If strFA = "" Then strFA = c.Address
If IsError(Application.Match(c.Offset(0, 1).value, w2.Range("A:A"), False)) Then
Set d = .Cells.Find("SecondThing", c, , xlWhole)
w2.Range("A" & k).value = c.Offset(1, 0).value
w2.Range("B" & k).value = d.Offset(0, 1).value
k = k + 1
End If
Set c = .Cells.Find("FirstThing", After:=c, lookat:=xlWhole)
Wend
End With
End Sub
The code works essentially like this:
Look through Sheet1 for a certain phrase.
Once the phrase is found, place the value from the cell one row over in Sheet2
Search for a second phrase.
Place the value from the cell one row over in the cell beside the other value in Sheet2
Repeat
Now. I have the same data that, don't ask me why, is in .doc files. I'd like to create something similar to this code that will go through and look for the first phrase, and place the next n characters in an Excel sheet, and then look for the second phrase and place the next m characters in the row beside the cell housing the previous n characters.
I'm not sure whether it's better to do this with a bash script or whether it's possible to do this with VBA, so I've attached both as tags.
Your question seems to be: "I'm not sure whether it's better to do this with a bash script or whether it's possible to do this with VBA"
The answer to that is: You'd need VBA, especially since this is a *.doc file - docx would be a different matter.
In order to figure out what that is, start by trying to do the task manually in Word. More specifically, how to use Word's "Find" functionality. When you get that figured out, record those actions in a macro to get the starting point for your syntax. The code on the Excel side for writing the data across will essentially stay the same.
You'll also need to decide where the code should reside: in Word or in Excel. That will mean researching how to run the other application from within the one you choose - lots of examples here on SO and on the Internet...

Excel VBA, multi parameter filter to return row ID

I currently have an algorithm that manipulates data on sheet 24 in my excel workbook. I have 3 integers, a, b, and c, that come from data on sheet 24, and I need to use them to return a unique row ID on sheet 14. A, B, and C, each represent their corresponding Column (range) on sheet 14 IE: Int a from sheet 24 is in column A on sheet 14. Int b in column b, and c in c. The numbers combined will always return a singular row ID from 14, in the case, integer row. I'm having a hard time writing a statement with Filter, evaluate, and a bunch of other Excel functions. Does anyone know how to launch a script from an algorithm executing on one sheet and just pull a row ID in sheet 14 using its' resulting three search numbers for a,b,c?
row = Sheets("Physical Disk Details").Columns("A").Find(what:=a, LookIn:=xlValues, lookat:=xlWhole) * Sheets("Physical Disk Details").Columns("B").Find(what:=b, LookIn:=xlValues, lookat:=xlWhole) * Sheets("Physical Disk Details").Columns("C").Find(what:=c, LookIn:=xlValues, lookat:=xlWhole)
This Function will return the first row number that is found to exactly match what is provided for a,b, and c. It demonstrates one of the simplest Range iteration and If/Then tests that forms the foundation of most data pattern searching subroutines you will find for Excel VBA. There are ways to streamline this code but it demonstrates the basic principle.
If you read every line of this code and understand what all of it does you can use the principles to accomplish most tasks even vaguely similar to it.
Function GetRowNumberByABCMatch(a As Integer, b As Integer, c As Integer) As Integer
Dim lastRowInDataSet As Integer
Dim i As Integer
'get last row containing data in column 1
lastRowInDataSet = Sheets("Physical Disk Details").Cells(Rows.count, 1).End(xlUp).Row
'i = current row number, start at row 1
For i = 1 To lastRowInDataSet
If Sheets("Physical Disk Details").Range("A" & i).Value = a _
And Sheets("Physical Disk Details").Range("B" & i).Value = b _
And Sheets("Physical Disk Details").Range("C" & i).Value = c Then
'all fields match, return the current row number
GetRowNumberByABCMatch = i
'stop processing and return because we found a single match
Exit Function
End If
Next i
'didn't find a matching row, return -1
GetRowNumberByABCMatch = -1
End Function

Speeding up adding validation lists to a spread range of cells using vba

I am working with a fairly small worksheet that has been developed by someone else. In this worksheet I have approx. 500 rows and some 100 columns (these values change dynamically).
The document adds validation lists to some cells based on a named range in another worksheet in the same workbook. This currently works, but very slowly.
The cells I would like to target are cells that on the same row, in column A, have a certain value. The cells should also have a specific name in its "header".
Currently, I am using a find statement to find all correct columns, and then for each of those columns I check the value in column A for the correct one, and if it is, I add the range.
Now to the question; How can I speed this up? When the sheet is at its largest it takes over a minute to complete the code, and since that happens when you open the sheet, people using the sheet are complaining. :)
Application.ScreenUpdating = False
Application.EnableEvents = False
Sheets(A).Activate
Sheets(A).Unprotect Password:=Str_SheetPassword
'Get each data ranges
Set Rg_TitleRange = ...
Set Rg_dataRange = ...
'Loop on each column that contains the keyword name
Set Rg_ActionFound = Rg_TitleRange.Find(Str_ColName, LookIn:=xlFormulas, _
lookAt:=xlWhole, SearchOrder:=xlByRows, MatchCase:=True)
If Not Rg_ActionFound Is Nothing Then
'Loop on each action column
Do
'For each data row, update the cells with validation list
For Int_RowIndex = 0 To Rg_dataRange.Rows.Count - 1
'Change cells wich are at the intersection of test definition row and action name column.
If Rg_dataRange(Int_RowIndex, 1) = Str_RowName Then
Set Val_ActionValidationList = Rg_dataRange(Int_RowIndex, Rg_ActionFound.Column).Validation
Val_ActionValidationList.Delete
Rg_dataRange(Int_RowIndex, Rg_ActionFound.Column).Validation.Add _
Type:=xlValidateList, Formula1:=("=" + Str_ValidationList)
End If
Next
'Loop end actions
Int_PreviousActionFoundColumn = Rg_ActionFound.Column
Set Rg_ActionFound = Rg_TitleRange.Find(CommonDataAndFunctionMod.Str_ActionNameRowLabel, Rg_ActionFound, LookIn:=xlValues, lookAt:=xlWhole, SearchOrder:=xlByRows, MatchCase:=True)
Loop While Rg_ActionFound.Column > Int_PreviousActionFoundColumn
End If
Application.ScreenUpdating = True
Application.EnableEvents = True
I have tested to just comment out the row where the validation is added, so I'm fairly sure that row is the time consumer (mostly). I would take any suggestions.
Thank you in advance!
After some tries I ended up redoing the code so that this routine is run on certain other events instead, hence removing the loading time on start up. The validations are updated only when needed now.
Thank you all for your suggestions!
As you used Loop inside a loop will always slows down the code. think of different algorithm try to use Exit Loop and Exit Do When to cut down the looping time.

QTP narrow a list of ChildObjects

[The description is a bit fudged to obfuscate my real work for confidentiality reasons]
I'm working on a QTP test for a web page where there are multiple HTML tables of items. Items that are available have a clickable item#, while those that aren't active have an item# as plain text.
So if I have a set of ChildObjects like this:
//This is the set of table rows that contain item numbers, active or not.
objItemRows = Browser("browserX").Page("pageY").ChildObjects("class:=ItemRow")
What is the simplest way in QTP land to select only the clickable link-ized item #s?
UPDATE: The point here isn't to select the rows themselves, it's to select only the rows that have items in them (as opposed to header/footer rows in each table). If I understand this correctly, I could then use objItemRows.Count to count how many items (available and unavailable) there are. Could I then use something like
desItemLink = Description.Create
desItemLink("micclass").value = "Link"
objItemLinks = objItemRows.ChildObjects(desItemLink)
To get the links within only the item rows?
Hope that clarifies things, and thanks for the help.
I think I have this figured out.
Set desItemLink = description.create
desItemLink("micclass").value = "Link"
desItemLink("text").RegularExpression = True
//True, Regex isn't really required in this example, but I just wanted to show it could be used this way
//This next part depends on the format of the item numbers, in my case, it's [0-9]0000[0-9]00[0-9]
For x = 0 to 9
For y = 0 to 9
For z = 0 to 9
strItemLink = x & "0000" & y & "00" & z
desItemLink("text").value = strItemLink
Set objItemLink = Browser("browser").Page("page").Link(desItemLink)
If objItemLink.Exist(0) Then
//Do stuff
End If
Next
Next
Next
Thanks for your help anyways, but the code above will iterate through links with names in a given incrementing format.

Resources