Yeow... That was a mouthful.
I'm setting up a log to track inventory in the office. For some items we have multiple copies of the same tool. I've gotten the VBA to track the owners with userforms and vba, but the multiple-identical-names, first-occurance, blank cell trick is proving to be too much for me.
So what you have is this:
Item Serial Number Owner
Item A 999999999
Item A 999999991
Item A 999999992
Item B 22221
Item B 22222
Item B 22223
Item C hhhg77
Item C hhhg78
Item C hhhg79
I need the code to search for the ITEM name, gathered from ComboBox1 on the Userform, and find the first occurance of the ITEM WITHOUT an owner, (so the corresponding "OWNER" cell should be blank, and put the OWNER, gathered from ComboBox2 on the userform, in that spot.
I've been fooling around with Index & Match and VLookup and countless searches for "Finding First Row" but I've come up empty.
The ranges might be changed, so I'm hesitant to be so specific as to say search between A2:A4, so a search would be best, I think.
What I have so far is this....and it's weak, I apologize.
Public Sub FindBlankOwner()
Dim MultiItem As Range
Dim MultiOwner As Range
Dim ITEM As String
Dim OWNER As String
Dim MultiSerial As Range
Dim NO As Range
ITEM = ComboBox1.Value
STATUS = Application.WorksheetFunction.VLookup(ITEM, LUP, 6, False)
OWNER = ComboBox2.Value
Set ws = Worksheets("Owners")
Set MultiItem = Worksheets("Owners").Range("A1:A28")
Set MultiOwner = Worksheets("Owners").Range("C1:C28")
Set MultiSerial = Worksheets("Owners").Range("B1:B28")
Fillin = Evaluate("INDEX(MultiOwner, MATCH(ITEM, &
' Set FILLIN = Application.WorksheetFunction.Match(ITEM, (MultiItem), 0) And (Application.WorksheetFunction.Match(" ", (MultiOwner), 0))
' NO.Value = OWNER
'Set FILLIN = MultiItem.Find(What:=ITEM) And MultiOwner.Find(What:="")
End Sub
Search for Available Items Based on Empty Owner
I've written some raw code for your review. You were on the right track, but with the methods below we avoid using worksheet functions and instead use VBA methods.
First thing we want to do is find the first occurrence of the user-defined ITEM
After that we check if it has an OWNER or not. If it does, find the next ITEM. If it doesn't, assign the user-defined OWNER to the user-defined ITEM.
I've included some message boxes for clarity and convenience of the end-user.
The Code
Sub FindBlankOwner()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Owners")
Dim ITEM As String: ITEM = ComboBox1.Value
Dim OWNER As String: OWNER = ComboBox2.Value
Dim BlankOwnerFound As Boolean: BlankOwnerFound = False
Dim firstResult As String
Dim ItemRange As Range: Set ItemRange = ws.Range("A:A").Find(What:=ITEM, LookIn:=xlValues, LookAt:=xlWhole)
If Not ItemRange Is Nothing Then
firstResult = ItemRange.Address
Do
If Trim(ItemRange.Offset(0, 2).Value) = "" Then
ItemRange.Offset(0, 2).Value = OWNER
BlankOwnerFound = True
Else
Set ItemRange = ws.Range("A:A").FindNext(ItemRange)
End If
Loop While Not ItemRange Is Nothing And ItemRange.Address <> firstResult And BlankOwnerFound = False
Else: MsgBox "No results for " & ITEM & " found.", vbCritical, "Cannot Find " & ITEM
End If
If BlankOwnerFound = True Then
MsgBox OWNER & " has checked out " & ITEM & " with Serial Number " & ItemRange.Offset(0, 1).Value & ".", _
vbOKOnly, ITEM & " Check-Out Successful"
Else: MsgBox "No available " & ITEM & " in inventory.", vbCritical, "All " & ITEM & " are checked out."
End If
End Sub
NOTE: I haven't tested this code aside from reading it to myself to ensure it makes sense. There may be a couple errors (hopefully not) and if you can't solve them, please don't hesitate to let me know so we can work on it together. Let me know if this works for you :)
Related
I'm trying to link shape data field from external data like excel.
As #JohnGoldsmith suggested I used DropLinked but "I'm getting object name not found" error.
My main agenda is drop multiple shapes on drawing with shape data field "Name", then fill all the shape data field using external data in order. I also used spatial search for dropping shapes on drawing(Thanks to #Surrogate). By the way I'm using Visio Professional 2019.
It's often a good plan to separate chained members so you can identify whether (as #Paul points out) you're having a problem getting to the stencil or the master.
Following is a modified example of link shapes to data. I've ditched all of the spatial search stuff as I think that's a separate issue. If you still have trouble with that I would ask another question and narrow your sample code to not include the data linking part - ie just drop shapes and try and change their position. Bear in mind there's also Page.Layout and Selection.Layout
I think you've got the adding the DataRecordsets in the other linked question, so this example makes the following assumptions:
You have a drawing document open
You have the "Basic Shapes" stencil open (note my version is metric "_M")
You have a DataRecordset applied to the document named "AllNames"
The above record set has a column named "Name" that contains the data you want to link
Public Sub ModifiedDropLinked_Example()
Const RECORDSET_NAME = "AllNames"
Const COL_NAME = "Name"
Const STENCIL_NAME = "BASIC_M.vssx"
Const MASTER_NAME = "Rectangle"
Dim vDoc As Visio.Document
Set vDoc = Application.ActiveDocument
Dim vPag As Visio.Page
Set vPag = Application.ActivePage
Dim vShp As Visio.Shape
Dim vMst As Visio.Master
Dim x As Double
Dim y As Double
Dim xOffset As Double
Dim dataRowIDs() As Long
Dim row As Long
Dim col As Long
Dim rowData As Variant
Dim recordset As Visio.DataRecordset
Dim recordsetCount As Integer
For Each recordset In vDoc.DataRecordsets
If recordset.Name = RECORDSET_NAME Then
dataRowIDs = recordset.GetDataRowIDs("")
xOffset = 2
x = 0
y = 2
Dim vStencil As Visio.Document
Set vStencil = TryFindDocument(STENCIL_NAME)
If Not vStencil Is Nothing Then
Set vMst = TryFindMaster(vStencil, MASTER_NAME)
If Not vMst Is Nothing Then
For row = LBound(dataRowIDs) + 1 To UBound(dataRowIDs) + 1
rowData = recordset.GetRowData(row)
For col = LBound(rowData) To UBound(rowData)
Set vShp = vPag.DropLinked(vMst, x + (xOffset * row), y, recordset.ID, row, False)
Debug.Print "Linked shape ID " & vShp.ID & " to row " & row & " (" & rowData(col) & ")"
Next col
Next row
Else
Debug.Print "Unable to find master '" & MASTER_NAME & "'"
End If
Else
Debug.Print "Unable to find stencil '" & STENCIL_NAME & "'"
End If
Else
Debug.Print "Unable to find DataRecordset '" & RECORDSET_NAME & "'"
End If
Next
End Sub
Private Function TryFindDocument(docName As String) As Visio.Document
Dim vDoc As Visio.Document
For Each vDoc In Application.Documents
If StrComp(vDoc.Name, docName, vbTextCompare) = 0 Then
Set TryFindDocument = vDoc
Exit Function
End If
Next
Set TryFindDocument = Nothing
End Function
Private Function TryFindMaster(ByRef vDoc As Visio.Document, mstNameU As String) As Visio.Master
Dim vMst As Visio.Master
For Each vMst In vDoc.Masters
If StrComp(vMst.NameU, mstNameU, vbTextCompare) = 0 Then
Set TryFindMaster = vMst
Exit Function
End If
Next
Set TryFindMaster = Nothing
End Function
The above code drops six shapes onto the page and adds a Shape Data row (Prop._VisDM_Name) with the corresponding data value. If you want the name text to appear in the shape then you would normally modify the master with an inserted field in the shape's text. (If you get stuck with this part then ask another question.)
One last point is that this example loops through the DataRecordset rows dropping a shape for each one, but there is also a Page.DropManyLinkedU method that allows you to this en masse.
i have below VBA code to update long list(1000) of part userform listbox with constant changes to design.
i need help with below 2 issues i am facing with code,
1)somehow, it is only updating only 1st selected item under multiselect listbox. can you pl help to check what is the issue with it to get all selected items updated by command button?
also, there are number of duplicates that i want to updates as well. however, below code updates only one and not other duplicate. can you pl help to correct code so it can update duplicates as well?
Private Sub cmdaction_Click()
Dim t, t1 As String
Dim vrech As Range, lColumn As Range
Dim sh As Worksheet
Dim i As Long
Dim selItem As String
Set sh = ThisWorkbook.Sheets("part bump")
Set lColumn = sh.Range("P1:AZA1").Find(Val(txtchangenumber.Value), , xlValues, xlWhole)
'Set lcolumn1 = sh.Range("F4:F1000")
If UserForm3.txtchangedescrption.Value = "" Then
MsgBox "Please enter Change Description"
Exit Sub
End If
If UserForm3.txtchangenumber.Value = "" Then
MsgBox "Please enter Change Number"
Exit Sub
End If
If UserForm3.cmbaction.Value = "" Then
MsgBox "Please Select part Action"
Exit Sub
End If
If lColumn Is Nothing Then
MsgBox "Change number not found"
Exit Sub
End If
With UserForm3.lstDatabase
For i = 0 To UserForm3.lstDatabase.ListCount - 1
If UserForm3.lstDatabase.Selected(i) = True Then
Set vrech = sh.Range("H4:H250").Find(.Column(7, i), , xlValues, xlWhole)
If Not vrech Is Nothing Then
Select Case cmbaction.Value
Case "RP"
t = Chr(Asc(Mid(.List(i, 7), 2, 1)) + 1)
t1 = Mid(.List(i, 7), 1, 2) & t & Mid(.List(i, 7), 4, 1)
Intersect(vrech.EntireRow, lColumn.EntireColumn) = t1
MsgBox "Selected parts 'RP' Action completed"
Case "RV"
Intersect(vrech.EntireRow, lColumn.EntireColumn) = .List(i, 7)
MsgBox "Selected parts 'RV' Action completed"
Case "DP"
Intersect(vrech.EntireRow, lColumn.EntireColumn) = "Deleted"
vrech.EntireRow.Font.Strikethrough = True
MsgBox "Selected parts 'DP' Action completed"
End Select
End If
End If
Next i
End With
End Sub
Upon further investigation I found that your handling of the Selected property is correct. I have deleted my advice in this regard and apologize for my hasty comment.
I have also re-examined your code and regret, I can't find a reason why it shouldn't deal with all selected items. without access to your workbook i don't have the ability to test and can't help you further.
Your second complaint is caused by this line of code.
Set vrech = sh.Range("H4:H250").Find(.Column(7, i), , xlValues, xlWhole)
It will find the first instance and no others. If you want the search to be repeated a loop will be required that repeats the search. Look up "VBA Find & FindNext MSDN" and you will find code samples how to construct the loop.
Note that in Dim t, t1 As String only t1 is a string. t is defined as a variant by virtue of not having a specified data type. This doesn't appear to be your intention.
I also noted your unusual use of Application.Intersect. Intersect(vrech.EntireRow, lColumn.EntireColumn) should be the equivalent of the simpler Sh.Cells(vrech.Row, lColumn), and it's recommended to specify the Value property when assigning a value to it.
I am running a search from Excel to get objects Internal ID's by External ID. In ThisWorkbook.Sheets("Other Data").Range("J30").Value I have External ID f5f9a21b-9208-de11-995f-005056bb3dfa. After search code should display Internal ID.
This one works and I am getting a message:
There were 3 objects with the display Id of f5f9a21b-9208-de11-995f-005056bb3dfa
How I can make this code to actually display these ID's for example starting from cell A1?
So instead of just a message:
MsgBox ("There were " & results.Count & " objects with the display Id of" & ThisWorkbook.Sheets("Other Data").Range("J30").Value)
I would get the ID's in Excel? Basically I need results.Count not to count items but input them to Excel.
Code edited according to suggestion, but debugger is pointing to ThisWorkbook.Sheets("Start").Cells(i, 1).Value = results(i)
Private Sub SurroundingSub()
Set oVault = oMFClientApp.BindToVault(szVaultName, 0, True, True)
' Create the condition.
Dim condition As New SearchCondition
Dim oScs: Set oScs = CreateObject("MFilesAPI.SearchConditions")
Dim oVaultConnections As MFilesAPI.VaultConnections
Dim i As Integer
' Set the expression.
condition.Expression.DataStatusValueType = MFStatusType.MFStatusTypeExtID
' Set the condition type.
condition.ConditionType = MFConditionType.MFConditionTypeEqual
' Set the value.
' In this case "MyExternalObjectId" is the ID of the object in the remote system.
condition.TypedValue.SetValue MFDataType.MFDatatypeText, ThisWorkbook.Sheets("Other Data").Range("J30").Value
'Add the condition to the collection.
oScs.Add -1, condition
'Search.
Dim results 'As ObjectSearchResults
Set results = oVault.ObjectSearchOperations.SearchForObjectsByConditions(oScs, MFSearchFlags.MFSearchFlagNone, False) ' False = SortResults
'Output the number of items matching (should be one in each object type, at a maximum).
'MsgBox ("There were " & results.Count & " objects with the display Id of" & ThisWorkbook.Sheets("Other Data").Range("J30").Value)
For i = 1 To results.Count
ThisWorkbook.Sheets("Start").Cells(i, 1).Value = results[i]
Next i
End Sub
EDIT 2
Also () does not work:
If you're looking for a quick and direct way try this:
For i = 1 To results.Count
Cells(i, 1).Value = results(i - 1)
Next i
Tip: Cells(row, column)
I've added items to my collection, named "Questions". I know it adds items to the collection because Questions.count is the right result in various scenarios. However, I'm unable to extract the value of individual .items within the collection.
It gives me the following error: "Invalid procedure call or argument"
So obviously, there must be something basic I don't understand about collections (I only recently learned about them, please be patient with me).
I'm using the methods that I've found online, specifically:
The site https://excelmacromastery.com/excel-vba-collections/ says I should be able to do this:
"You can also use the Item Property to access an item in the collection. It is the default method of the collection so the following lines of code are equivalent:
Debug.Print coll(1)
Debug.Print coll.Item(1)"
Doesn't work for me, no idea why.
'[1] SELECT BOX
Dim SelectedBox As Long
SelectedBox = Box 'NEED TO CONVERT RESULT OF FUNCTION "BOX" TO A VARIABLE WITH TYPE LONG
'[2] TEST CRITERIA FOR QUESTION
Dim Questions As New Collection
Dim SubjectRange As Long
SubjectRange = ThisWorkbook.Sheets(cmbTopics.Text).Cells(Rows.Count, "A").End(xlUp).Row
Dim BoxMatch As Boolean
Dim ChapterMatch As Boolean
'TEST EACH QUESTION IN TOPIC (DETERMINED BY LISTBOX SELECTIONS)
For X = 2 To SubjectRange
BoxMatch = False 'SAYS WHETHER IT PASSED THE TEST
ChapterMatch = False 'SAYS WHETHER IT PASSED THE TEST
'IS QUESTION IN THE RIGHT BOX?
If ThisWorkbook.Sheets(cmbTopics.Text).Range("D" & X).Value = SelectedBox Then
BoxMatch = True
End If
'IS QUESTION IN THE RIGHT CHAPTER?
For Y = 0 To lbChapters.ListCount - 1
If _
lbChapters.List(Y) = ThisWorkbook.Sheets(cmbTopics.Text).Range("B" & X).Value And _
lbChapters.Selected(Y) = True _
Then
ChapterMatch = True
Next Y
'IF SO, THEN ADD IT TO THE LIST OF CANDIDATE QUESTIONS ("QUESTIONS")
If BoxMatch = True And ChapterMatch = True Then
Questions.Add ThisWorkbook.Sheets(cmbTopics.Text).Range("A" & X).Value
End If
Next X
'MsgBox ("Matches: " & Questions.Count)
Dim n As Long
n = RndBetween(1, Questions.Count)
MsgBox (Questions.Item(n))
I want to be able to extract the string that should be at the location specified by the code.
I saw you said that Questions.count should be greater than 0 but it would result in the error you are seeing. To be sure you should add:
If Questions.count > 0 then
MsgBox (Questions.Item(n))
Else
MsgBox ("Questions was empty")
end if
Also check that "n" is being returned as an integer.
I manage a contract log that list out all of my company's contracts with the effective and expiring date.
I've written VBA code that alerts me whenever any one of the contracts is about to expire; a message box will show up that tells me the "carrier's contract# is about to expire". (Please see the code below).
However, because there are different Amendments for each contract, the same contract number may be listed out multiple times in the spreadsheet. If one contract is about to expire, the code notifies me multiple times.
How can I modify my code so it only alerts me once for the same contract number?
Column A is the carrier name, column B is the contract #, Column C is the Amendment# and Column G is the expiration date for each contract.
Let me know if I didn't make myself clear enough or more information is needed.
Private Sub Workbook_Open()
Dim rngC As Range
With Worksheets("NON-TPEB SC LOGS(OPEN)")
For Each rngC In .Range(.Range("G5"), .Cells(.Rows.Count, "G").End(xlUp))
If rngC.Value > Now And (rngC.Value - Now) < 7 Then
MsgBox .Cells(rngC.Row, 1).Value & "'s " & _
.Cells(rngC.Row, 2).Value & " is expiring!!"
End If
Next rngC
End With
End Sub
I would use a Scripting.Dictionary to keep track of contract numbers that have already been checked. This is how you might implement it.
After you do your logic test (If rngC.Value > Now And...) check to see if the contractNum exists in the dictionary. That's what this line does:
If Not checkedDict.Exists(contractNum) Then
If that evaluates True, then the contract has not already been checked, so we add it to the dictionary, and display the message box.
If that evaluates to False, then the contract does exist in the
dictionary, so can do nothing, since the user has already been
informed of the expiring contract.
Here is the full code (untested):
Private Sub Workbook_Open()
'Requires reference to Microsoft SCripting Runtime
' or, simply declare the scripting obects as generic "Object" variables.
Dim checkedDict As Scripting.Dictionary
'Dim checkedDict as Object '## Use this line (andcomment out the preceding line if you cannot enable the library reference to Scripting Runtime
Dim contractNum As String
Dim carrierName As String
Dim rngC As Range
Set checkedDict = CreateObject("Scripting.Dictionary")
With Worksheets("NON-TPEB SC LOGS(OPEN)")
For Each rngC In .Range(.Range("G5"), .Cells(.Rows.Count, "G").End(xlUp))
carrierName = .Cells(rngC.Row, 1).Value
contractNum = .Cells(rngC.Row, 2).Value
If rngC.Value > Now And (rngC.Value - Now) < 7 Then
If Not checkedDict.Exists(contractNum) Then
checkedDict.Add contractNum, carrierName
MsgBox carrierName & "'s " & _
contractNum & " is expiring!!"
Else:
' this contract# already exists, so, do nothing
' because the user was already informed.
End If
End If
Next rngC
End With
set checkedDict = Nothing
End Sub
The above code requires a reference to Microsoft Scripting Runtime Library, or, simply Dim checkedDict as Object instead.
I always use an AlreadyChecked string variable to keep track of what has already been processed.
In the loop add a check like this:
Dim AlreadyChecked As String
AlreadyChecked = "#"
If Instr(AlreadyChecked, "#" & ValueToCheck & "#") = 0 Then
AlreadyChecked = AlreadyChecked & ValueToCheck & "#"
... do your stuff ...
End If