Why is SelectionChange event at the application level not working? - vba

I have created a little macro to return the number of unique values and display it in the status bar of Excel when a range is selected. This works fine at the document level. However the SelectionChange event is not launching when I attempt to run it at the application level. The following is what I have.
Class Module 'ExcelEventCapture'
Option Explicit
Public WithEvents ExcelApp As Application
Private Sub ExcelApp_SelectionChange(ByVal Target As Range)
If TypeName(Target) = "Range" Then
Application.StatusBar = "Unique Count: " & CountUnique(Target)
End If
End Sub
Private Function CountUnique(rng As Range) As Long
Dim dict As Dictionary
Dim cell As Range
Set dict = New Dictionary
For Each cell In rng.Cells
If cell.Value2 <> 0 Then
If Not dict.Exists(cell.Value) Then
dict.Add cell.Value, 0
End If
End If
Next
CountUnique = dict.Count
End Function
ThisWorkbook
Option Explicit
Dim myobject As New ExcelEventCapture
Sub Workbook_Open()
Set myobject.ExcelApp = Application
End Sub
What am I missing? Thanks

SelectionChange event does not exist in Application class.
You can use SheetSelectionChange event and there is no need to check Target class name.
Private Sub ExcelApp_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
Application.StatusBar = "Unique Count: " & CountUnique(Target)
End Sub

Related

Excel VBA Worksheet Change Event assigned

Using Excel 2007, I understand that I can create worksheet_change event on the worksheet it's created.
But how do I assign a global sub change events to a newly created worksheet?
e.g.
Public Sub DataChange(ByVal Target As Range)
' this will check and see if the user or operator has change the column field
' if they fill in "X", mark the whole row to red color
' otherwise leave it black
Dim KeyCells As Range
Dim LastRow As Long
LastRow = Cells(Rows.Count, 1).END(xlUp).Row
Set KeyCells = Range("L2:L" & LastRow)
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
If Target.Value = "X" Or Target.Value = "x" Then
Target.EntireRow.Font.color = vbRed
Else
Target.EntireRow.Font.color = vbBlack
End If
End If
End Sub
Then in a separate sub procedure in Module1...
Public Sub CreateWorkSheet()
Dim ws As Worksheet
Set ws = Sheets.Add
ws.Name = "Test1"
' Here where I want to set the event but I do not know the syntax
' ws.OnChange = DataChange
Debug.Print "Done"
End Sub
I'm used to assign events on the fly when creating controls (C#/WPF/Pascal), so I figured there would be one in Excel world. Any advice or help would be greatly appreciated.
As mentioned by Jeeped, probably the easiest way would be to copy the sheet that already had the Private Sub Worksheet_Change code behind it, but there is also another way, if you place the following code under ThisWorkbook, whenever a new sheet is created it will add the desired code behind it:
Private Sub Workbook_NewSheet(ByVal Sh As Object)
Dim NewSheet As Worksheet
Set NewSheet = Sheets(ActiveSheet.Name)
Code = "Private Sub Worksheet_Change(ByVal Target As Range)" & vbCrLf
Code = Code & "MsgBox ""your code here""" & vbCrLf
Code = Code & "End Sub"
With ThisWorkbook.VBProject.VBComponents(NewSheet.Name).CodeModule
NextLine = .CountOfLines + 1
.InsertLines NextLine, Code
End With
End Sub
The drawback here is that the Trust Settings for Macros would need to be changed by clicking on the Trust access to the VBA project object model:
EDIT:
You could also copy code from one worksheet to another using a similar method:
Sub test()
Dim CodeCopy As VBIDE.CodeModule
Dim CodePaste As VBIDE.CodeModule
Dim numLines As Long
Set CodeCopy = ActiveWorkbook.VBProject.VBComponents("Sheet1").CodeModule
Set CodePaste = ActiveWorkbook.VBProject.VBComponents("Sheet2").CodeModule
numLines = CodeCopy.CountOfLines
'Use this line to erase all code that might already be in sheet2
'If CodePaste.CountOfLines > 1 Then CodePaste.DeleteLines 1, CodePaste.CountOfLines
CodePaste.AddFromString CodeCopy.Lines(1, numLines)
End Sub
I'd go for the last #Jeeped's suggestion
place this code in ThisWorkbook code pane
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
DataChange Target ' this sub will be called at any change of any worksheet passing the chenged range
End Sub
then place this in the same code pane or in any other Module
Public Sub DataChange(ByVal Target As Range)
' this will check and see if the user or operator has change the column field
' if they fill in "X", mark the whole row to red color
' otherwise leave it black
Dim KeyCells As Range
Set KeyCells = Range("L2:L" & Cells(Rows.Count, 1).End(xlUp).Row)
If Not Application.Intersect(KeyCells, Target) Is Nothing Then Target.EntireRow.Font.color = IIf(UCase(Target.Value2) = "X", vbRed, vbBlack)
End Sub

excel on change not working if cell value is changed by another module

i have a range whose value is changed realtime but the onchange module does nothing if value is changed by other module for that range. however if i change value manually it works.
code :-
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Dim lastRow As Long
Dim cell As Range
If Not Intersect(Target, Range("J10:J43")) Is Nothing Then
Application.EnableEvents = False
For Each cell In Target
If cell.Value < cell.Offset(0, 4).Value Then
cell.Offset(0, 7).Value = cell.Offset(0, 1).Value
'Module1.OnGenOrder
End If
Next cell
End If
Application.EnableEvents = True
End Sub
NOTE:- i think module Private Sub Worksheet_Change(ByVal Target As Range)
is not able to sense changes. The value is changed by a module in another external .xla file. but a change by simple formulas like =a1+b1 works well
update
this is code of cell to monitor
=c:\Excelmacros\updateprice.xla!dataupdate($H12,"price1")
Event handler procedures have a simple naming convention:
Private Sub [EventSource]_[EventName]([args])
Seeing how the event source is Worksheet, it looks like your handler is in some worksheet's code-behind module; that will only ever respond to Change events on that worksheet.
If you want to handle Change events on any worksheet in ThisWorkbook, then handle the SheetChange event of the Workbook class, in the code-behind module for ThisWorkbook:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
End Sub
Notice how the changed sheet is being received as a parameter.
If you want to handle Change worksheet events on any worksheet in another workbook, then you need a class module and a WithEvents field - the ThisWorkbook code-behind can serve (a workbook is a class, after all), for simplicity's sake:
Private WithEvents app As Excel.Application
You'll need to Set that app event source to a valid Excel.Application object reference as appropriate (say, in the Open handler for ThisWorkbook), and then you can handle application-wide events:
Private Sub Workbook_Open()
Set app = Excel.Application
End Sub
Private Sub app_SheetChange(ByVal Sh As Object, ByVal Target As Range)
MsgBox "Cell " & Target.Address(External:=True) & " was changed."
End Sub

How to get VBA code to run on Multiple Sheets?

I was wondering how to get this code to work on every sheet and new sheets that will be made in the Excel workbook. The new sheet Thank you to everyone that helps.
Dim cmt As Comment
Dim charCount As String
Dim prevTarget As Range
Sub Worksheet_C(ByVal Target As Range)
If Target.Value <> Empty Or Target.Value <> "0" Then
If Target.Value <> Empty Then
Set prevTarget = Target
Set Target = Target
End If
Set cmt = prevTarget.Comment
If Target = Empty Then
Set prevTarget = Target
End If
If cmt Is Nothing Then
'MsgBox "There is no comment"
ElseIf Len(cmt.Text) > 150 Then
charCount = Len(cmt.Text)
MsgBox "Character Limit is 150. Your comment contains " + charCount + "."
End If
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Call Worksheet_C(Target)
End Sub
The Workbook_SheetSelectionChange() in ThisWorkbook witll execute for all sheets:
In ThisWorkbook module:
Option Explicit
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
RestrictCommentSize Target
End Sub
Module1 (a new generic VBA module) can contain your initial Sub Worksheet_C (cleaned up a bit)
Option Explicit
Public Sub RestrictCommentSize(ByVal Target As Range)
With Target
If .CountLarge = 1 Then
If Len(.Value2) <> 0 And .Value2 <> "0" And Not .Comment Is Nothing Then
If Len(.Comment.Text) > 150 Then
Dim msg As String
msg = "Your comment contains " & Len(.Comment.Text) & " characters"
MsgBox msg & vbCrLf & vbCrLf & "(more than the max of 150)"
End If
End If
End If
End With
End Sub
Worksheet_SelectionChange is a worksheet specific routine.
If new sheets are created manually, you cannot easily control the sheet's VBA content (unless you run a background process to poll the sheets and check if the code exists).
However, if the sheets are created by code, you can add code that will also create the sheet specific VBA code.

Error passing array to listbox as a parameter

Objective : Create a userform and take a user input, and then from user input put it in a list and when you click the list it automatically find it in the whole workbook.
Something Like this:
I saw this post: Match in whole workbook
And I created something out of that:
Public Sub CommandButton3_Click()
Dim TempArray As Variant
Dim RealArray()
TempArray = Application.InputBox("Select a range", "Obtain Range Object", Type:=64)
ArrayRows = UBound(TempArray)
ReDim RealArray(1 To ArrayRows)
For i = 1 To ArrayRows
RealArray(i) = TempArray(i, 1)
Next i
MsgBox "The number if rows selected are " & ArrayRows
ListBox1.List = RealArray
ListBox1 Arraay:=RealArray
End Sub
Public Sub ListBox1_Click(ByRef Arraay() As Variant)
Dim Sh As Worksheet
Dim something As Range
Dim ArrayRows As Long
For Each Sh In ThisWorkbook.Worksheets
With Sh.UsedRange
For i = 1 To ArrayRows
Set something = RealArray.Find(What:=RealArray(i))
If Not something Is Nothing Then
Do Until something Is Nothing
test = something.Value
Set something = .FindNext(something)
Loop
End If
Next i
End With
Set something = Nothing
Next
End Sub
After creating this, I get an error regarding the second sub.
procedure declaration does not match description of event or procedure having the same name
The Listbox click event doesn't take any parameters.
Private Sub ListBox1_Click()
End Sub
If you want to pass an array between sub then you can so it this way
Dim MyArray() As Variant
Public Sub CommandButton3_Click()
'~~> Initialize array
End Sub
Private Sub ListBox1_Click()
'~~> Use array here
'~~> Also put an error check if the array is initialized or not
End Sub

Refresh combo box after list update vba excel

I'm trying to auto update a combobox list. It updates correctly only when I close and then open the workbook, or when I press the stop button on VBA and run the macro again. I have the following VBA code.
Private Sub UserForm_Initialize()
Dim cod As Range
Dim pro As Range
Dim cli As Range
Dim ws As Worksheet
Dim ws5 As Worksheet
Set ws = Worksheets("ListaProductos")
Set ws5 = Worksheets("ListaClientes")
For Each cod In ws.Range("CodigoProductoLista")
With Me.codigo
.AddItem cod.Value
.List(.ListCount - 1, 1) = cod.Offset(0, 1).Value
End With
Next cod
For Each cli In ws5.Range("ClienteLista")
With Me.cliente
.AddItem cli.Value
.List(.ListCount - 1, 1) = cli.Offset(0, 1).Value
End With
Next cli
No.Value = True
calendario2.Visible = False
calendario2.Refresh
calendario = Date
Me.codigo.SetFocus
End Sub
Thanks!
You could call the UserForm_Initialize procedure again, but you will have to clear the lists first. You could use it in a commandbutton, or in an event for instance.
That Initialize event will only trigger when the form is loading. Add a button to your form called cmdRepopulate and use this code instead:
Option Explicit
Private Sub UserForm_Initialize()
PopulateCodigoProductoLista
PopulateClienteLista
FinishingOff
End Sub
Private Sub PopulateCodigoProductoLista()
Dim rngData As Range
With Worksheets("ListaProductos").Range("CodigoProductoLista")
Set rngData = .Resize(.Rows.Count, 2)
End With
PopulateComboUsingRange Me.codigo, rngData
End Sub
Private Sub PopulateClienteLista()
Dim rngData As Range
With Worksheets("ListaClientes").Range("ClienteLista")
Set rngData = .Resize(.Rows.Count, 2)
End With
PopulateComboUsingRange Me.cliente, rngData
End Sub
Private Sub FinishingOff()
No.Value = True
calendario2.Visible = False
calendario2.Refresh
calendario = Date
Me.codigo.SetFocus
End Sub
Private Sub PopulateComboUsingRange(cboDataDestination As MSForms.ComboBox, _
rngDataSource As Range)
Dim lngCounter As Long
With cboDataDestination
.Clear
For lngCounter = 1 To rngDataSource.Rows.Count
.AddItem rngDataSource.Cells(lngCounter, 1)
If rngDataSource.Columns.Count = 2 Then
.List(.ListCount - 1, 1) = rngDataSource.Cells(lngCounter, 2)
End If
Next
End With
End Sub
Private Sub cmdRepopulate_Click()
PopulateCodigoProductoLista
PopulateClienteLista
FinishingOff
End Sub
There are some limitations (because it's too late), notably the generic combo population routine but in its current for it should be good for you. The code will run on the form's Initialize event and whenever you click the repopulate button. I decided to work with your named ranges as they were - no changes.
Note the use of a generic proc to populate the combo. If you had other combos on other forms in this same workbook, you could move that proc to a separate module, change it to Public (rather than Private) and save yourself a lot of typing by reusing the code.