Initialize Correct Value to Avoid Object variable or With block variable not set - vba

I have block of code where I'm using a For... Next loop to go through an Excel sheet, and tell me if values entered in the text boxes were found or not. I've modified it to work if the values match. Yet, I'm receiving the Object/With Block variable not set error, and it confusing me. I've created the following:
Dim Value2Find_1 As String = txtMachNumber.Text 'Serial number value.
Dim Value2Find_2 As String = txtMachName.Text 'Machine name value.
Dim ReplaceWithValue1 As String = "" 'Replaces the serial number value if found in the sheet.
Dim ReplaceWithValue2 As String = "" 'Replaces the machine name value if found in the sheet.
Dim ReplaceWithValue3 As String = "" 'Replacement for the date-time in the Date Column.
Dim Range2Use_1 = xlWS.Range("A1:A4000") 'Range to span the A Column.
Dim Range2Use_2 = xlWS.Range("B1:B4000") 'Range to span the B Column.
Dim Range2Use_3 = xlWS.Range("F1:F4000") 'Range to span the F Column.
Dim xlCell_A = Range2Use_1.Find(txtMachNumber.Text) 'Looks up the searched serial value in A Column.
Dim xlCell_B = Range2Use_2.Find(txtMachName.Text) 'Looks up the searched machine value in B Column.
Dim LastRow = xlWS.Range("A4000").End(Excel.XlDirection.xlUp).Row + 1
Dim i As Integer
With xlWS
For i = 1 To LastRow
If Not (Value2Find_1 = txtMachNumber.Text And Value2Find_2 = txtMachName.Text) Then
MessageBox.Show("No value exists here...")
Else
Range2Use_1.Find(What:=Value2Find_1, MatchCase:=True)
Range2Use_2.Find(What:=Value2Find_2, MatchCase:=True)
MsgBox("Found both values: " & Value2Find_1 & " and " & Value2Find_2 & " on row " & xlCell_A.Row)
End If
Exit Sub
Next
End With
If my textbox entries are not in the sheet, the errors returns on the following line of code:
MsgBox("Found both values: " & Value2Find_1 & " and " & Value2Find_2 & " on row " & xlCell_A.Row)
I've narrowed it down to have something to do with the variable that returns the row number of the located textbox entries - xlCell_A. This is where I'm stuck, however. What do I need to set this as in order to avoid the Object/With Block variable not set error? I am afraid I don't know what this is asking for.

I think the problem with your code is that the Find method returns Nothing whenever no match is found, as stated in its documentation. Thus, xlCell_A.Row returns the error because the Row method cannot be called on Nothing.
Actually, I see a number of further issues with your code:
The interior of the for loop does not depend on the loop variable i. Hence, it does exactly the same thing in each interation.
The variable xlWS of the With block is never used, which makes the With block unnessesary.
The return values of the Find methods in the loop never gets assigned to anything. Because of this, they have no effect.
The condition in the if statement always returns False since you never change the values of Value2Find_1 and Value2Find_2 and you initialized them to txtMachNumber.Text and txtMachName.Text, respectively.
If you intend to evaluate whether the values txtMachNumber.Text and txtMachName.Text are present in the column A and B, respectively, you can just test whether xlCell_A and xlCell_B are Nothing.
Since you want to find both on the same row, which using Find does not guarantee, it might be easier to use a loop as in your code but replace
txtMachNumber.Text and txtMachName.Text with Range2Use_1.Cells(i,1) and Range2Use_2.Cells(i,1) in the if statement. (This compares the value in the ith row with the values to search for.) Obviously, you would have to exit the loop after finding a match, e.g. using the break statement.

Related

Excel VBA : Auto numbering

I'm creating a database on Excel, and encountered some problems as I tried to assign auto number to each row.
Requirements are:
generate auto number to each row(on the column A) when column B is not blank.
the number should be unique and must always be connected to the contents of the same row even when the column is sorted or when new rows are inserted, etc.
when a new row is inserted (anywhere on the same column), a new number should be assigned (the newest number should be the biggest number)
if
possible, the auto number should have a prefix, and number should be displayed in four digits (e.g. 0001, 0011)
I have tried some VBA codes I found from other people's questions (e.g. Excel VBA : Auto Generating Unique Number for each row).
So far, the code below has worked the best, but the requirement (3) and (4) couldn't be solved by that code.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim maxNumber
If Not Intersect(Target, Range("B:B")) Is Nothing Then
' don't run when more than one row is changed
If Target.Rows.Count > 1 Then Exit Sub
' if column A in the current row has a value, don't run
If Cells(Target.Row, 1) > 0 Then Exit Sub
' get the highest number in column A, then add 1 and write to the
' current row, column A
maxNumber = Application.WorksheetFunction.Max(Range("A:A"))
Target.Offset(0, -1) = maxNumber + 1
End If
End Sub
I'm short of the knowledge of VBA and I hope someone could help me this.
Many thanks.
Alternative via CustomDocumentProperties
Instead of using a hidden sheet as proposed by #TimWilliams, one can assign incremented values to a user defined custom document property (CDP), naming it e.g. "InvNo" holding the newest invoice number. The cdp remain stored in the saved workbook.
The function below gets the current number saved to this workbook related property and returns the next number by adding 1 to the current value. It uses a help procedure RefreshCDP to assign the new value (could be used of course independantly to reset values programmaticaly to any other value). - If the cdp name isn't passed as (optional) argument, the function assumes "InvNo" by default.
Note that code requires some error handling to check if the cdp exists.
Example call
Dim InvoiceNumber as Long
InvoiceNumber = NextNumber("InvNo") ' or simply: NextNumber
Public Function NextNumber(Optional CDPName As String = "InvNo") As Long
'a) get current cdp value
Dim curVal As Long
On Error Resume Next
curVal = ThisWorkbook.CustomDocumentProperties(CDPName)
If Err.Number <> 0 Then Err.Clear ' not yet existing, results in curVal of 0
'b) increment current cdp value by one to simulate new value
Dim newVal As Long
newVal = curVal + 1
'Debug.Print "Next " & CDPName & " will be: " & newVal
'c) assign new value to custom document property
RefreshCDP CDPName, newVal, msoPropertyTypeNumber
'Debug.Print "New " & CDPName & " now is: " & ThisWorkbook.CustomDocumentProperties(CDPName)
NextNumber = newVal
End Function
Help procedure RefreshCDP
Sub RefreshCDP(CDPName As String, _
newVal As Variant, docType As Office.MsoDocProperties)
On Error Resume Next
ThisWorkbook.CustomDocumentProperties(CDPName).Value = newVal
'If cdp doesn't exist yet, create it (plus adding the new value)
If Err.Number > 0 Then
ThisWorkbook.CustomDocumentProperties.Add _
Name:=CDPName, _
LinkToContent:=False, _
Type:=docType, _
Value:=newVal
End If
End Sub
Related links
MS help: Excel.Workbook.CustomDocumentProperties
Check if BuiltInDocumentProperty is set without error trapping
Chip Pearson: Document Properties
How to add a DocumentProperty to CustomDocumentProperties in Excel?
Do not use Max() to find the next number - use instead a hidden sheet or name to store the current number, and increment it each time a new Id is required.
For example:
Public Function NextNumber(SequenceName As String)
Dim n As Name, v
On Error Resume Next
Set n = ThisWorkbook.Names(SequenceName)
On Error GoTo 0
If n Is Nothing Then
'create the name if it doesn't exist
ThisWorkbook.Names.Add SequenceName, RefersTo:=2
v = 1
Else
'increment the current value
v = Replace(n.RefersTo, "=", "")
n.RefersTo = v + 1
End If
NextNumber = v
End Function
This allows you to use multiple different sequences as long as you give each one a distinct name.
Dim seq
seq = NextNumber("seqOne")
'etc

VBA - adding a sheet with duplicates and tranposing output into rows

I have the following data set, which contains duplicates.
values:
2880CR-20.36KX53305DECOAK2015
F05572-CN48517OCTOAK2016
F05572-CN48517DECOAK2016
F05572-CN48517NOVOAK2015
F05572-CN48517NOVOAK2015(duplicate)
F05572-CN48517DECOAK2015
F05573-CN48517JANOAK2016
F05573-CN48517FEBOAK2016
F05573-CN48517JANOAK2015
F05573-CN48517FEBOAK2015
F05573-CN48517MAROAK2015
F05573-CN48517APROAK2015
F05573-CN48517APROAK2015(duplicate)
I am trying to create a macro that will look at the values in column A, from A2:A (count of rows in column), and return a list of the duplicate values contained in the string declared "strMyDupList". Basically, if there is atleast 1 duplicate, the msgbox will pop up and the new sheet created with the columns address and values and I am trying to list out all the values seperated my a comma VERTICALLY, instead of horizontally across the sheet. so like:
Address value
$A$5 F05572-CN48517NOVOAK2015
$A$13 F05573-CN48517APROAK2015
my code is :
If strMyDupList <> "" Then
MsgBox "The following entries have been used more than once:" & vbNewLine & strMyDupList
Worksheets.Add.name = name
Worksheets(name).Range("A1").Value = "Location"
Worksheets(name).Range("B1").Value = "Value"
' Worksheets(name).Range("A2:C2").Value = Split(strMyDupList, ",")
Worksheets(name).Range("B4:B6") = Split(Application.WorksheetFunction.Transpose(strMyDupList), ",")
The results are that I am able to get the values tranposed from horizontal to vertical, however, with this code, it is only returning the FIRST VALUE in the list of values in the string, so it's returning:
Address value
$A$5 F05572-CN48517NOVOAK2015
$A$5 F05572-CN48517NOVOAK2015 (should be F05573-CN48517APROAK2015)
I've seen the UBound with Resize could work but I have no idea how the syntax works or is used. Can someone assist?
Thank you
Here is a complete example of how to leave duplicates out of your information.
Essentially, it sorts all of your information. Therefore, when you sort you'll get the consecutive value which would be itself if it was a dupe.
It uses a .NET feature, System.Collections.ArrayList, that was in 2.0 & 3.5 so that has to be installed on your machine. Usually it already is but it may not be. You can turn it on through Programs & Features.
Sub StringArrayDupeChecker()
Dim var As Variant
Dim holder As String
Dim strMyList() As String
Dim myDupeData As Variant
Dim str As String
str = "one,two,three,three,three,four,five,five"
strMyList = Split(str, ",")
holder = ""
Set var = CreateObject("System.Collections.ArrayList")
Set myDupeData = CreateObject("System.Collections.ArrayList")
For Each i In strMyList
var.Add (i)
Next i
var.Sort
For Each j In var
If Not j = holder Then
'do your stuff
str = "notDupe"
Else
myDupeData.Add(j)
End If
holder = j
Next j
End Sub

Circular reference is user defined function VBA excel

My aim is to add the values of certain columns using a user defined function in the actual row. The columns are given in another table. I am reading the name of rows, calculating the actual value and sum them. This function called once from excel but executed 4 times. At the end it indicates a circular reference error. There is no such error in the excel file, I checked if the udf return just 42 then there is no error. First I suspected Application.Caller, but ruled out.
Function SumColumnsWithSuffix(suffix As String, rowNumber) As Integer
'can be used only in Összesíto table
Dim myTable As Excel.ListObject
Dim mySheet As Excel.Worksheet
Dim myRow As Excel.ListRow
Set mySheet = ThisWorkbook.Worksheets("összesíto")
Set myTable = mySheet.ListObjects("Számlák")
Dim columnName As String
result = 0
For Each myRow In myTable.ListRows
columnName = Intersect(myRow.Range, myTable.ListColumns("Oszlop név").Range)
columnName = "Összesíto[" & columnName & " " & suffix & "]"
'actualRow = Application.Caller.row
'rowName = actualRow & ":" & actualRow
rowName = rowNumber & ":" & rowNumber
myRowRange = Range(rowName)
actualValue = Intersect(Range(columnName), Range(rowName))
result = result + actualValue
Next myRow
SumColumnsWithSuffix = result
End Function
myRowRange is not explicitly declared (or used, actually) so it is implicitly a Variant. That means your assignment here...
myRowRange = Range(rowName)
...is also making an implicit call to .Value. That call will evaluate the results of every single cell in Range(rowName) to populate the array of Variant that it returns. If any of those cells contains a call to SumColumnsWithSuffix, you'll get a circular reference.

WWBasic + SPSS, script to rename value labels

before I start I want to point out that I tagged this question as VBA because I can't actually make a new tag for Winwrap and I've been told that Winwrap is pretty much the same as VBA.
I'm working on SPSS V19.0 and I'm trying to make a code that will help me identify and assign value labels to all values that don't have a label in the specified variable (or all variables).
The pseudo code below is for the version where it's a single variable (perhaps inputted by a text box or maybe sent via a custom dialogue in the SPSS Stats program (call the .sbs file from the syntax giving it the variable name).
Here is the Pseudo Code:
Sub Main(variable As String)
On Error GoTo bye
'Variable Declaration:
Dim i As Integer, intCount As Integer
Dim strValName As String, strVar As String, strCom As String
Dim varLabels As Variant 'This should be an array of all the value labels in the selected record
Dim objSpssApp As 'No idea what to put here, but I want to select the spss main window.
'Original Idea was to use two loops
'The first loop would fill an array with the value lables and use the index as the value and
'The second loop would check to see which values already had labels and then
'Would ask the user for a value label to apply to each value that didn't.
'loop 1
'For i = 0 To -1
'current = GetObject(variable.valuelist(i)) 'would use this to get the value
'Set varLabels(i) = current
'Next
'Loop for each number in the Value list.
strValName = InputBox("Please specify the variable.")
'Loop for each number in the Value list.
For i = 0 To varLabels-1
If IsEmpty (varLabels(i)) Then
'Find value and ask for the current value label
strVar = InputBox("Please insert Label for value "; varLabels(i);" :","Insert Value Label")
'Apply the response to the required number
strCom = "ADD VALUE LABELS " & strVar & Chr$(39) & intCount & Chr$(39) & Chr$(39) & strValName & Chr$(39) &" ."
'Then the piece of code to execute the Syntax
objSpssApp.ExecuteCommands(strCom, False)
End If
'intCount = intCount + 1 'increase the count so that it shows the correct number
'it's out of the loop so that even filled value labels are counted
'Perhaps this method would be better?
Next
Bye:
End Sub
This is in no way functioning code, it's just basically pseudo code for the process that I want to achieve I'm just looking for some help on it, if you could that would be magic.
Many thanks in advance
Mav
Winwrap and VBA are almost identical with differences that you can find in this post:
http://www.winwrap.com/web/basic/reference/?p=doc_tn0143_technote.htm
I haven't used winwrap, but I'll try to answer with my knowledge from VBA.
Dim varLabels As Variant
You can make an array out of this by saying for example
dim varLabels() as variant 'Dynamically declared array
dim varLabels(10) as variant 'Statically declared array
dim varLabels(1 to 10) as variant 'Array starting from 1 - which I mostly use
dim varLabels(1 to 10, 1 to 3) 'Multidimensional array
Dim objSpssApp As ?
"In theory", you can leave this as a variant type or even do
Dim objSpssApp
Without further declaration, which is basically the same - and it will work because a variant can be anything and will not generate an error. It is good custom though to declare you objects according to an explicit datatype in because the variant type is expensive in terms of memory. You should actually find out about the objects class name, but I cannot give you this. I guess that you should do something like:
set objSpssApp = new <Spss Window>
set objSpssApp = nothing 'In the end to release the object
Code:
'loop 1
For i = 0 To -1
current = GetObject(variable.valuelist(i)) 'would use this to get the value
Set varLabels(i) = current
Next
I don't exactly know why you want to count from 0 to -1 but perhaps it is irrelevant.
To fill an array, you can just do: varLabels(i) = i
The SET statement is used to set objects and you don't need to create an object to create an array. Also note that you did not declare half of the variables used here.
Code:
strVar = InputBox("Please insert Label for value "; varLabels(i);" :","Insert Value Label")
Note that the concatenation operator syntax is &.
This appears to be the same in WinWrap:
http://www.winwrap.com/web/basic/language/?p=doc_operators_oper.htm
But you know this, since you use it in your code.
Code:
'intCount = intCount + 1 'increase the count so that it shows the correct number
'it's out of the loop so that even filled value labels are counted
'Perhaps this method would be better?
I'm not sure if I understand this question, but in theory all loops are valid in any situation, it depends on your preference. For ... Next, Do ... Loop, While ... Wend, in the end they all do basically the same thing. intCount = intCount + 1 seems valid when using it in a loop.
Using Next (for ... next)
When using a counter, always use Next iCounter because it increments the counter.
I hope this reply may be of some use to you!

Array from Range in Excel VBA

Well I've been struggling with the little bit of code and can't seem to get around it ...
I'm trying to get an array from a range of cells, the array however is showing up to be 1 element wide.
Well here's the code:
Dim item As Variant
MsgBox Range("D19:H19").Count
item = Range("D19:H19").Value
MsgBox LBound(item) & " " & UBound(item)
as per my understanding item should contain a 2D array... however I'm getting the following result
1st MsgBox prints 5
2nd MsgBox prints 1 1
What's going wrong?
The problem is in LBound and UBound
jtolle was correct about the LBound and UBound.
LBound(item, 2)
UBound(item, 2)
However, item must not be dimmed as an array (you'll get an error).
I think this is what you want
Dim item As Variant
MsgBox Range("D19:H19").Count
item = Range("D19:H19").Value
MsgBox LBound(item, 2) & " " & UBound(item, 2)
For i = LBound(item, 2) To UBound(item, 2)
MsgBox item(1, i)
Next
Your item should contain a 2-D array as expected. If you stick a breakpoint in your code and look at the little "Locals" window in the VBA editor, you should see that. Your calls to LBound and UBound are getting the bounds in the first dimension. If you call Lbound(item,2) and UBound(item,2), you should get 1 and 5 as you expect.
EDIT:
That is, once you've made the assignment, item would look like something you could have declared as such:
Dim item(1 to 1, 1 to 5)
One of the banes of VBA programming is that arrays can have arbitrary lower bounds. So all of your code needs to be aware of that.
That's correct as is. Even if you select an array of cells, you still have the option to select one single cell out of the array (and step for example with tab through the items of this array)
.Value
only gets you the content of the currently single-selected cell.
if you want the enumeration of the array, you may call the .Cells()-method of the Range-object
Assuming that D19 until H19 contain "a" through "e" respectively, calling
Range("D19:H19").Cells(2)
returns you "b". Note that this is a one-based array and can be 2-dimensional. Cells() takes at most 2 parameters to specify the inner offset from the selection's origin.
hope that clarifies... regards
Try this:
Dim item As Variant
MsgBox Range("D19:H19").Count
item = Application.Transpose(Range("D19:H19").Value)
MsgBox LBound(item) & " " & UBound(item)
if you want a 1D array, to join it for an IN clause, for example, you should transpose your range.
I've found you have to transpose twice for a row, once for a column of data like this:
Dim rngRow As Range, rngColumn As Range
Set rngRow = Sheets(1).Range("A1", "Z1")
Set rngColumn = Sheets(1).Range("A1", "A20")
Dim arrRowValues, arrColValues
arrRowValues = WorksheetFunction.Transpose(WorksheetFunction.Transpose(rngRow))
arrColValues = WorksheetFunction.Transpose(rngColumn)
Dim numList As String, stringList As String
numList = Join(arrRowValues, ",")
stringList = "'" & Join(arrColValues, "','") & "'"
worth a play.