Alternative to SendKeys in InputBox to choose cells with least user input - excel

I use the SendKeys method, for user-friendliness, with an InputBox, for selecting multiple cells to use for calculations.
I want to avoid having the user hold CTRL while clicking each cell or pressing Shift-F8 to select multiple cells.
I want the InputBox to pop up and to select cells that are not necessarily right next to one another, without having to do anything else.
Here is a sample of what I have.
Sub CalculateIt()
Dim calcRange As Range
On Error Resume Next
SendKeys "+{F8}"
Set calcRange = Application.InputBox("Select the cells you would like to use.", Type:=8)
If Err.Number = 424 Then Exit Sub
Dim stuff As Double
For Each calcCell in calcRange
If IsNumeric(calcCell.Value) Then stuff = stuff + calcCell.Value
Next calcCell
MsgBox "The Solution: " & Sqr(stuff)
End Sub
I want to avoid using the SendKeys method because I've read a lot about it being finicky and fragile, and that it can cause problems. I have run into some scenarios where it doesn't work and I have to hold CTRL or do Shift-F8 anyways.

Possible solution... Add a checkbox to the worksheet then go into the Microsoft Excel Objects | ThisWorkbook window and enter the following code. If the checkbox is checked, then whatever formula you'd like executed will be as the user clicks each cell.
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
Dim nbrCell As Double
If IsNumeric(ActiveCell.Value) Then nbrCell = ActiveCell.Value Else nbrCell = 0
If ActiveSheet.CheckBox1.Value = True Then ActiveSheet.Range("A1").Value = ActiveSheet.Range("A1").Value + nbrCell
End Sub
You can also add a second checkbox to remove selected cells if you're concerned the user might accidentally click on the wrong cell and want that value removed.

Related

Excel using auto-generated hyperlinks to hide rows in a table

I have a table where I want to be able to hide individual rows at a mouse click. The (seemingly) easiest solution I've found is to have a column filled with hyperlinks that call a macro to hide the row that they're in.
There are two ways of calling macros from hyperlinks: using Worksheet_FollowHyperlink with manual hyperlinks, and using =HYPERLINK.
The former works fine, except there's no way (that I've found) to have them generate automatically when new rows are added to the table. I would have to either manually copy them down every time, which is unviable, or add them with VBA, which adds a bunch of complexity to an otherwise simple task.
The latter generates fine, being a formula, but it doesn't actually work. It doesn't trigger Worksheet_FollowHyperlink, and when using =HYPERLINK("#MyFunction()") it just doesn't hide rows (or do much other than editing cells contents).
Function MyFunction()
Set MyFunction = Selection
Selection.EntireRow.Hidden = True
End Function
Is there a good solution to this?
Rather than a Hyperlink, you could handle a Double Click event on the table column
Something like
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim NameOfTableColumn As String
On Error GoTo EH:
NameOfTableColumn = "DblClickToHide" ' update to suit your table
If Not Application.Intersect(Target, Target.ListObject.ListColumns(NameOfTableColumn).DataBodyRange) Is Nothing Then
Target.EntireRow.Hidden = True
Cancel = True
End If
Exit Sub
EH:
End Sub
Please, copy the next code in the sheet code module where the table to be clicked exists. Clicking on each cell in its first column (dynamic to rows adding/insertions/deletions), the clicked row will be hidden:
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim tbl As ListObject
If Target.cells.CountLarge > 1 Then Exit Sub
Set tbl = Me.ListObjects(1) 'you may use here the table name
If Not Intersect(Target, tbl.DataBodyRange.Columns(1)) Is Nothing Then
Application.EnableEvents = False
Target.EntireRow.Hidden = True
Application.EnableEvents = True
End If
End Sub
It would be good to think about a way to unhide the hidden row if/when necessary. If only a row should be hidden at a time, it is easy to unhide all the rest of column cells...

Disable a specific form control button from running its assigned macro even if it is clicked

I would like to be able to prevent a specific button ("Button 4925") from running its assigned macro even when it is clicked. Basically, when you click it, it would either do nothing or show a message that says " This is an essential item that cannot be deleted"
In other words, I would like to be able to exit the sub only if the clicked button is in cell A12. Otherwise, run the code as normal. I don't know how to do that considering that I am a very beginner in VBA.
Some information:
The button is a form control button. Not an Active X one. It gets copied and pasted by another macro on the sheet. The assigned macro is written under a Standard Module.
The assigned macro function is to delete a relative range of rows. Here is the code:
Sub Delete_Button()
' Delete_Button Macro
' Step 1: Select the cell under the clicked button
Dim r As Range
Dim s As Object
Set r = ActiveSheet.Buttons(Application.Caller).TopLeftCell
r.Select
' Step 2: delete all buttons relative to the selected cell from step 1
StartCell = ActiveCell.Offset(-5, 0).Address
EndCell = ActiveCell.Offset(0, 0).Address
For Each s In ActiveSheet.DrawingObjects
If Not Intersect(Range(StartCell, EndCell), s.TopLeftCell) Is Nothing Then
s.Delete
End If
Next s
' Step 3: delete the rows relative to the selected cell from step 1
ActiveCell.Offset(-7, 0).Rows("1:9").EntireRow.Select
Selection.Delete Shift:=xlUp
ActiveCell.Offset(-4, 0).Range("A1").Select
End Sub
You must 'tell' to the code, in a way, that it must not delete the range.
So, I would suggest you to create a Private variable on top of the module keeping the button code (in the declarations area):
Private stopButCode As Boolean
Than, you must make this variable True. Use a Check box, or a piece of code in another control to make it True.
The Button code must be adapted in a way like following:
If Not stopButCode Then
'delete whatever is to be deleted
Else
MsgBox "Deletion not allowed..."
Exit Sub
End If
Edited:
If you want the code not working only if the button will be on cell "A12", you can use adapt your code as following:
Dim r As Range
Dim s As Object
Set r = ActiveSheet.Buttons(Application.Caller).TopLeftCell
If r.Address = "$A$12" then Exit Sub
'here follows your existing code...

Excel VBA: Pass `Target` Variable from Click Event to Userform

I am building an Excel 2016 Userform using VBA and need to collect the row and column of the cell from which the form is opened. I open the form on a cell double click with Worksheet_BeforeDoubleClick and then initialize the Userform with UserForm_Initialize(). I would like to pass the Target of the double click event to UserForm_Initialize() but am not sure how to. This forum thread addresses this issue, but the provided solutions did not work for me.
Here is my Worksheet_BeforeDoubleClick:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Column = Target.Column
Row = Target.Row
'Find the last non-blank cell in column B(2)
lRow = Cells(Rows.Count, 2).End(xlDown).Row
'Find the last non-blank cell in row 2
lCol = Cells(2, Columns.Count).End(xlToRight).Column
If Not Intersect(Target, Range(Cells(3, 3), Cells(lRow, lCol))) Is Nothing Then
Cancel = True
EdgeEntryForm.Show
End If
End Sub
And my UserForm_Initialize():
Private Sub UserForm_Initialize()
Dim Column As Long, Row As Long 'I would like to fill these with the Target values
MsgBox ("Row is " & Row & " Column is " & Column)
'Description.Caption = "Fill out this form to define a network edge from " & Cells(2, Row).Value & " to " & Cells(Column, 2).Value
End Sub
As suggested in my comments, one way would be to just use the ActiveCell and assign that to a variable.
Alternatively, if you do want to pass it as a variable, you can do it with a bit of a workaround, by having a global variable to temporarly hold that information:
In your worksheet code:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
'.....
With UserForm1
Set .rngTarget = Target
.Show
End With
'.....
End Sub
In your userform:
Public rngTarget As Range
Private Sub UserForm_Activate()
'....
If Not rngTarget Is Nothing Then
MsgBox ("Row is " & rngTarget.Row & " Column is " & rngTarget.Column)
Else
MsgBox "something went wrong with assigning rngTarget variable"
End If
'....
End Sub
EDIT: I was trying initially to propose something similar to #MathieuGuindon's answer, but was failing due to my limited knowledge on the difference between initialise and activate (thanks Mathieu).
I've updated the answer to make use of the global variable at userform level, rather than use one from a module.
The form is shown modally, so ActiveCell isn't going to change on you, and should be safe to use in the form's code-behind.
The problem with that, is that you've now tied the form to ActiveSheet/ActiveCell, and now in order to test anything you need to Select or Activate a cell.
If the form code only needs to know about the cell's Address, then it shouldn't be given a Range (give it a Range and it can access any cell in any sheet in any workbook in the Application instance) - that's the principle of least knowledge at play. But this is obviously example code, so let's go with a Range:
Option Explicit
Private internalWorkingCell As Range
Public Property Get WorkingCell() As Range
Set WorkingCell = internalWorkingCell
End Property
Public Property Set WorkingCell(ByVal value As Range)
Set internalWorkingCell = value
End Property
Now your form code can use WorkingCell or internalWorkingCell to do its thing, and no global variable needs to float around;
With New UserForm1 ' Initialize handler runs here
Set .WorkingCell = Target
.Show ' Activate handler runs here
End With
The WorkingCell belongs to the form - it has no business being in global scope.
Careful with the Initialize handler in forms - especially when you use its default instance (i.e. when you don't New it up): you don't control when that handler runs, the VBA runtime does; UserForm_Initialize will run the first time the form instance is referenced (in your case, immediately before the .Show call), and then never again unless the instance is destroyed (clicking the red X button would do that).
A MsgBox call in the Initialize handler will run before the form is shown; you probably want to move that code to the Activate handler before it causes problems.

Change Worksheet When a Cell Value and a Checkbox Change

I have a Workbook with multiple Sheets. I have a menu page (Worksheet) with multiple user choices (Enter a new order, update an order, etc.) Each choice has a check box beside it and depending on which check box is checked, cells F4:F21 change from 0 to 1 and, cell B1 changes to the name of the Worksheet where I want to go. I have the following VBA in the Main Menu worksheet but when I click a check box, nothing happens. Any ideas why?
CODE
Private Sub Worksheet_Activate()
ClearMenuForm
End Sub
Private Sub Worksheet_Change (ByVal Target As Range)
Dim sh As String
If Not Intersect(Target, Range("F4:F21")) Is Nothing Then
sh = Cells(1, "B").Value
Sheets(sh).Select
End If
End Sub
Clicking a check box does not activate the event Worksheet_Change (see this). That is why nothing happens.
Try changing one of the cells instead to see the effect.
What I think you want to do is assign an action to your Checkbox(es). You can do this in two ways:
Right clicking on the checkbox, and Assign Macro...
You have to create the associated macro, which will likely contain parts of the code that you already wrote, and/or calls to subs you have. You may bring the VBE (Alt+F11), insert a module in your VBA project, and write your Sub, e.g.,
Sub CheckBox1_Click()
MsgBox "Checkbox 1a has changed"
End Sub
Via VBA (e.g., this). With the sample code below, you would execute InitCBs, and that would associate CheckBox1Change with the checkbox (it actually assigns actions for both checkboxes in the figure; action for checkbox 2 is CheckBox2Change). You may also set InitCBs to be executed when opening the file.
Sub CheckBox1Change()
MsgBox "Checkbox 1b has changed"
End Sub
Sub InitCBs()
Dim cb As CheckBox
For Each cb In ActiveSheet.CheckBoxes
With cb
Dim action As String
'action = "CheckboxChange"
action = Replace(cb.Name, " ", "") & "Change"
.OnAction = action
End With
Next cb
End Sub
You've defined sh as a String. Since there is no sheet named "1", for example, your code will generate a "Subscript out of Range" runtime error. Try changing sh to a Long.
Dim sh As Long

Pop-Up Error Message (macro) - to update by checkbox selection - Excel 2010

I have a macro which is basically working as I want (alerting the user when two conflicting checkboxes are selected) - code below. The 1 in G2 is the value generated to indicate this case.
The error message fires on SelectionChange, but this appears to be only when another cell is selected by mouse. The worksheet contains a series of checkboxes for the user to select from, and the intention is for the user to only use the checkboxes, never needing to select or input directly into cells. In which case, the error message would never fire even when the scenario described has occurred.
Is there a way of having a msgbox macro trigger by the update of any checkbox on the sheet?
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Range("G2") = 1 Then
MsgBox "ERROR - Select AND Reject checked"
End If
End Sub
Also, I would like to extend the range to apply to all the cells in column G, I just can't seem to get this to work for me. I have seen a few examples citing "G:G" but I have so far only got this to work for one cell.
Apologies in advance for any glaring errors, I've used Excel for a while now - but I'm brand new to using VBA.
Mutually exclusive options are usually indicated with option buttons (also known as radio buttons) instead of checkboxes. Is there any reason you're not using option buttons for this task?
As far as calling the same code for all checkboxes, the checkboxes would have to be Form Controls (not ActiveX Controls), and you could assign them to this macro:
Sub CheckBox_Clicked()
Dim chk As CheckBox
Set chk = ActiveSheet.CheckBoxes(Application.Caller)
MsgBox chk.Name
End Sub
And lastly, for your SelectionChange event to monitor an entire column, it would look similar to this:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim ClickedCell As Range
Dim rngClicked As Range
Application.EnableEvents = False
Set rngClicked = Intersect(Columns("G"), Target)
If Not rngClicked Is Nothing Then
For Each ClickedCell In rngClicked.Cells
If ClickedCell.Value = 1 Then MsgBox "ERROR - Select AND Reject checked"
Next ClickedCell
End If
Application.EnableEvents = True
End Sub

Resources