Run a Macro every time sheet is changed - vba

i'm still fairly new to macros, i've got a bit of code i need to run on a sheet every time it gets updated, changed, or whatever.
Here is the code I need to run: How can i do this?
Sub UnMergeFill()
Dim cell As Range, joinedCells As Range
For Each cell In ThisWorkbook.ActiveSheet.UsedRange
If cell.MergeCells Then
Set joinedCells = cell.MergeArea
cell.MergeCells = False
joinedCells.Value = cell.Value
End If
Next
End Sub

You can boost the efficiency of your macro by locating the merged cells to process rather than looping through every cell in the Worksheet.UsedRange property and examining it for the Range.MergeCells Property.
Within the worksheet's conventional Range.Find method, there is an option to look for formatting. On this sub-dialog's Alignment tab, you'll find the option to locate Merged cells.
        
This can be incorporated into your VBA sub procedure using the Range.Find method and the Application object's .FindFormat property.
Your sub procedure using FindFormat:
Sub UnMergeFill(Optional ws As Worksheet)
If ws Is Nothing Then Set ws = ActiveSheet
Dim fndMrg As Range, joinedCells As Range
Application.FindFormat.MergeCells = True
With ws
On Error Resume Next
Set fndMrg = .Cells.Find(What:=vbNullString, SearchFormat:=True)
Do While Not fndMrg Is Nothing
Set joinedCells = fndMrg.MergeArea
fndMrg.MergeCells = False
'fndMrg.UnMerge '???
joinedCells.Value = fndMrg.Value
Set fndMrg = .Cells.Find(What:=vbNullString, SearchFormat:=True)
Loop
End With
Application.FindFormat.MergeCells = False
End Sub
Slightly revised Worksheet_Change event macro with more environment shutdown during processing.
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo bm_Safe_Exit
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.DisplayAlerts = False
Call UnMergeFill(Target.Parent)
bm_Safe_Exit:
Application.DisplayAlerts = True
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
I've opted to specify the worksheet to be processed rather than rely on the ActiveSheet property. There is the possibility that the Worksheet_Change could be initiated by an outside process when it is NOT the active sheet.
In short, opt for bulk operations whenever possible and avoid looping whenever you can. This is not blinding fast but it should be substantially quicker than looping through the cells.

In the code module for that particular worksheet, just add this:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
UnMergeFill
Application.EnableEvents = True
End Sub

Related

VBA - autoupdate filter in excel after entering data

I'm fairly new to VBA and have been trying to get my spreadsheets to do a little more than just pivot tables allow. I've been able to set up some autofilters in excel using VBA, but now I'd like to have the worksheet autofilter after I enter data into a cell. However, neither of the two lines below work after I press enter.
Here are the two various lines of code I've tried:
1
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$M$5" Then
Application.EnableEvents = False
FilterTo1Critera
Application.EnableEvents = True
End If
End Sub
2
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Dim cel As Range
Set ws = ThisWorkbook.Sheets("Sheet3")
If Not Intersect(Target, Range("A3")) Is Nothing Then
For Each cel In Target
Range("A3").Value = "Changed"
Application.EnableEvents = False
If IsEmpty(ws.Range("A")) Then Sheet1.Range("A").Value = 0
Application.EnableEvents = True
Next cel
End If
End Sub
What's the correct approach to take? Also, is there some good classes that I can take to brush up on some of these concepts??
Thanks in advance!

VBA - Range("All cells but a few")

I am trying to clear the contents of all cells on a worksheet apart from a specific range. I have tried to copy the range to the clipboard then to paste it back on again in the same place, however excel being the usual tricky beast - it doesn't want to play ball.
The range I would like to keep the same is AB1:AC5.
Any Suggestions Apprichiated...
(here is my code)
Sub Button21_Click()
Application.ScreenUpdating = False
With Worksheets(2)
.Range("AB1:AC5").Copy
.Cells.ClearContents
.Paste(Destination:=Sheets("Data").Range("AB1"))
End With
Application.ScreenUpdating = True
End Sub
use an array instead:
Sub Button21_Click()
Application.ScreenUpdating = False
Dim oldValues As Variant
With Worksheets(2)
oldValues = .Range("AB1:AC5").Value
.Cells.ClearContents
.Range("AB1:AC5").Value = oldValues
End With
Application.ScreenUpdating = True
End Sub

Copy cells formulas VBA

I did a program in VBA to copy the formulas in each cell in a specific column, I have 30501 points and the program is really slow even to calculate 100 points, there is a better way to do so?
Sub Copyformulas()
Dim i As Integer
Dim cell As Range
Dim referenceRange As Range
Dim a As String
a = "$T$30510"
Set range1= ActiveSheet.Range("A1:A30510")
Set myrange = Range("T16:T30510")
i = 16
Do Until Cells(20, 30510)
With range1
For Each cell In myrange
If cell.HasFormula Then
Cells(i, 35).Value = cell.Address
Cells(i, 36).Value = "'" & CStr(cell.Formula)
i = i + 1
End If
Next
End With
Loop
End Sub
You can use SpecialCells to refine your range. You don't need to use ActiveSheet it is implied.
Set rSource = Range("A16:A30510").SpecialCells(xlCellTypeFormulas)
Sub Copyformulas()
Application.Calculation = xlManual
Application.ScreenUpdating = False
Application.EnableEvents = False
Dim c As Range
Dim rSource As Range
Set rSource = ActiveSheet.Range("A16:A30510").SpecialCells(xlCellTypeFormulas)
For Each c In rSource
c.Offset(0, 34) = c.Address
c.Offset(0, 35) = "'" & c.Formula
Next
Application.Calculation = xlAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Try adding the following:
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.EnableEvents = False
... Your Code ...
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
You may only need the first one, but they are all good practice in using. Also, where are you using the With ... End With statement? I don't see any use of it in the block.
It is good practice to use Option Explicit at the top of the module. And range1 and myrange are not declared.
Application.Calculation
When a worksheet is accessed or a range's precedents has changed, Excel will automatically recalculate the formulas on the worksheet. Since you are looping over 30,000 times, this causes Excel to recalculate each time through the loop and, thus, slows down performance.
Application.ScreenUpdating
This line stops Excel from screen flashes and other things that occur as the macro runs.
Application.EnableEvents
This line turns off events, such as Worksheet_Change, so that the event is not triggered. If it is not turned off then any time a change occurs on the worksheet the code in the change event will run. If you have a Worksheet_SelectionChange event then code will run every time you select a different cell. These events are written in the worksheet or workbook objects located in the project window of the VBE and there are many events to choose from. Here is a very simple illustration. Place the following in the Sheet1 object in the project window:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
MsgBox "Hi!"
End Sub
Now click around on the worksheet. You see it responds to each selection change. Now place the following in a regular module:
Sub TestEnableEvents()
Application.EnableEvents = False
ActiveCell.Offset(1, 0).Select
Application.EnableEvents = True
End Sub
When you run the above code the message box will not be triggered.

Use cell value as range to hide columns

I have a spreadsheet that there is a checkbox the purpose of the checkbox is to hide the name of clients in two adjacent columns. Because the spreadsheet changes from time to time the position of the columns changes thus it is currently P:Q but a year ago it was H:I.
I want to store the 'range' in a cell and reference that from my vba and get that to hide the columns. The checkbox is a simple toggle. I have tried various incarnations without success and my latest effort tells me that I have not se up the range properly. The cel I am using for teh range is F4. The code is currently:
Private Sub CheckBox2_Click()
Dim c As Range
Dim Visy As Integer
Dim My_range As String
'My_range is the range of filled rows stored as a range in cell F4
'Visy stores the state of the checkbox
If CheckBox2.Value = True Then
Visy = 1
Else
Visy = 0
End If
'Stop any use of the spread sheet and set variable initial states
Application.EnableEvents = False
My_range = Sheet9.Cells(4, 6).Value
'Hide the columns
Range(My_range).Hidden = Visy
'Sheet9.colums(My_range).Hidden = True
'Re enable application
On Error GoTo 0
Application.EnableEvents = True
End Sub
This is within a single sheet:
Sub qwerty()
My_range = Cells(4, 6).Value
Range(My_range).EntireColumn.Hidden = True
End Sub
Your Private Sub CheckBox2_Click should be in a worksheet's code sheet. I believe this is the worksheet identified by the Sheet9 worksheet .CodeName property.
A Private Sub in a worksheet codesheet does not have to explicitly reference the .Parent worksheet property on any Range object or
Range.Cells object unless you want to reference another worksheet's cells. These are bound to the cells on the worksheet whose codesheet you are on regardless of the ActiveSheet property.
Private Sub CheckBox2_Click()
Range(Cells(4, "F").Text).EntireColumn.Hidden = CBool(Me.Value)
End Sub
Do not confuse a worksheet's Private Sub behavior with a Private Sub on a module code sheet. A module codesheet should always explicitly reference the parent worksheet (and often the parent workbook) regardless of whether the Sub is Public or Private.
You have to use code in context:
Private Sub CheckBox2_Click()
Dim wsh As Worksheet
Dim sRangeName As String
'context!
Set wsh = ThisWorkbook.Worksheets("TypeNameHere")
sRangeName = wsh.Range("F4")
wsh.Range(sRangeName).EntireColumn.Hidden = CheckBox2.Value
Set wsh = Nothing
End Sub
Thanks to all who responded it helped a lot and put me on the right track. As several of you noted context is important and I was mixing private sub and sub and so had a scope problem when it came to ranges. I also from another source had the suggestion to use a named range rather than read a cell value since the columns were always adjacent. I have published the code below in case it is of value to anyone in the future.
Private Sub CheckBox2_Click()
'Requires ClientNameCol to be set to the range to be hidden
Dim Visy As Boolean
'Stop any use of the spread sheet and set variable initial states
Application.EnableEvents = False
'Check if sheet is to be hidden or not
If Worksheets("Client 16").CheckBox2.Value = True Then
Visy = True
Else
Visy = False
End If
'Hide/unhide the columns
With ThisWorkbook
.Worksheets("Client 16").Range("ClientNameCol").EntireColumn.Hidden = Visy
End With
On Error GoTo 0
Application.EnableEvents = True
End Sub

VBA Trying to make WorkSheet_Change work on multiple sheets but not all

I need to make a Worksheet_Change that checks for the change of values in 2 different cells in 2 different sheets. However I have more than 2 sheets and don't want to use a Workbook_Change so those other sheets are not affected.
My Code works but only checks for the cells in one worksheet but not for the other worksheet. I need to check in both worksheets.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("M9")) Is Nothing Then
Application.EnableEvents = False
Application.ScreenUpdating = False
Call Macro5
Application.EnableEvents = True
Application.ScreenUpdating = True
End If
If Not Intersect(Target, Range("I88")) Is Nothing Then
Application.EnableEvents = False
Application.ScreenUpdating = False
Call Macro6
Application.EnableEvents = True
Application.ScreenUpdating = True
End If
End Sub
Thanks.
In the Workbook's code module you can access events triggered on any Worksheet.
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
Application.EnableEvents = False
Application.ScreenUpdating = False
If (Not Intersect(Target, Range("M9")) Is Nothing) Then
Call Macro5
ElseIf (Not Intersect(Target, Range("M9")) Is Nothing) Then
Call Macro6
End If
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
If you need to know what worksheet the event fired on you can use the ByVal Sh As Object parameter.
If Sh.Name = "Sheet1" then
If you want to access the Sh object's properties using intellisense, cast Sh back from a Object back into a WorkSheet Object
Dim ws as WorkSheet
Set ws = Sh
The Private command restricts your subroutine to the current worksheet, so you're correct in using that command to keep the subroutine from altering all of your sheets.
Like newguy said, the simplest fix is to place the code in each of the worksheet modules you want it to alter, each using the Private command.