I have a VSTO (Visual Studio Tools for Office) workbook with several cached variables. The relevant code for one of these is:
Public Class ThisWorkbook
<Cached()>
Public RetentionPC As Double
Private Sub ThisWorkbook_Startup(sender as Object, e as System.EventArgs) Handles Me.Startup
If Not IsCached(RetentionPC) then
RetentionPC = 0.5
End If
End Sub
End Class
This code runs and the value of the property is set, but it does not persist when the workbook is saved.
What am I missing?
I dont think you will be able to store cached values like that. If you wish to store information I would recommend using custom properties (either CustomProperties on a Worksheet or CustomDocumentProperties on a Workbook, depending on what you need).
Another way would be to store information to User or Application settings. This would store it across multiple Workbooks:
http://msdn.microsoft.com/EN-US/library/ms250653
Related
I have a userform that is used to generate reports.
In case i have to share the userform with someone i share the entire excel sheet.
Is it possible to make the existing user form as an Add-in.so that,once installed it can be accessible through any excel sheet that is opened and not just that particular excel sheet.
Thanks in advance.
Yes, you can, but, you need to do a bit of preparation.
Ensure your addin has a Project name that differs from the name of your workbook. For example, if your user's workbook's Project is called VBAProject, then your add-in's project name must be (and should be named something more appropriate anyway) as something like MyAddin.
So, you have:
Book1.xlsm (Project name = VBAProject), and
MyAddin.xlam (Project name MyAddin)
Steps:
Within Book1/VBAProject, add a reference (Tools..References) to MyAddin.
Within MyAddin, create your UserForm (MyUserForm)
For early-binding, we need to make the form instancing PublicNotCreatable, but the VBE UI doesn't offer that property for forms, so we need to export the form to a file folder, then edit the MyUserForm.frm file, changing the Attribute VB_Exposed attribute to True (by default it's False). That is, in a text editor, edit the exported file named MyUserForm.frm and adjust the existing line as follows:
Attribute VB_Exposed = True
Save the file changes, (delete the original form in MyAddin) and then Import the MyUserForm.frm into the project. The new user form will have PublicNotCreatable instancing.
Add a public factory function to MyAddin, that will create a new instance of the form, and return it to any VBA that calls it:
Public Function GetUserForm() As MyUserForm
Set GetUserForm = New MyUserForm
End Function
In Book1.xlsm, you can now write code like the following, along with full early-binding support.
Public Sub test()
Dim frm As MyAddin.MyUserForm
Set frm = MyAddin.GetUserForm()
frm.Show
End Sub
Yes, you can share the userform.
Right click on the userform and Export it.
And you can add the form by Importing it.
Thanks
I am trying to create custom properties in a worksheet that the user can’t see. My reasons are the following:
avoid the CustomProperties collection (no direct access),
avoid using global variables (get dropped randomly on workbook save), or
using specific named cells on the worksheet as retrieving and inserting the values seem slow.
The code below seems to work, and I can work with all needed worksheet properties and methods of the created sht object. The downside is the loss of intellisense, which is not a problem for me.
Although this seems to work, my code now appears to randomly go unstable, and I can’t find any direct cause, other than the fact that I may have violated something.
I currently have 7 custom properties each in 5 worksheets.
Here is a code sample in the worksheet (at top):
Private pPassword As String
Public Property Get Password() As String
Password = pPassword
End Property
Public Property Let Password(Value As String)
pPassword = Value
End Property
And here is a code sample in a module:
Sub doSomething(currentSheetName as string)
Dim Password as String
Dim sht as object
Set sht = ThisWorkbook.Worksheets(currentSheetName)
Blahblahblah code
Password = sht.password
Blahblahblah code
End sub
Based on the suggestion from iled, I decided to create a blank worksheet, rename it to shtProperties, and add custom properties and methods to it. I can now use intellisense and change/retrieve properities from anywhere in the project. (example: shtProperties.Password = myPassword). When each sheet is activated, I simply clear out the custom properties of shtProperties, and reset to new properties of the activesheet. No more crashes. Thanks iled for the suggestion.
This question already has answers here:
Excel VBA - QueryTable AfterRefresh function not being called after Refresh completes
(2 answers)
Closed 5 years ago.
I need to create an event to apply certain code after data connections (text files) have refreshed. How do i control when the data is refreshed.
Turn off background refresh on your connections before you do anything else. Then use ActiveWorkbook.RefreshAll any code placed after will not execute till after the refresh, as long as background refresh is off for all connections.
ActiveWorkbook.RefreshAll
other vba code here
How do i control when the data is refreshed.
Open the Connections menu, and then select the connection, and view/edit its Properties:
I need to create an event to apply certain code after data connections (text files) have refreshed.
I interpreted this literally, as you need an event. Fortunately, it's possible to do this. It's not a built-in event like Worksheet_Change but it's still something that can be done with VBA.
If you create a class object then a QueryTable object can be configured WithEvents, and there are two events which you can trap: AfterRefresh and BeforeRefresh. Sounds like you need the AfterRefresh event.
How to add Event Handler for QueryTable
Create a Class module named clsEvents_QueryTable (or you can name it something else, just be consistent in the rest of the code). Put this code, which will allow you to establish an event-handler for a QueryTable object, and the two events' procedures.
Option Explicit
Public WithEvents cQT As Excel.QueryTable
Private Sub Class_Initialize()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(1) '## Modify as needed
Set cQT = ws.QueryTables.Item(1) '## Modify as needed
End Sub
Private Sub cQT_AfterRefresh(ByVal Success As Boolean)
'###
' Code placed in, or called *from* this procedrure will run AFTER refresh
MsgBox Me.cQT.Name & " After refreshing..."
End Sub
Private Sub cQT_BeforeRefresh(Cancel As Boolean)
'###
' Code placed in, or called *from* this procedrure will run BEFORE refresh
MsgBox Me.cQT.Name & " Before refreshing..."
End Sub
Put this in the top of a standard module:
Public QT As clsEvents_QueryTable
In your ThisWorkbook module, do this:
Option Explicit
Private Sub Workbook_Open()
If QT Is Nothing Then
Set QT = New clsEvents_QueryTable
End If
End Sub
(You could do that in some other module, but this is just an example).
Now the table has the two event-handlers, and any time the QueryTable is refreshed, it will automatically invoke the code which is included or called from the event handler(s).
You can extend this to handle multiple QueryTables with some modification (using a collection of object, instead, etc.).
As a spreadsheet developer, I am trying to stitch together two sets of rows: one from a web query to a web service I own, and the other a set of manual rows added by the spreadsheet's user (not me).
Excel's built in Web Query / Connections object only provides two modes: I can turn on "Enable background refresh" which makes the web query asynchronous, or uncheck it.
With it unchecked, Excel freezes up while the query executes, which is undesireable. With it checked, there doesn't seem to be any kind of callback or event hook available to be notified, so that I can operate against the refreshed web data.
Is there another way to do this?
An Excel web query utilizes an object called a QueryTable to carry out the business of retrieving and displaying the data.
A QueryTable can be accessed by VBA.
And just like the chart object a querytable object has events that can only be responded to by using the WithEvents keyword from a class module, like so:
Private WithEvents MyQueryTable As QueryTable
Private Sub MyQueryTable_AfterRefresh(ByVal Success As Boolean)
'Do your post processing here...
End Sub
Excel supports the ability to open a URL as another Excel workbook, via the Workbooks.Open method:
From MSDN:
Sub OpenUSDRatesPage()
Dim objBK As Workbook
Dim objRng As Range
'Open the page as a workbook.
Set objBK = Workbooks.Open("http://www.x-rates.com/tables/USD.HTML")
'Find the Canadian Dollar cell.
Set objRng = objBK.Worksheets(1).Cells.Find("Canadian Dollar")
'Retrieve the exchange rate.
MsgBox "The CAD/USD exchange rate is " & objRng.Offset(-6, -1).Value
End Sub
The call is synchronous, so you can operate on the resulting data in the new workbook immediately after the Open call.
While the workbook is loading, Excel will display a progress bar. When you're done, you can call .Close to close the web data workbook. (e.g., for the MSDN example, you'd call objBK.Close when you're done.)
The caveats of using this approach:
You're on the hook to migrate the data from the web workbook to your own (ThisWorkbook) yourself, unlike a refreshable Excel Web Query that has a set destination.
If your web endpoint has a document name that matches the name of a document open in Excel, the user will get a warning that a document with the same name is open.
I have a workbook that is the main template for our group. Within this template I have several tabs that I would like to incorporate macros on for hiding and unhiding of rows based on values. I setup this macro on one tab, tested it out and it worked. However, when I went through the process of what the users will be doing the macro did not carry forward to the new workbook. I do not want to waste time doing the macro on the other sheets if I am unable to have the macro carry forward. Can someone please help as my template is due today?
In case more info is needed on the process of the template....The template opens up with a User form for the individual to enter in specific information and then during the process of the uploading of an information feed it creates a new workbook.
Write your template macro in a code module, then save and right-click the .bas module in the solution explorer, and export the module to a location you'll programmatically load it from later.
Then in another module, write the procedure that creates the workbook. Before saving the new workbook, load the exported module file into the workbook's VBProject.VBComponents collection, like this:
Dim targetWorkbook As Workbook
'...set up target workbook
targetWorkbook.VBProject.VBComponents.Import "module1.bas"
targetWorkbook.SaveAs "workbook.xls"
EDIT
If your macro code needs to listen to Excel's events (and/or worksheet events), you'll need to also add a class module where you're going to do exactly that. A code module will need to instantiate it for the class module's code to run.
Create a new class module and call it something like "clsExcelApp":
Private WithEvents xlApp As Excel.Application
Option Explicit
Private Sub Class_Initialize()
Set xlApp = Application
End Sub
Private Sub Class_Terminate()
Set xlApp = Nothing
End Sub
Then you can write event handlers for xlApp, such as:
Private Sub xlApp_SheetActivate(ByVal Sh As Object)
'do something whenever a worksheet gets activated.
End Sub
Private Sub xlApp_WorkbookBeforeSave(ByVal Wb As Workbook, ByVal SaveAsUI As Boolean, Cancel As Boolean)
'do something before a workbook gets saved.
End Sub
The code module would only need to instantiate it, like this:
private App As New clsExcelApp
Option Explicit
The rest of the module could be macros and/or functions:
Public Sub Macro1()
'do something
End Sub
Public Function Smurf(Range As Excel.Range) As Long
'Smurf the Range and then smurf a Long
End Function
Then save your hard work and export all modules and import them programmatically into the workbook you want to "inject" the functionality into.
Note You should always restrict access to VBProject, but to execute the Excel.Workbook.VBProject.VBComponents.Import(String)
method you'll need it enabled. Just remember to turn the security back
on when you're done - better safe than sorry!