Command button in a userform to delete multiple rows depending on selection in listbox - vba

I am currently working on a userform which has a "Clear Process" command button. The idea is that my userform has a listbox which will list all of the current processes.
From here the user will select which process(es) he/she would like to clear from the worksheet (delete all rows relating to the process).
Embedded in the code I have used the word "Lisa" as a point of reference for the previous userform to know which cell the Process Name should be, using the offset function.
I would like to be able to use the word "Lisa" once the process to be deleted has been identified by the user. This would always be the row where "Lisa" is found and 19 rows below.
I have started some code but when trying to find "Lisa" depending on the selection made by the user I came across an issue.
Private Sub ClearButton_Click()
Dim findvalue As Range
Dim cDelete As VbMsgBoxResult
'hold in memory
Application.ScreenUpdating = False
'check for values
If Emp1.Value = "" Or Emp2.Value = "" Then
MsgBox "There are no processes to delete"
Exit Sub
End If
'confirm process should be deleted
cDelete = MsgBox("Are you sure you want to delete this process?", vbYesNo)
If cDelete = vbYes Then
'find the process to be deleted
'''''''set findvalue =
'''''''delete entire process
findvalue.EntireRow.Delete
End If
End Sub
Hopefully this is enough information, any help would be greatly appreciated :)

If you use Named ranges for your processes, which seems to be almost mandatory in your case, then you can do the following:
Sub DeleteNamedRange(rngName As String)
Range(rngName).ClearContents
ThisWorkbook.Names(rngName).Delete
End Sub
Invoke the Sub this way:
Call DeleteNamedRange("Lisa")

Something as small as this one would set a range to a found value and delete it:
Public Sub TestMe()
Dim findValue As Range
Set findValue = Selection.Find("Lisa")
findValue.EntireRow.Delete
End Sub
As a good practice, you may check whether the findValue is not nothing before deleting. Thus, you avoid an error:
If Not findValue Is Nothing Then
findValue.EntireRow.Delete
End If
And if you want to make the code even one step further, keep in mind that the default value of the argument After in Find() is the first cell. Thus, Find() always start looking from the second cell. To avoid this, and to start looking from the first cell, it is considered a good practice to pass the After:= argument explicitly:
Public Sub TestMe()
Dim findValue As Range
Dim selectedValue As Range
Set selectedValue = ActiveSheet.Range(Selection.Address)
With selectedValue
Set findValue = .Find("Lisa", after:=.Cells(.Cells.Count))
End With
If Not findValue Is Nothing Then
findValue.EntireRow.Delete
End If
End Sub
To make the code even more "interesting", one may decide to check whether a range is selected (a shape can be also selected). Thus, the TypeName(Selection) can be used with something like this:
If TypeName(Selection) <> "Range" Then
MsgBox "Range is not selected!"
Exit Sub
End If
Range.Find MSDN

Related

Why do I get the an error with the code below

I have the following code and keep getting the message "Object variable or With Block variable not set
Sub Find()
Dim Order As String
Order = frmForm.txtSO
Sheets("Download").Select
Range("D2:D4130").Find(What:="Order").Select
End Sub
The frmForm.txtSO is a field in a user form I have created. I'm not sure why I keep getting the error message
Fixing The Error:
You need to remove the quotes around "Order", currently you are searching for the string "Order" and not the variable, this should work:
Sub Find()
Dim Order As String
Order = frmForm.txtSO
Sheets("Download").Select
Range("D2:D4130").Find(What:=Order).Select
End Sub
Accounting for search string not found:
You may also want to add some code to handle the scenario where the "Order" can't be found, something like this:
Sub Find()
Dim Order As String
Dim findRng As Range
Order = frmForm.txtSO
Set findRng = Sheets("Download").Range("D2:D4130").Find(What:=Order)
If Not findRng Is Nothing Then
Sheets("Download").Activate
findRng.Select
Else
MsgBox ("Order not found")
End If
End Sub
Ensuring multiple search strings can be found:
To ensure you are able to "loop" through the duplicate records in column D you'll need to modify the code again. Essentially, you need to change the Find range so that it starts it's search from the currently selected cell and works it's way down from there:
Sub Find()
Dim Order As String
Dim findRng As Range
Order = frmForm.txtSO
Set findRng = Range("D" & ActiveCell.Row & ":D4130").Find(What:=Order)
If Not findRng Is Nothing Then
findRng.Select
Else
MsgBox ("Order not found")
End If
End Sub
However, if when the userform is opened the currently selected cell is on a row lower than the record you're searching for, the above code will be unable to find said record. To get around this, instead of activating the "Download" sheet from this sub, simply active the sheet and select cell D1 from the same sub that launches the userform, like so:
Sheets("Download").Activate
Range("D1").Select
frmForm.Show
This will ensure that when the userform is initially launched the search will begin from D1, however each subsequent time that Find is run it will begin the search from whatever was selected in the previous run

VBA User form gives warning if duplicate is found

I think I need to try and make this question easier. So here goes;
I am creating a User form in Excel that will act as a data capture form.
In this form I have a Textbox called PolBX In this a is placed and at submission data in PolBX is copied into the "G" column using this code
Cells(emptyRow, 7).Value = PolBX.Value. This works great.
I discovered that there may be instances where the User may accidently use the same Unique Id number twice. so I am trying to find out how to code it that after the User has entered the Unique Id number it would check for that string (Consists of letters and numbers). if it finds the string already in the 7th column(G) it must say something like
"Policy number already Used, please try again"
I am thinking I will need to use the following subroutine
Private Sub PolBX_AfterUpdate()
End Sub
Can some please assist with creating this code...
Also can you please explain what you are doing as I started VBA about a week ago
You can add the following code to search for your policy number, and if nothing found then PolLookup = Nothing.
Option Explicit
Sub Test()
On Error GoTo ErrHandler
Dim ws As Worksheet, PolLookup As Range, LookupRng As Range
Set ws = ThisWorkbook.Worksheets(1)
'This is the range you want to search, it can be a long range
'or it can be a single cell.
Set LookupRng = ws.Range("A:A")
'Range.Find is looking for your value in the range you specified above
Set PolLookup = LookupRng.Find("YourLookupValue")
'PolLookup = Nothing if it didn't find a match, so we want to use
'If <NOT> Nothing, because this suggests .Find found your value
If Not PolLookup Is Nothing Then
Err.Raise vbObjectError + 0 'Whatever error you want to throw for finding a match
End If
'Exit before you reach the ErrHandler
Exit Sub
ErrHandler:
If Err.Number = vbObjectError + 0 Then
'Handle your error. Do you want to stop completely? Or have the
'User enter a new value?
End If
End Sub
Basically, after your user enters their value in your UserForm, just make a call to this Sub to do a quick lookup.
Playing around I discovered a Much easier way! I included a Button with he following code attached
Private Sub CommandButton8_Click()
Search = PolBX.Text
Set FoundCell = Worksheets("sheet1").Columns(7).Find(Search,LookIn:=xlValues, lookat:=xlWhole)
If FoundCell Is Nothing Then
MsgBox "No duplicates found"
Else
MsgBox "This policy has already been Assessed" & "Please assess a different case"
PolBX.Value = ""
End If

Data Validation and Worksheet Change Event

I use a VBA macro to query a database and build a list of available projects when a workbook is opened using the workbook activate event. I have project numbers and project names that are combined into two separate data validation lists and applied to two cells. The worksheet change event tests for changes in these cells, splits their data validation lists into arrays, and chooses the corresponding project information from the other array. For instance, if I pick a project number, the worksheet change event finds the project number's position in the project number array, and then picks the project's name from the name array based on position.
This works perfectly whenever a value is selected from the drop down, but I run into problems when values outside the list are entered. For instance, if I enter a blank cell I may get the data validation error or I may get a type mismatch when I use match to find the entered value in the array. I have an error handler to handle the type mismatch, but I would like the data validation error to trigger every time instead. Another problem is that Events will sometimes be disabled. This is much more serious because users will not have a way to turn these back on.
On top of this, I cannot figure out where or how this is happening. I can't replicate how the Events are disabled using breaks because duplicating the steps that lead to the events being disabled with breaks in place only leads to my error handler. However, when breaks aren't applied, the error handler will sometimes fail to trigger and the events will be disabled. Since I'm disabling events just before I parse arrays, I'm thinking the worksheet change fails at the Loc=Application.Match(Target.Text, NumArr, 0) - 1 line, but I can't figure out why no error would be triggered. At the very least, I should get a message with the error number and description, and events should be re-enabled.
Can anyone advise on the interaction between worksheet change and data validation? What is the call order here? Any other advice? Anything I'm missing?
ETA: I've Googled this, but I haven't found anything that helps. Everything that comes up is about working the data validation into worksheet change, nothing about the interaction or call order.
ETA #2: After trying the experiment in the answer below (Thanks Gary's Student), this gets a little more odd. If I choose "Retry" and choose the old, default value, I get the old value three times. If I hit delete, I get a space in the message box, but only one message box. Then the cell is left blank. I can put DV into a loop by clicking "Retry" and accepting the space. The DV error will come up until I click cancel. Then I will get a series of empty text message boxes, one for each time I retried the empty cell. If I start off with a listed value, clear the cell with backspace, click "Retry," and try to select another value, the worksheet change event fails at Intersect 3 times. I think the answer below sheds more light on what is going on, but it does bring up more questions also.
Here is the code I have:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim NumArr() As String
Dim ProjArr() As String
Dim Loc As Integer
On Error GoTo ErrHandler:
If Target.Address = "$E$4" Then
'Disable events to prevent worksheet change trigger on cell upates
Application.EnableEvents = False
'Parse validation lists to arrays
NumArr = Split(Target.Validation.Formula1, ",")
ProjArr = Split(Target.Offset(1, 0).Validation.Formula1, ",")
'Change error handler
On Error GoTo SpaceHandler:
'Determine project number location in array
Loc = Application.Match(Target.Text, NumArr, 0) - 1
'Change error handler
On Error GoTo ErrHandler:
'Change cell value to corresponding project name based on array location
Target.Offset(1, 0) = ProjArr(Loc)
'Unlock cells to prepare for editing, reset any previously imported codes
Range("C8:G32").Locked = False
'Run revenue code import
RevenueCodeCollector.ImportRevenueCodes
'Re-enable events
Application.EnableEvents = True
End If
If Target.Address = "$E$5" Then
Application.EnableEvents = False
NumArr = Split(Target.Validation.Formula1, ",")
ProjArr = Split(Target.Offset(-1, 0).Validation.Formula1, ",")
Loc = Application.Match(Target.Text, NumArr, 0) - 1
Target.Offset(-1, 0) = ProjArr(Loc)
Range("C8:G32").Locked = False
RevenueCodeCollector.ImportRevenueCodes
Application.EnableEvents = True
End If
Exit Sub
ErrHandler:
MsgBox Err.Number & " " & Err.Description
Application.EnableEvents = True
Exit Sub
SpaceHandler:
MsgBox "Pick a project from the dropdown.", vbOKOnly, "Error"
Application.EnableEvents = True
End Sub
You have a very open-ended question...........not having the time to do a full whitepaper, here is a simple experiment. I use the Event code:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A1 As Range, rINT As Range
Set A1 = Range("A1")
Set rINT = Intersect(A1, Target)
If rINT Is Nothing Then Exit Sub
MsgBox A1.Value
End Sub
and in A1, I setup DV as follows:
If I use the drop-down, I get the value entered and I also get the MsgBox. However, if I click on the cell and type some junk what happens is:
the DV alert occurs and I touch the CANCEL Button
I get 2 MsgBox occurrences, each with the original contents rather than the attempted junk !!
I have absolutely no idea why the event is raised since the cell is not actually changed, let alone why the Event is raised twice !! It is almost as if
the event is raised on junk entry, but the DV alarm has precedence, the DV reverse the entry and another event is raised, and finally both events get processed.
Hopefully a person smarter than me will chime in.
With ref to the query, Workaround for the DV and change event is managed.
Public strRange As String
Public bCheck As Boolean
Private Sub Worksheet_Change(ByVal Target As Range)
If bCheck Then Exit Sub
MsgBox "Correct Entry!"
strRange = Target.Address
bCheck = True
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Address <> strRange Then bCheck = False
End Sub
http://forum.chandoo.org/threads/multiple-worksheet-change-event-with-data-validation.32750

Macro launching when a cell value changes due to a formula not by the user

I would like my Macro to launch whenever a value in a cell containing a formula changes.
i.e. the user is modifying another cell thus changing the value of the cell in question.
I have noticed that using the statement (found herein), only works if the user modifies the cell itself but not if the cell changes automatically - due to a formula as specified above.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A20")) Is Nothing Then ...
Any thoughts??
I tried to follow the answers from this question "automatically execute an Excel macro on a cell change" but it did not work...
Thanks in advance :)
A possible work-around comes from the fact that, to change a value, the user needs to change the selection first. So I would:
1) Declare a global variable called "oldValue" on top of the WS source code module:
Dim oldValue As Variant
2) Register the old value of your formula before the user types anything (let's say it's in Range("A4"), I let you adapt with the others):
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Range("A4")
End Sub
3) Check if the change has affected the formula in the Change event:
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("A4") <> oldValue Then
MsgBox "User action has affected your formula"
End If
End Sub
I've tested with a simple sum, I'm able to write cells that are not involved without any prompt but if I touch one of the cells involved in the sum the MsgBox will show up. I let you adapt for multiple cases, for user adding/removing rows (in that case I suggest to name the ranges containing the formulas you want to track) and the worksheet references.
EDIT I'd like to do it at once, not by going through 2 processes, is it possible? The problem is my macro involves a range containing more than one cell so it will be hard to store old values for 10 cells.
If ranges are next to each other, then instead of using a variable you can use a collection:
Dim oldValues As New Collection
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
For j = 1 To 10
oldValues.Add Range("A" & j).Value
Next j
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
For j = 1 To 10
If Range("A" & j).Value <> oldValues(j) Then
MsgBox "The value of Range(A" & j & ") has changed"
End If
Next j
End Sub
Of course, if ranges are not close to each other, you can just store them anyway in the SelectionChange event like this:
oldValues.Add Range("A1").Value
oldValues.Add Range("B7").Value
'...
and if you done this ONCE, with 10 ranges only, it should be a reasonable solution to your problem.
You said, "I would like my Macro to launch whenever a value in a cell containing a formula changes..."
If having your code run whenever a cell containing a formula is recalculated (which is not exactly what you asked for), one solution might be to create a VBA function that simply returns that value passed to it, plus does whatever else you want to do when the formula is recalculated...
Public Function Hook(ByVal vValue As Variant) As Variant
Hook = vValue
' Add your code here...
End Function
...then "wrap" your formula in a call to this function. For example, if the formula you are interested in is =A1+1, you would change this to =Hook(A1+1), and the Hook function would be called whenever A1+1 is recalculated (for example, when the value in A1 changes). However, it is possible that recalculating A1+1 will yield the same result and still call the Hook function (for example, if the user re-enters the same value in A1).
You can have a go at this:
First, in a Module Code declare a Public Variable.
Public r As Range, myVal '<~~ Place it in Module
Second, initialize your variables in Workbook_Open event.
Private Sub Workbook_Open()
Set r = Sheet1.Range("C2:C3") '<~~ Change to your actual sheet and range
myVal = Application.Transpose(r)
End Sub
Finally, set up your Worksheet_Calculate event.
Private Sub Worksheet_Calculate()
On Error GoTo halt
With Application
.EnableEvents = False
If Join(myVal) <> Join(.Transpose(r)) Then
MsgBox "Something changed in your range"
'~~> You put your cool stuff here
End If
myVal = .Transpose(r)
forward:
.EnableEvents = True
End With
Exit Sub
halt:
MsgBox "Error " & Err.Number & ": " & Err.Description
Resume forward
End Sub
Above will trigger the event when values in C2:C3 changes.
Not really very neat but works in detecting changes in your target range. HTH.
Declaring a module -level variable like Matteo describes is definitely one good way to go.
Brian 's answer is on the right track with regards to keeping all is the code in the same place, but it's missing one critical part : Application.Caller
When used in function that is called by a single cell, Application.Caller will return the Range object of that cell. This way you can store the old value within the function itself when it is called, then once you're done with calculating the new value you can compare it with the old and run more code as required.
Edit: The advantage with Application.Caller is that the solution scales in and of itself, and does not change no matter how the target cells are arranged (I.e. Continuous or not).

Is there a way to use an input value (textbox) of a userform as a variable for another userform?

Ok so what I am trying to accomplish: I have one userform that asks how many new orders the user needs to process. I set the user input to a variable in this code (not sure if it even does anything).
Private Sub CommandButton1_Click()
UserForm2.Show
OrderNum.Text = NewOrders
'I changed the textbox name to OrderNum
End Sub
Then when UserForm2 pops up, I want to be able to input more data with more specific information about the orders. So if on Userform1 I entered in 3, I want to have to submit new data into UserForm2 3 different times. I tried using a For - Next loop (below) but it doesn't work. I'm not sure how (or if) I can store variables like that between Userforms.
Private Sub CommandButton1_Click()
Dim lRow As Long
Dim ws As Worksheet
Set ws = Worksheets("Core Info")
OrderNum.Text = NewOrders
lRow = ws.Cells(Rows.Count, 2).End(xlUp).Offset(1, 0).Row
For i = 1 To NewOrders
ws.Cells(lRow, 1).Value = TextBox1.Text
ws.Cells(lRow, 3).Value = TextBox2.Text
Next i
UserForm2.Hide
End Sub
The the second userform pops up as it should, but then nothing works after that. Can anyone tell me what I could do to fix this?
Note: I realize that those of the above start with CommandButton1 (default) but they are on different Userforms.
It will be helpful (and is good coding practice) to give your form controls more easily recognizable names, instead of the ambiguous CommandButton1 nomenclature. Revise CommandButton1's name on UserForm1 to Form1_SubmitButton. Then, use this code to handle the form submission.
Sub Form1_SubmitButton_Click()
' a textbox control named OrderNum on UserForm1 has captured the value
'Assign the value from the OrderNum textbox to a named variable in the worksheet
ActiveWorkbook.Names.Add "OrderNum", Me.OrderNum.Text
UserForm2.Show
End Sub
Then, in UserForm2 change the name of your command button to something like Form2_SubmitButton and use this code:
Sub Form2_SubmitButton_Click()
Dim orderNum as Long
orderNum = Replace(ActiveWorkbook.Names("OrderNum").Value,"=",vbNullString)
'the rest of your code goes here.
End Sub
So, what we have done above is to create a Name in the worksheet which contains the value you want to use on the several forms. You can always obtain this value by Replace(ActiveWorkbook.Names("OrderNum").Value,"=",vbNullString)
Alternatively, you could use a public variable in your code module.