I am trying to change active printer according to worksheet name when click on Quick Print button, however, the App_WorkbookBeforePrint event is triggered twice. Tried App_WorkbookBeforeClose also triggered twice. I have already changed the Error Trapping to Break on All Error, but it seems like no errors have occurred.
ThisWorkbook:
Private XLApp As CExcelEvents
Private Sub Workbook_Open()
Set XLApp = New CExcelEvents
End Sub
Class Modules:
Option Explicit
Private WithEvents App As Application
Private Sub Class_Initialize()
Set App = Application
End Sub
Private Sub App_WorkbookBeforePrint(ByVal Wb As Workbook, Cancel As Boolean)
MsgBox Wb.FullName
assignPrinter
End Sub
Module:
Const printer1 As String = "Bullzip PDF Printer on Ne10:"
Const printer2 As String = "EPSONF7E8B5 (L565 Series) on Ne07:"
Public Sub assignPrinter()
Dim ws As Worksheet
Dim wsn As String
Set ws = ActiveWorkbook.ActiveSheet
wsn = ws.Name
Select Case wsn
Case "FGWIP"
Application.ActivePrinter = printer1
ws.PrintOut
Exit Sub
Case "Rework"
Application.ActivePrinter = printer2
ws.PrintOut
Exit Sub
Case Else
MsgBox "Else case."
Exit Sub
End Select
End Sub
Update:
Use App_SheetActivate instead of App_WorkbookBeforePrint to change active printer and remove ws.Printout as mentioned by Matley
I re-created your modules and the only error I received was in the code Debug.Print assignPrinter in which I replaced with assignPrinter.
The rest of the codes works fine. App_WorkbookBeforePrint did not trigger twice. You can set a breakpoint in App_WorkbookBeforePrint then look into Stack to see which triggers App_WorkbookBeforePrint for the second time.
Related
I'd like to get an Excel macro running as soon as cell "A500"
becomes visible on the screen when scrolling down/up the worksheet.
I remember reading somewhere about an active-x or standard control
that has an "on scrolling into view" event, so this could be done
by placing a control directly on the worksheet near the desired cell.
Finding this control currently eludes me.
A better way of course would be a cell formula, subclassing still is
a bad idea in the long run i guess :)
Sub temp_01() 'Excel Vba
'user scrolls down from cell "A1"
'when the user reaches cell "A500" show the following message:
MsgBox "Chapter 2"
End Sub
As mentioned above, with the help of the Onupdate event, (catches the mousewheel, not clicking on the scrollbars) (Change Sheetname(s) and Range(s) to yours)
In Class called ClsMonitorOnupdate:
Option Explicit
Private WithEvents objCommandBars As Office.CommandBars
Private rMonitor As Range
Private scrol As Boolean
Public Property Set Range(ByRef r As Range): Set rMonitor = r: End Property
Public Property Get Range() As Range: Set Range = rMonitor: End Property
Private Sub Class_Initialize()
Set objCommandBars = Application.CommandBars
End Sub
Private Sub Class_Terminate()
Set objCommandBars = Nothing
End Sub
Private Sub objCommandBars_OnUpdate()
Dim myrng As Range
If ActiveWorkbook.Name <> ThisWorkbook.Name Then Exit Sub
If ActiveSheet.Name <> rMonitor.Parent.Name Then Exit Sub
If TypeName(Selection) <> "Range" Then Exit Sub
If Intersect(Selection, rMonitor) Is Nothing Then Exit Sub
Set myrng = Application.Intersect(ActiveWindow.VisibleRange, ActiveSheet.Range("a500"))
If Not myrng Is Nothing And Not scrol Then scrol = True: MsgBox "chapter"
If myrng Is Nothing And scrol Then scrol = False
End Sub
In the ThisWorkbook section:
Option Explicit
Private sRanges As String
Private cMonitor As ClsMonitorOnupdate
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Set cMonitor = Nothing
End Sub
Private Sub Workbook_Open()
Zetaan ActiveSheet
End Sub
Sub Zetuit()
Set cMonitor = Nothing
End Sub
Sub Zetaan(sht As Worksheet)
Select Case sht.Name
Case "Sheet1": sRanges = "A1:ZZ1000"
Case "Other Sheet": sRanges = "A1:ZZ1000"
Case Else: Exit Sub
End Select
Set cMonitor = New ClsMonitorOnupdate
Set cMonitor.Range = Sheets(sht.Name).Range(sRanges)
End Sub
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Zetaan Sh
End Sub
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
Set cMonitor = Nothing
End Sub
I want to apply the error handling mechanism in Excel VBA, I want to catch this "runtime error 9", but it's not working.
I am using this userform_initialize() method/sub over and over again, each time I don't want to open this "SAMPLE UPDATE FILE.xlsm" workbook instead, I want to check if it's already open. if yes, then switch to that window or open that workbook.
I have tried on error resume next statement as well but still, it breaks on switching to window "Windows("SAMPLE UPDATE FILE.xlsm "). Select"
Private Sub UserForm_Initialize()
Application.DisplayAlerts = False
On Error GoTo OPEN_WB_ERR
Windows("SAMPLE UPDATE FILE.xlsm").Select
UserForm1.ComboBox1.RowSource = ("'X:\SAMPLE UPDATE FILE.xlsm'!SEARCH")
Windows("PROFORMA_INVOICE.xlsm").Activate
On Error GoTo 0
Exit Sub
OPEN_WB_ERR:
Workbooks.Open Filename:="X:\SAMPLE UPDATE FILE.xlsm"
UserForm1.ComboBox1.RowSource = ("'X:\SAMPLE UPDATE FILE.xlsm'!SEARCH")
Windows("PROFORMA_INVOICE.xlsm").Activate
Resume Next
End Sub
any advice will be helpful...
Check your setting in the VB editor (Tools >> Options >> General tab >> Error Trapping) for how errors are handled - if you have "Break on all errors" selected then it will always break regardless of any error handling you have set. "Break in Class module" is a good option.
Try,
Private Sub UserForm_Initialize()
Dim path As String, Fn As String
Dim Wb As Workbook
Fn = "X:\SAMPLE UPDATE FILE.xlsm"
Set Wb = Workbooks.Open(Filename:=Fn)
UserForm1.ComboBox1.RowSource = "'" & Fn & "'" & "!SEARCH"
ThisWorkbook.Activate
End Sub
The Initialize event procedure runs when the form is first created, before it is shown. You should open your workbook before creating the form, not as part of that process. Try a procedure like the one below, to be installed in a standard code module.
Sub OpenUserForm()
Dim MyForm As UserForm1
' open your workbook here
Set MyForm = New UserForm1 ' this fires the Initialize event
UserForm1.Show
' the code below runs when MyForm is closed
Unload MyForm
Set MyForm = Nothing
End Sub
Note that a form by the name of UserForm1 must exist. I recommend to give it another, more descriptive name. If you do that whatever name you give is the one to use in the Dim statement declaring MyForm.
I use a WorkbookIsOpen function
Public function WorkbookIsOpen(byval strFile as string) as Boolean
Dim wbkCurr as excel.workbook
WorkbookIsOpen = false
For each wbkCurr in application.Workbooks
If wbkCurr.name = strfile then
WorkbookIsOpen = true
Exit for
Endif
Next wbkCurr
End function
Pass just the file name and extension ie myworkbook.xlsx
Then I just adjust my logic accordingly
Ultimately, I would like to run a macro after anyone refreshes the workbook, specifically using the Refresh button under the Data tab in Excel.
For the time being, I would be satisfied just getting the BeforeRefresh or AfterRefresh QueryTable events to fire upon pressing the Refresh button.
In addition to the documentation on the Microsoft Dev Center website, the relevant posts I have read include:
Excel VBA - QueryTable AfterRefresh function not being called after Refresh completes
VBA For Excel AfterRefresh Event
There are other posts but I lack the reputation to post them.
Here is what I have:
Under Class Modules (qtclass)
Option Explicit
Private WithEvents qt As Excel.QueryTable
Private Sub qt_AfterRefresh(ByVal Success As Boolean)
MsgBox "qt_AfterRefresh called sucessfully."
If Success = True Then
Call Module2.SlicePivTbl
MsgBox "If called succesfully."
End If
End Sub
Private Sub qt_BeforeRefresh(Cancel As Boolean)
MsgBox "qt_BeforeRefresh called."
End Sub
Under the ThisWorkbook module
Private Sub Workbook_Open()
Dim qtevent As qtclass
Dim qt As QueryTable
Set qt = ThisWorkbook.Worksheets("Data-Fund").ListObjects(1).QueryTable
Set qtevent = New qtclass
End Sub
I have tried variations of the second code block under specific worksheets as well, but have yet to find anything that works. Do I need to somehow dim the QueryTable in question in the Worksheet module?
You haven't actually connected the querytable to the class instance.
Revised qtclass
Option Explicit
Private WithEvents qt As Excel.QueryTable
Public Property Set HookedTable(q As Excel.QueryTable)
Set qt = q
End Property
Private Sub qt_AfterRefresh(ByVal Success As Boolean)
MsgBox "qt_AfterRefresh called sucessfully."
If Success = True Then
Call Module2.SlicePivTbl
MsgBox "If called succesfully."
End If
End Sub
Private Sub qt_BeforeRefresh(Cancel As Boolean)
MsgBox "qt_BeforeRefresh called."
End Sub
New ThisWorkbook code:
Dim qtevent As qtclass
Private Sub Workbook_Open()
Set qtevent = New qtclass
Set qtevent.HookedTable = ThisWorkbook.Worksheets("Data-Fund").ListObjects(1).QueryTable
End Sub
Note that this is quite closely coupled. It would be more re-usable if you were to raise events in the class and declare your qtevent variable WithEvents.
Source: https://www.excelandaccess.com/create-beforeafter-query-update-events/
Class:
Note: Class Name = clsQuery
Option Explicit
Public WithEvents MyQuery As QueryTable
Private Sub MyQuery_AfterRefresh(ByVal Success As Boolean)
If Success Then
Debug.Print "After ReFresh"
End If
End Sub
Private Sub MyQuery_BeforeRefresh(Cancel As Boolean)
Debug.Print "Before ReFresh"
End Sub
Module:
Option Explicit
Dim colQueries As New Collection
Sub InitializeQueries()
Dim clsQ As clsQuery
Dim WS As Worksheet
Dim QT As QueryTable
For Each WS In ThisWorkbook.Worksheets
For Each QT In WS.QueryTables
Set clsQ = New clsQuery
Set clsQ.MyQuery = QT
colQueries.Add clsQ
Next QT
Next WS
End Sub
ThisWorkbook.Event:
Private Sub Workbook_Open()
Call InitializeQueries
End Sub
How can the following conditions be met with VBA code?
A particular worksheet is always displayed on open, even if the worbook is opened without enabling macros.
A workbook user may save the workbook while working on any worksheet.
The save must not interfere with the user - no navigating away to a different sheet, no messageboxes, etc.
The regular save functions (Ctrl-S, clicking Save) must remain available and when used must obey the criteria above.
I'd like to avoid the attempted solutions I've listed at the bottom of this question.
Details:
The workbook is created using Office 2007 on a Windows 7 machine. It is an .xlsm workbook with 2 worksheets, "Scheduler" and "Info." Sheet tabs are not visible. Not all users will enabled macros when the workbook is opened.
Upon opening the workbook, a user will only be exposed to one sheet as follows:
"Info" shows up if macros are disabled, and basically tells anyone who opens the workbook that macros need to be enabled for full workbook functionality. If macros are enabled at this point, "Scheduler" is activated.
"Scheduler" is where data is stored and edited, and is automatically shown if macros are enabled. It is not presented to the user when the workbook is opened without macros enabled.
"Info" must show up first thing if the workbook is opened and macros are disabled.
Attempted Solutions (I'm looking for better solutions!):
Placing code in the Workbook.BeforeSave event. This saves with "Info" activated so it shows up when the workbook is opened. However, if the user is in "Scheduler" and not done, I cannot find a way in this event to re-activate "Scheduler" after the save.
Using Application.OnKey to remap the Ctrl-s and Ctrl-S keystrokes. Unfortunately this leaves out the user who saves using the mouse (clicking File...Save or Office Button...Save).
Checking during every action and if needed activating "Scheduler". In other words, inserting code in something like the Workbook.SheetActivate or .SheetChange events to put "Scheduler" back into focus after a save with "Info" activated. This runs VBA code constantly and strikes me as a good way to get the other code in the workbook into trouble.
Placing code in the Worksheet("Info").Activate event, to change focus back to "Scheduler". This leads to the result of "Scheduler", not "Info", showing when the workbook is opened, even with macros disabled.
Will this not work? Updated to handle Saving gracefully
Private Sub Workbook_Open()
ThisWorkbook.Worksheets("Scheduler").Activate
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
ThisWorkbook.Worksheets("Info").Activate
If (ShouldSaveBeforeClose()) Then
Me.Save
Else
Me.Saved = True ' Prevents Excel Save prompt.
End If
End Sub
Private Function ShouldSaveBeforeClose() As Boolean
Dim workbookDirty As Boolean
workbookDirty = (Not Me.Saved)
If (Not workbookDirty) Then
ShouldSaveBeforeClose= False
Exit Function
End If
Dim response As Integer
response = MsgBox("Save changes to WorkBook?", vbYesNo, "Attention")
ShouldSaveBeforeClose= (response = VbMsgBoxResult.vbYes)
End Function
I don't have time to test this out, but you might be able to do this using Application.OnTime in your BeforeSave event handler. Something like:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim objActiveSheet
Set objActiveSheet = Me.ActiveSheet
If objActiveSheet Is InfoSheet Then Exit Sub
If Module1.PreviousSheet Is Nothing Then
Set Module1.PreviousSheet = objActiveSheet
InfoSheet.Activate
Application.OnTime Now, "ActivatePreviousSheet"
End If
End Sub
Then in Module1:
Public PreviousSheet As Worksheet
Public Sub ActivatePreviousSheet()
If Not PreviousSheet Is Nothing Then
PreviousSheet.Activate
Set PreviousSheet = Nothing
End If
End Sub
Edit 2: Here is a re-write that does not utilize AfterSave. You may need to tweak the dialog created from GetSaveAsFilename according to your needs.
This relies on overriding default save behavior and handling the save yourself.
Private actSheet As Worksheet
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Cancel = True
PrepareForSave
manualSave SaveAsUI
AfterSave ThisWorkbook.Saved
End Sub
Private Sub PrepareForSave()
Set actSheet = ThisWorkbook.ActiveSheet
ThisWorkbook.Sheets("Info").Activate
hidesheets
End Sub
Private Sub manualSave(ByVal SaveAsUI As Boolean)
On Error GoTo SaveError 'To catch failed save as
Application.EnableEvents = False
If SaveAsUI Then
If Val(Application.Version) >= 12 Then
sPathname = Application.GetSaveAsFilename(FileFilter:="Excel Files (*.xlsm), *.xlsm")
If sPathname = False Then 'User hit Cancel
GoTo CleanUp
End If
ThisWorkbook.SaveAs Filename:=sPathname, FileFormat:=52
Else
sPathname = Application.GetSaveAsFilename(FileFilter:="Excel Files (*.xls), *.xls")
If sPathname = False Then
GoTo CleanUp
End If
ThisWorkbook.SaveAs Filename:=sPathname, FileFormat:=xlNormal
End If
Else
ThisWorkbook.Save
End If
SaveError:
If Err.Number = 1004 Then
'Cannot access save location
'User clicked no to overwrite
'Or hit cancel
End If
CleanUp:
Application.EnableEvents = True
End Sub
Private Sub AfterSave(ByVal bSaved As Boolean)
showsheets
If actSheet Is Nothing Then
ThisWorkbook.Sheets("Scheduler").Activate
Else
actSheet.Activate
Set actSheet = Nothing
End If
If bSaved Then
ThisWorkbook.Saved = True
End If
End Sub
Private Sub hidesheets()
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Info" Then
ws.Visible = xlVeryHidden
End If
Next
End Sub
Private Sub showsheets()
For Each ws In ThisWorkbook.Worksheets
ws.Visible = True
Next
End Sub
Private Sub Workbook_Open()
AfterSave True
End Sub
The only way to make Info display first without macros enabled is if that is how the workbook was saved. This is most reasonably handled when saving.
Unless I misunderstood your issue, not using BeforeSave seems misguided. Just make sure to use AfterSave as well. Here's an example:
Private actSheet As Worksheet
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
showsheets
actSheet.Activate
Set actSheet = Nothing
Thisworkbook.Saved = true 'To prevent save prompt from appearing
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Set actSheet = ThisWorkbook.activeSheet
ThisWorkbook.Sheets("Info").Activate
hidesheets
End Sub
Private Sub Workbook_Open()
showsheets
ThisWorkbook.Sheets("Scheduler").Activate
End Sub
Private Sub hidesheets()
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Info" Then
ws.Visible = xlVeryHidden
End If
Next
End Sub
Private Sub showsheets()
For Each ws In ThisWorkbook.Worksheets
ws.Visible = True
Next
End Sub
The use of the private object actSheet allows the "ActiveSheet" to be reselected after save.
Edit: I noticed you had more requirements in the comments. The code has been updated so that now upon saving, only the Info sheet will be visible, but when opened or after saving, every sheet will reappear.
This makes it so that any user opening the file without macros will not be able to save with a different sheet activated, or even view the other sheets. That would certainly help motivate them to enable macros!
This problem has been flogged to death in the past, its just hard to find a solution that actually works. Take a look at this code which should do what you need. Basically it shows a splash screen, with all other sheets hidden if the user does not enable macros. It will still save normally if the user clicks save and wont interfere with their work. If they save with there worksheet open it will still show only the splash screen when next opened. Download the sample file below and you can test for yourself, make sure you download the file posted by Reafidy it has over 400 views. If you need it modified further let me know.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
bIsClosing = True
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim wsArray() As Variant
Dim iCnt As Integer
Application.ScreenUpdating = 0
Splash.Visible = True
For Each wsSht In ThisWorkbook.Worksheets
If Not wsSht.CodeName = "Splash" Then
If wsSht.Visible = True Then
iCnt = iCnt + 1: Redim Preserve wsArray(1 To iCnt)
wsArray(iCnt) = wsSht.Name
End If
wsSht.Visible = xlSheetVeryHidden
End If
Next
Application.EnableEvents = 0
ThisWorkbook.Save
Application.EnableEvents = 1
If Not bIsClosing Then
For iCnt = 1 To UBound(wsArray)
Worksheets(wsArray(iCnt)).Visible = True
Next iCnt
Splash.Visible = False
Cancel = True
End If
Application.ScreenUpdating = 1
End Sub
Private Sub Workbook_Open()
Dim wsSht As Worksheet
For Each wsSht In ThisWorkbook.Worksheets
wsSht.Visible = xlSheetVisible
Next wsSht
Splash.Visible = xlSheetVeryHidden
bIsClosing = False
End Sub
A sample file can be found here.
How about using a 'proxy workbook'.
The 'proxy workbook'
is the only workbook which is directly opened by the users
contains the info sheet
contains VBA to open your 'real workbook' using Workbooks.Open (As I've checked with Workbooks.Open documentation by default it will not add the file name to your recent files history unless you set the AddToMru argument to true)
if required the VBA code could even make sure that your 'target workbook' is trusted (I found some sample code here)
The 'target workbook'
contains your Schedule and any other sheets
is only opened if the VBA code in 'proxy workbook' was executed
can be saved by the user at any time as usual
I've got no Office 2007 at hand to test this but think it should do.
I've made a couple of macros that run through right click menu button based on the cell value. Typically, if I right click on cell with value 'XYZ', the menu button shows as 'Run macro for XYZ' and then does a bunch of operations: show a couple of user forms, run an SQL query, show and format result data.
On the original .xlsm file, on 'Thisworkbook' I have the following code:
Public WithEvents mxlApp As Application
Public WithEvents mxlSh As Worksheet
Private Sub mxlApp_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As
Boolean)
... (do stuff here) ...
End Sub
...
Private Sub Workbook_Open()
Call AutoExec
End Sub
...
On a separate module, I have the following function used to set my event handler
Public Sub AutoExec()
Set mxlApp = Application
Set ColectionOfMxlEventHandlers = New Collection
ColectionOfMxlEventHandlers.Add mxlApp
Debug.Print ThisWorkbook.Name & " Initialized"
End Sub
The problem: on the original .xlsm file, the code works fine: every time I right-click on a cell which meets certain criteria, I get the 'Run macro for XYZ' and all is fine.
Once I save the file as .xlam and load it as addin, the code won't work.
I have been looking everywhere on the internet and here and couldn't figure out how to resolve this issue.
EDIT:
After modifying the code as kindly suggested by creamyegg, this is what I have:
In class module clsAppEvents:
Private WithEvents mxlApp As Excel.Application
Private Sub Class_Initialize()
Set mxlApp = Excel.Application
End Sub
Private Sub mxlApp_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
Dim cBut As CommandBarButton
On Error Resume Next
Call CleanMenu
If Len(Target.Value) = 8 Then
MyId = Target.Value
With Application
Set cBut = .CommandBars("Cell").Controls.Add(Temporary:=True)
End With
With cBut
.Caption = "Run SQL Query for " & MyId
.Style = msoButtonCaption
.FaceId = 2554
.OnAction = "CallGenericQuery"
End With
End If
With Application
Set cBut = .CommandBars("Cell").Controls.Add(Temporary:=True)
End With
With cBut
.Caption = "Columns_Select"
.Style = msoButtonCaption
.FaceId = 255
.OnAction = "CallShowHide"
End With
On Error GoTo 0
End Sub
in Thisworkbook class I have
Public m_objMe As clsAppEvents
Private Sub Workbook_Open()
Set m_objMe = New clsAppEvents
Debug.Print ThisWorkbook.Name & " Initialized"
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
On Error Resume Next
Call CleanMenu
On Error GoTo 0
Set m_objMe = Nothing
End Sub
Private Sub Workbook_Deactivate()
Call CleanMenu
End Sub
MyId is defined as a public string in the main module containing the CallShowHide and callGenericQuery subs
The issue sounds like your WithEvents is still in your ThisWorkbook Class? What you need to do is create a new class and then instantiate an instance of this on the Workbook_Open() event of your add-in. For example:
New Class (clsAppEvents):
Private WithEvents mxlApp As Excel.Application
Private Sub Class_Initialize()
Set mxlApp = Excel.Application
End Sub
Private Sub mxlApp_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
...
End Sub
Add-in ThisWorkbook Class:
Private m_objMe As clsAppEvents
Private Sub Workbook_Open()
Set m_objMe = New clsAppEvents
End Sub
Private Sub WorkbookBeforeClose(Cancel As Boolean)
Set m_objMe = Nothing
End Sub