Limit Worksheet Event Macro to Certain Columns - excel

The following code will update cells to 1-5 based on doubleclicks on the cell.
I'm looking to limit this to a few columns in the spreadsheet (e.g. if I doubleclick on A2, nothing should happen).
Clearly the .Columns("B:C") is not in the right spot.
Private Sub Worksheet.Columns("B:C")_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Cancel = True
If Target.Value < 5 Then
Target.Value = Target.Value + 1
Else
Target.Value = 5
End If
End Sub

As mentioned by Scott Craner in the comments, the proper way to handle this is to test if the Target range intersects the columns.
I would also recommend setting Cancel = True. This prevents the cell from going into edit mode.
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Not Intersect(Columns("B:C"), Target) Is Nothing Then
If Target.Value < 5 Then
Target.Value = Target.Value + 1
Else
Target.Value = 5
End If
Cancel = True
End If
End Sub

A Worksheet Before Double-Click: Increment Cell Value
Adjust the values in the event procedure.
Option Explicit
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
' Reference (set) the range (columns).
Dim srg As Range: Set srg = SetColumnsUR(Me, "B2:C2,E2,G2:H2")
' Check if the double-clicked cell ('Target') doesn't intersect.
If Intersect(srg, Target) Is Nothing Then Exit Sub
' Write the integer according to the logic.
Target.Value = MyIntegerLogic(Target.Value, 1, 5)
' Suppress the default behavior of double-clicking a cell.
Cancel = True
End Sub
Function SetColumnsUR( _
ByVal ws As Worksheet, _
ByVal FirstRowAddress As String) _
As Range
With ws.Range(FirstRowAddress)
With .Areas(1).Resize(ws.Rows.Count - .Row + 1)
Set SetColumnsUR = Intersect(.Cells, ws.UsedRange)
End With
If .Areas.Count = 1 Then Exit Function
Set SetColumnsUR = Intersect(SetColumnsUR.EntireRow, .EntireColumn)
End With
End Function
Function MyIntegerLogic( _
ByVal Value As Variant, _
ByVal MinInteger As Long, _
ByVal MaxInteger As Long) _
As Long
Dim Number As Long: Number = MinInteger - 1
If VarType(Value) = vbDouble Then ' is a number
If Int(Value) = Value Then ' is a whole number (integer)
Select Case Value
Case MinInteger To MaxInteger - 1: Number = Value + 1 ' 1.)
Case MaxInteger: Number = MaxInteger ' 2.)
Case Else ' covered below
End Select
End If
End If
If Number = MinInteger - 1 Then Number = MinInteger ' 3.) all other cases
MyIntegerLogic = Number
End Function

Related

Macro Add Texts On Double-Click

I made the following code that adds a text when double-clicking a cell. But i need a routine where if i double-click again in the same cell it will add a different text, if i click a third time it will add another text, and so on. This loop should continue for 6 different texts.
Thanks in advance for any help.
Private Sub Worksheet_BeforeDoubleClick( _
ByVal Target As Range, Cancel As Boolean)
Dim rInt As Range
Dim rCell As Range
Set rInt = Intersect(Target, Range("H2:H2000"))
If Not rInt Is Nothing Then
For Each rCell In rInt
rCell.Value = "Info"
Next
End If
Set rInt = Nothing
Set rCell = Nothing
Cancel = True
End Sub
You'll need to test what the cell already has in it, and add the required word based on that
Private Sub Worksheet_BeforeDoubleClick( _
ByVal Target As Range, Cancel As Boolean)
Dim Words(0 To 5) As String
Words(0) = "1st word"
' etc to fill array
Dim rInt As Range
Set rInt = Intersect(Target, Me.Range("H2:H2000"))
If Not rInt Is Nothing Then
Select Case Target.Value2
Case Words(5)
'Already has last word
Case Words(4)
Target.Value2 = Words(5)
Case 'etc for other Words 3..0
Case Else
Target.Value2 = Words(0)
End Select
Cancel = True
End If
End Sub
Cycle Array Values on Double-Click
Option Explicit
Private Sub Worksheet_BeforeDoubleClick( _
ByVal Target As Range, Cancel As Boolean)
Const fcAddress As String = "H2"
Dim Strings As Variant
Strings = VBA.Array("One", "Two", "Three", "Four", "Five", "Six")
With Me.Range(fcAddress)
With .Resize(Me.Rows.Count - .Row + 1)
If Intersect(.Cells, Target) Is Nothing Then Exit Sub
End With
End With
Cancel = True
Dim IsFound As Boolean
Dim siValue As Variant: siValue = Target.Value
If Not IsError(siValue) Then
If Len(siValue) > 0 Then
Dim siIndex As Variant
siIndex = Application.Match(siValue, Strings, 0)
If IsNumeric(siIndex) Then
If siIndex <= UBound(Strings) Then
IsFound = True
End If
End If
End If
End If
If IsFound Then
Target.Value = Strings(siIndex)
Else
Target.Value = Strings(0)
End If
End Sub

Target cells not triggered by event `Worksheet_Change`. How to fix?

I am using below codes as the following:
Code(1)# Worksheet_SelectionChange Insert Date by using Date Picker(calendar) on sheet "North"
Column M.
Code(2) # Worksheet_Change of sheet North to Log changes of any cells and put in sheet("Log").
Code(3) in a separate module "Calendar" to initiate calendar
the codes works except in one condition
Target cells not triggered by event Worksheet_Change
to produce issue use calendar to enter any value but not click outside Column M then delete these values again , then switch to sheet "Log" you will notice that there are no entries for deleted values at all.
As always: any help will be appreciated.
(Link for the real file found in first comment)
Option Explicit
Option Compare Text
Private Sub worksheet_SelectionChange(ByVal Target As Excel.Range)
If Not Intersect(Target, Range("M3:M100")) Is Nothing Then
Call Basic_Calendar
Else
boolDate = False 'make it false to trigger the previous behavior in Worksheet_Change event
End If
End Sub
Private Sub Worksheet_Change(ByVal Target As Range) 'Log Changes of Current Sheet and put in Sheet("Log")
Dim RangeValues As Variant, r As Long, boolOne As Boolean, TgValue 'the array to keep Target values (before UnDo)
Dim SH As Worksheet: Set SH = Sheets("Log")
Dim UN As String: UN = Application.UserName
If Not Intersect(Target, Range("AK:XFD")) Is Nothing Then Exit Sub 'not doing anything if a cell in AK:XFD is changed
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
If Target.Cells.Count > 1 Then
TgValue = ExtractData(Target)
Else
TgValue = Array(Array(Target.value, Target.Address(0, 0))) 'Put the target range in an array (or as a string for a single cell)
boolOne = True
End If
Application.EnableEvents = False 'Avoide trigger the change event after UnDo
If boolDate Then '____________________________________________________________
Dim prevTarget
prevTarget = Target.value 'memorize the target value
Target.value = PrevVal 'change the target value to the one before changing
RangeValues = ExtractData(Target) 'extract data exactly as before
Target.value = prevTarget 'set the last date
Else '____________________________________________________________
Application.Undo
RangeValues = ExtractData(Target) 'Define RangeValue
PutDataBack TgValue, ActiveSheet 'Put back the changed data
End If
If boolOne Then Target.Offset(1).Select
Application.EnableEvents = True
Dim columnHeader As String, rowHeader As String
For r = 0 To UBound(RangeValues)
If RangeValues(r)(0) <> TgValue(r)(0) Then
columnHeader = Cells(1, Range(RangeValues(r)(1)).Column).value
rowHeader = Range("B" & Range(RangeValues(r)(1)).Row).value
Sheets("Log").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Resize(1, 6).value = _
Array(UN, Now, rowHeader, columnHeader, TgValue(r)(0), RangeValues(r)(0))
End If
Next r
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Sub PutDataBack(arr, SH As Worksheet)
Dim i As Long, arrInt, El
For Each El In arr
SH.Range(El(1)).value = El(0)
Next
End Sub
Function ExtractData(Rng As Range) As Variant
Dim a As Range, arr, Count As Long, i As Long
ReDim arr(Rng.Cells.Count - 1)
For Each a In Rng.Areas 'creating a jagged array containing the values and the cells address
For i = 1 To a.Cells.Count
arr(Count) = Array(a.Cells(i).value, a.Cells(i).Address(0, 0)): Count = Count + 1
Next
Next
ExtractData = arr
End Function
' in a separate module "Calendar" to initiate calendar
Option Explicit
Option Compare Text
Public PrevVal As Variant, boolDate As Boolean
Sub Basic_Calendar()
Dim datevariable As Variant
datevariable = CalendarForm.GetDate
If datevariable <> 0 Then
PrevVal = Selection.value: boolDate = True
Selection.value = datevariable
End If
End Sub
In order to make the solution allowing multiple cells entry from the Callendar, but also allowing multiple deletions, please adapt it in the next way:
Use this modified code in the module where Basic_Calendar Sub exists:
Option Explicit
Option Compare Text
Public PrevVal(), boolDate As Boolean
Sub Basic_Calendar()
Dim datevariable As Variant
datevariable = CalendarForm.GetDate
If datevariable <> 0 Then
PrevVal = Selection.value: boolDate = True
Selection.value = datevariable
Else
Erase PrevVal 'to identify the case of deletion
End If
End Sub
Edited:
If your installation/version is not deal with directly loading the array, please use the next version, which do it by iteration:
Sub Basic_Calendar()
Dim datevariable As Variant
datevariable = CalendarForm.GetDate
If datevariable <> 0 Then
Dim i As Long
ReDim PrevVal(1 To Selection.Rows.Count, 1 To 1)
For i = 1 To Selection.Rows.Count
PrevVal(i, 1) = Selection.Cells(i).value
Next i
boolDate = True
Selection.value = datevariable
Else
Erase PrevVal 'to identify the case of deletion
End If
End Sub
Adapt this part of the Worksheet_Change event code in the next way:
If Target.Cells.Count > 1 Then
If Not CBool(Not Not PrevVal) Then boolDate = False 'the new line checking if the multiple rows array is empty (or not)
TgValue = ExtractData(Target)
Else
TgValue = Array(Array(Target.value, Target.Address(0, 0))) 'Put the target range in an array (or as a string for a single cell)
boolOne = True
End If
The logic of the modification works as following:
a. When the Calendar form is called and it returns a Date, in a multi rows range, the delivered datevariable is dropped in the selected cells, and their previous value are loaded in PrevVal() array;
b. A change in Column "M:M" triggers the event and in case of PrevVal() not empty, it acts as usually for inserting Data (using the PrevVal() array elements instead of UnDo, which does not work for data added by code). In case of an empty array, it makes boolDate = False, switching the code to the clasic variant (able to use UnDo, because deletion has been done by the user)...
No need to check the code on another PC. It was a matter of solution logic starting from a wrong assumption and it cannot work differently than on your laptop.

Conflict between two events if Filtermode = False and any cells changed by Fill handle. Error raised (Method 'Undo' of object 'Application' failed)?

I have two codes depend on application events to run.
Code (1) change color of column_A If FilterMode is True on any column of ActiveSheet.
Code (2) Log changes of any cells in ActiveSheet and put in another sheet("Log").
Error raised if : Filtermode = False and any cells changed by fill handle (the small square in the lower-right corner of the selected cell) ,
I got this error
Method 'Undo' of object '_Application' failed
on this line Application.Undo on Code (2).
I tried to use to disable and enable events with code (1) with no luck.
any help will be appreciated.
Option Compare Text
Private Sub worksheet_SelectionChange(ByVal Target As Excel.Range)
'Code (1) change color of column_A If FilterMode is True on any column of active sheet.
Dim Column_A As Range
Set Column_A = ActiveSheet.Range("A3", ActiveSheet.Range("A" & ActiveSheet.Rows.count).End(xlUp))
If ActiveSheet.FilterMode = True Then
Column_A.Interior.Color = RGB(196, 240, 255)
Else 'FilterMode = False
Column_A.Interior.Color = RGB(255, 255, 255)
End If
End Sub
' Code (2) Log Changes of Current Sheet and put in Sheet("Log")
Private Sub Worksheet_Change(ByVal Target As Range)
Dim RangeValues As Variant, r As Long, boolOne As Boolean, TgValue
Dim sh As Worksheet: Set sh = Sheets("Log")
Dim UN As String: UN = Environ$("username")
If Not Intersect(Target, Range("AK:XFD")) Is Nothing Then Exit Sub 'not doing anything if a cell in "AK:XFD" is changed
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
If Target.Cells.count > 1 Then
TgValue = extractData(Target)
Else
TgValue = Array(Array(Target.value, Target.Address(0, 0))) 'put the target range in an array (or as a string for a single cell)
boolOne = True
End If
Application.EnableEvents = False 'Avoide trigger the change event after UnDo
Application.Undo
RangeValues = extractData(Target) 'Define RangeValue
putDataBack TgValue, ActiveSheet 'Reinsert changed data
If boolOne Then Target.Offset(1).Select
Application.EnableEvents = True
Dim columnHeader As String, rowHeader As String
For r = 0 To UBound(RangeValues)
If RangeValues(r)(0) <> TgValue(r)(0) Then
columnHeader = Cells(1, Range(RangeValues(r)(1)).Column).value
rowHeader = Range("B" & Range(RangeValues(r)(1)).Row).value
Sheets("Log").Range("A" & Rows.count).End(xlUp).Offset(1, 0).Resize(1, 6).value = _
Array(UN, Now, rowHeader, columnHeader, TgValue(r)(0), RangeValues(r)(0))
'Array("User Name", "Date,Time", "Work Order", "Column Label", "New Value", "Old Value")
Range(RangeValues(r)(1)).EntireRow.AutoFit
If Range(RangeValues(r)(1)).RowHeight < 53 Then
Range(RangeValues(r)(1)).RowHeight = 53
End If
End If
Next r
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Sub putDataBack(arr, sh As Worksheet)
Dim i As Long, arrInt, El
For Each El In arr
sh.Range(El(1)).value = El(0)
Next
End Sub
Function extractData(rng As Range) As Variant
Dim a As Range, arr, count As Long, i As Long
ReDim arr(rng.Cells.count - 1)
For Each a In rng.Areas 'creating a jagged array containing the values and the cells address
For i = 1 To a.Cells.count
arr(count) = Array(a.Cells(i).value, a.Cells(i).Address(0, 0)): count = count + 1
Next
Next
extractData = arr
End Function
I figured out the issue, although the error rising with code (2) Worksheet_Change event ,
But actually SelectionChange event on code(1) is the real problem.
Apparently, when I drag down, it is sort of like selecting cells individually and all of them at the same time.
To solve this issue, a condition must be added to event SelectionChange to count the target cells:
If Target.Cells.CountLarge = 1 then
So I just modified the code to look like this in the SelectionChange part and it now works perfectly.
'Code (1)
Private Sub worksheet_SelectionChange(ByVal Target As Excel.Range)
If Target.Cells.CountLarge = 1 Then
Dim Column_A As Range
Set Column_A = ActiveSheet.Range("A3", ActiveSheet.Range("A" & ActiveSheet.Rows.count).End(xlUp))
If ActiveSheet.FilterMode = True Then
Column_A.Interior.Color = RGB(255, 0, 0)
Else 'FilterMode = False
Column_A.Interior.Color = RGB(255, 255, 255)
End If
End If
End Sub
In the meantime, I learned that Calculate event would be best choice to trapping a change to a filtered list as described on this link
https://www.experts-exchange.com/articles/2773/Trapping-a-change-to-a-filtered-list-with-VBA.html

Hiding rows based on cell value become unhidden after another row becomes hidden

I'm trying to hide some rows in another worksheet based on the value of cells in another one and I seem to have the VBA code working to a certain degree but it seems that when I change the value to "No" to hide a certain row and then pick "No" for another cell only the rows associated with the second question are hidden and the first set of rows become visible again. See below for the error in context
Sub Worksheet_Change(ByVal Target As Range)
If (Target.Row = 12) And (Target.Column = 4) And (Target = "No") Then
Worksheets("Additional Procedures").Rows("13:16").EntireRow.Hidden = True
Else
Worksheets("Additional Procedures").Rows("13:16").EntireRow.Hidden = False
End If
If (Target.Row = 13) And (Target.Column = 4) And (Target = "No") Then
Worksheets("Additional Procedures").Rows("17:18").EntireRow.Hidden = True
Else
Worksheets("Additional Procedures").Rows("17:18").EntireRow.Hidden = False
End If
End Sub
So in context, if in the worksheet cell D12 the value is "No" then rows 13:16 get hidden in worksheet "Additional Procedures" but then if I were also to have cell D13's value as "No" then rows 13:16 become visible and rows 17:18 become hidden. I'd like for them for rows 13:18 to stay hidden if both cells D12 and D13 have a value of "No"
I also have this code further down which I wrote to hide rows 32:35 only if the values in cells D21:D23 all have the value of "No". I've included this in case this is what is causing the issue:
Dim Count As Integer
Dim Range As Variant
Count = 0
Range = Worksheets("Risk Assessment").Range("D21:D23")
For Each Cell In Range
If Cell = "No" Then
Count = Count + 1
End If
Next Cell
If Count = 3 Then
Sheets("Additional Procedures").Select
Worksheets("Additional Procedures").Rows("32:35").EntireRow.Hidden = True
Else
Worksheets("Additional Procedures").Rows("32:35").EntireRow.Hidden = False
End If
Thank you in advance for any advice you guys can provide
Hide Rows of a Range
Standard Module (e.g. Module1)
Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Loops through the cells of a given range (SourceRange) and
' checks their values against a given value (SourceValue).
' If the values are the same it hides (otherwise it shows)
' the rows of another given range (HideRows).
' Inputs
' SourceRange The range where the value is going to be searched for.
' SourceValue The value that is searched for.
' HideRows The range whose rows will be hidden or shown.
' findOneOnly By default (False), the values of all cells of SourceRange
' have to be equal to SourceValue. When set to True, only
' one value has to be equal. This has no effect
' if SourceRange contains only one cell.
' Remarks: The comparison is case-sensitive i.e. e.g. A <> a.
' SourceValue is declared as Variant to be able to hold
' different data types.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub HideRows(SourceRange As Range, _
ByVal SourceValue As Variant, _
HideRows As Range, _
Optional ByVal findOneOnly As Boolean = False)
Dim Data As Variant: Data = SourceRange.Value
If Not IsArray(Data) Then GoTo doOne Else GoTo doMulti
doOne:
If Not IsError(Data) Then
If Data = SourceValue Then GoTo doHideRows Else GoTo doShowRows
Else
GoTo doShowRows
End If
doMulti:
Dim i As Long, j As Long
For i = 1 To UBound(Data)
For j = 1 To UBound(Data, 2)
If Not IsError(Data(i, j)) Then
If Data(i, j) = SourceValue Then
If findOneOnly Then GoTo doHideRows
Else
If Not findOneOnly Then GoTo doShowRows
End If
Else
If Not findOneOnly Then GoTo doShowRows
End If
Next j
Next i
If findOneOnly Then GoTo doShowRows Else GoTo doHideRows
doHideRows:
HideRows.EntireRow.Hidden = True
Exit Sub
doShowRows:
HideRows.EntireRow.Hidden = False
Exit Sub
End Sub
Sheet Module (e.g. Sheet1)
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const wsName As String = "Additional Procedures"
Const Criteria As Variant = "No"
Dim CheckAddress As Variant
CheckAddress = Array("D12", "D13", "D21:D23")
Dim HideRowsAddress As Variant
HideRowsAddress = Array("13:16", "17:18", "32:35")
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets(wsName)
Dim j As Long
For j = 0 To UBound(CheckAddress)
If Not Intersect(Target, Range(CheckAddress(j))) Is Nothing Then
HideRows Range(CheckAddress(j)), Criteria, _
ws.Rows(HideRowsAddress(j))
End If
Next j
End Sub
If only one found No in range D21:D23 should be enough to hide rows 32:35, use True:
HideRows Range(CheckAddress(j)), Criteria, _
ws.Rows(HideRowsAddress(j)), True
This code may do what you want. If not, it should give you new impetus on how to structure the IF conditions. Please try it.
Sub Worksheet_Change(ByVal Target As Range)
Dim TgtRows As String
With Target
If .Column = 4 Then
Select Case .Row
Case 12
TgtRows = "13:16"
Case 13
TgtRows = "17:18"
End Select
If Len(TgtRows) Then
' assuming that Target is on Worksheets("Additional Procedures")
' comparison is case insensitive
Rows(TgtRows).EntireRow.Hidden = (StrComp(.Value, "no", vbTextCompare) = 0)
End If
End If
End With
End Sub

Double click to insert character on merged cells

I want to insert or remove a "X" inside cells inside a certain range ("A1:A19"), by double clicking. The code below is placed on the "Microsoft Excel Objects\ThisWorkbook" in the project macro.
Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
If Not Intersect(Target, Range("A1:A19")) Is Nothing Then
If Len(Trim(Target)) = 0 Then
Target.Value = "X"
Cancel = True
ElseIf UCase(Trim(Target)) = "X" Then
Target.ClearContents
Cancel = True
End If
End If
End Sub
This code works for non merged cells. However, I have a situation where the cells must be merged ( 2 by 2, in the column), and in this situation I get the following error:
"Run-time error '13'"
Type mismatch
How must be the code modified to prevent this?
try
Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
If Not Intersect(Target, Range("A1:A19")) Is Nothing Then
If Target.Cells.Count = 1 Then ' handle single cell
If Len(Trim(Target)) = 0 Then
Target.Value = "X"
Cancel = True
ElseIf UCase(Trim(Target)) = "X" Then
Target.ClearContents
Cancel = True
End If
Else ' handle merged
Dim theAddress As String
theAddress = Split(Target.Address, ":")(0) & ":" & Split(Target.Address, ":")(0)
If Len(Trim(Range(theAddress))) = 0 Then
Target.Value = "X"
Cancel = True
ElseIf UCase(Trim(Range(theAddress))) = "X" Then
Target.ClearContents
Cancel = True
End If
End If
End If
End Sub
When your cells are merged, target is returning a range of multiple cells and it is trying to put a value into cells it can't put values into. Try this:
Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
Dim myRange As Range
Set myRange = Target.Cells(1, 1)
If Not Intersect(myRange, Range("A1:A19")) Is Nothing Then
If Len(Trim(myRange)) = 0 Then
myRange.Value = "X"
Cancel = True
ElseIf UCase(Trim(myRange)) = "X" Then
Target.ClearContents
Cancel = True
End If
End If
End Sub
It returns a range reference as being the top left cell in your merged range and allows you to enter values based on that.

Resources