I have hard times trying to update labels in a VBA form based on Worksheet values in Excel.
The methods I've tried so far:
1st Take:
Sub Update_Label_FirstTime()
Label1.Caption = Sheets(1).Cells(1, 1)
End Sub
'Whenever I call the Macro for a second time It fails and VBA freezes my form
2nd Take
Private Sub UserForm_Activate()
call Update_Label
End Sub
Sub Update_Label()
Label1.Caption = Sheets(1).Cells(1, 1)
Application.OnTime Now() + TimeValue("00:00:01"), "Update_Label"
End Sub
' also tried Controls("Label1").Caption = Sheets(1).Cells(1, 1).Value
These are all stored and ran from within the UserForm code. Both codes work fine on 1st load/call but both break my form whenever I call them a second time.
Each time I call for these updates (after the initial values are loaded) the form freezes and the VBA window in popped-up without any code highlights or error messages.
I have used these methods before and had no such issues.
Any thoughts ?
Try moving Update_Label to a standard module.
You will need to reference the form in the code. You may also want to recalculate before getting the cell value.
Sub Update_Label()
Dim rng As Range
Set rng = Sheets(1).Cells(1, 1)
rng.Calculate
UserForm1.Label1.Caption = rng
Application.OnTime Now() + TimeValue("00:00:01"), "Update_Label"
End Sub
Related
The title says it all...
For example, if I want to have a cell which displays the current time and auto updates minute by minute (well, I think we call that a clock), how do I do it?
Is there a simple function implemented already or should I create a macro and assign it to a specific event?
EDIT: Following the provided answer by #Jim K, I want to be more clear about what I want. The "clock" example above was here to make it simple to understand, but what I really want is in the title: a cell value which changes periodically, be it a string, a number, a date...
First enter =NOW() in a cell, and format the number by going to Format -> Cells.
Next, this Basic macro (from here) recalculates every minute. Go to Tools -> Customize and assign it to the Open Document event.
Sub RecalculatePeriodically
Const secs = 60
On Error Goto ErrorHandler
Do While True
Wait(1000 * secs)
ThisComponent.calculateAll()
Loop
ErrorHandler:
'Document was probably closed
End Sub
However, this crashes when exiting LibreOffice. So instead, use the following threaded Python macro (like here). Assign keep_recalculating_thread to the Open Document event.
import time
from threading import Thread
import uno
def keep_recalculating_thread(action_event=None):
t = Thread(target = keep_recalculating)
t.start()
def keep_recalculating():
oDoc = XSCRIPTCONTEXT.getDocument()
while hasattr(oDoc, 'calculateAll'):
oDoc.calculateAll()
time.sleep(60)
g_exportedScripts = keep_recalculating_thread,
Another idea is at https://ask.libreoffice.org/en/question/5327/how-can-i-run-a-macro-at-regular-time-interval/, although it requires linking to another file which seems cumbersome.
EDIT:
Maybe you are looking for something like this? Test it by starting with a blank spreadsheet and entering 1 in cell A1.
def keep_changing_cell(action_event=None):
t = Thread(target = keep_changing_thread)
t.start()
def keep_changing_thread():
oDoc = XSCRIPTCONTEXT.getDocument()
oSheet = oDoc.CurrentController.ActiveSheet
COLUMN_A = 0
FIRST_ROW = 0
oCell = oSheet.getCellByPosition(COLUMN_A, FIRST_ROW)
while hasattr(oDoc, 'calculateAll'):
oCell.setValue(oCell.getValue() + 1)
time.sleep(2)
tl;dr
Is there a simple function implemented already
No.
From LibreOffice and fairly recent:
(I don't know a 'clock' property applying to cells.)
There are simple ways to obtain the time, for example given suitable formatting, with date:
=NOW()
or Ctrl+;
Or, for example, without date:
=MOD(NOW(),1)
The first and last will update, but only when the sheet is recalculated.
For a cell that ticks away (eg second by second) I believe you will need a script.
I've browsed the various questions already asked with this issue other users have faced and none of the solutions seem to fix the error code coming up.
I have a form which prompts the user for a reference number - they input this into the text field and then press OK
'OK button from form1
Public Sub CommandButton1_Click()
refInput = refTextBox.Value
InputRef.Hide
ExportShipForm.Show
End Sub
Once this has been pressed, the next form appears which I would like to be populated with data based on the reference number input on the first form. I have an update button which will update the "labels" on the form to show the data - this is where I am getting an error.
The first label to update is through a Vlookup:
Below the users clicks the update button the 2nd form:
Public Sub btnUpdate_Click()
Call ICS_Caption
lbl_ICS.Caption = Label_ICS
End Sub
This calls a function below:
Public Sub ICS_Caption()
Dim ws1 As Worksheet
refInput = InputRef.refTextBox.Value
Set ws1 = Worksheets("MACRO")
dataRef = Worksheets("Shipping Data").Range("A:K")
Label_ICS = WorksheetFunction.VLookup(refInput, dataRef, 7, False)
End Sub
The error continues to come up each time - I have ran the vlookup manually in a cell outside of VBA and it works fine.
I have typed the range in the Vlookup whilst also using named ranges but each variation shows the same error.
Eventually, I would want the label on form 2 to update with the result of the Vlookup.
Any ideas?
You need to Dim dataRef as Range and then Set it.
See code Below:
Dim DataRef as Range
Set dataRef = Worksheets("Shipping Data").Range("A:K")
Just like a Workbook or Worksheet you need to Set the Range
Just as Grade 'Eh' Bacon suggest in comments its always best to Dim every reference.
The best way to do so is to put Option Explicit all the way at the top of your code. This forces you to define everything which helps it preventing mistakes/typo's etc.
Update edit:
The problem was you are looking for a Reference number in your Sheet (thus an Integer) but refInput is set as a String this conflicts and .VLookup throws an error because it can't find a match.
I have reworked your code:
Your sub is now a function which returns the .Caption String
Function ICS_Caption(refInput As Integer)
Dim dataRef As Range
Set dataRef = Worksheets("Shipping Data").Range("A:K")
ICS_Caption = WorksheetFunction.VLookup(refInput, dataRef, 7, False)
End Function
The update Button calls your Function and provides the data:
Public Sub btnUpdate_Click()
lbl_ICS.Caption = ICS_Caption(InputRef.refTextBox.Value)
End Sub
By using a Function you can provide the Integer value and get a return value back without the need of having Global Strings or Integers.
Which would have been your next obstacle as you can only transfer Variables between Modules/Userforms by using a Global Variable.
I would even advice to directly use the function in the Initialize Event of your 2nd Userform to load the data before the Userform shows this is more user friendly then needing to provide data and then still needing to push an update button.
Verify that you have no missing libraries in VBA IDE > Tools > References
Try using a worksheet cell as the place to store and retrieve refTextBox.Value, rather than refInput (which I assume is a global variable):
Public Sub CommandButton1_Click()
...
Worksheets("Shipping Data").Range($M$1).Value=refTextBox.Value
End Sub
Public Sub ICS_Caption()
Dim refInput as Long'My assumption
...
refInput=Worksheets("Shipping Data").Range($M$1).Value
...
End Sub
Make sure you have Option Explicit at the top of all of your code windows.
OK, I've been out of Access programming for a couple of versions, but I could swear I used to be able to point controls at form global variables. Sample code as follows:
Option Compare Database
Option Explicit
Dim Testvar As String
Private Sub Form_Load()
Testvar = "Load"
End Sub
Private Sub Form_Open(Cancel As Integer)
Testvar = "open"
End Sub
Private Sub Text0_Click()
Testvar = "settest"
End Sub
I should be able to put a text box on the control that can see the TestVar variable, but the controls don't do it. Also, I used to be able to do that with the form's record source.
So, the questions -
Am I crazy - that was never possible?
Or have I forgotten how to address the form?
And then the most important question - what is the best way to get around this?
The most common way this is used is to pass in OpenArgs (record keys in this case) which is then parsed in to global vars and then a couple of controls display the open args and/or look up values to display from the keys.
I really hate to have to build routines that rebuild and load the record sources for the controls. Hope someone knows a better approach
In addition to your existing event procedures, you can add a function in the form module which retrieves the value of the Testvar module variable.
Function GetTestvar() As String
GetTestvar = Testvar
End Function
Then use =GetTestvar() as the Control Source for Text0.
You have to actually set the value of the text box. There's no way (to the best of my knowledge) to bind a text box to a variable.
Option Compare Database
Option Explicit
Private Sub Form_Load()
Text0.Value = "Load"
End Sub
Private Sub Form_Open(Cancel As Integer)
Text0.Value = "open"
End Sub
Private Sub Text0_Click()
Text0.Value = "settest"
End Sub
Of course, you could store the value in a variable and use it to set the value instead, but it makes little sense to do so in this simple example.
The TempVars collection is a feature introduced in Access 2007. So, if your Access version is >= 2007, you could use a TempVar to hold the string value. Then you can use the TempVar as the control source for your text box.
With =[TempVars]![Testvar] as the Control Source for Text0, the following event procedures do what you requested.
Private Sub Form_Open(Cancel As Integer)
TempVars.Add "Testvar", "Open"
End Sub
Private Sub Form_Load()
TempVars("Testvar") = "Load"
End Sub
Private Sub Text0_Click()
TempVars("Testvar") = "settest"
Me.Text0.Requery
End Sub
Note: [TempVars]![Testvar] will then be available throughout the application for the remainder of the session. If that is a problem in your situation, you could remove the TempVar at Form Close: TempVars.Remove "Testvar"
Requirement was: To show the login Id of the application user on all forms in the application.
Here is how I implemented this:
Create a module: module_init_globals
with the following code:
Option Compare Database
'Define global variables
Global GBL_LogonID as string
Option Explicit
Public Sub init_globals ()
'Initialize the global variables
'Get_Logon_Name is a function defined in another VBA module that determines the logon ID of the user
GBL_LogonID = Get_Logon_Name()
End Sub
On the main/first form - we need to call the module that will initialize the global variables:
In the code for "on open" event I have:
Private Sub Form_Open (Cancel as Integer)
call init_globals
End Sub
then on each of the forms in the app, I have a text control - txtUserID to display the logon id of the user
and I can set it's value in the "on open" event of the form.
txtUserID.value = GBL_LogonID
Is there a way to run a function in VBA the moment data in any control element changes? I've tried Form_AfterUpdate and Form_DataChange but they seem not to do anything
You do not have to code After Update/Change event of the controls, check out Key Preview
You can use the KeyPreview property to specify whether the form-level
keyboard event procedures are invoked before a control's keyboard
event procedures. Read/write Boolean.
Use it carefully.
For example, with KeyPreview on:
Private Sub Form_KeyPress(KeyAscii As Integer)
MsgBox "You pressed a key"
End Sub
Step 1: Create a function
Function DoStuff()
Call RunMySub
End Function
Step 2: Create a Macro (Named RunMyCode)
RunCode
Function Name DoStuff()
Step 3: Modify the Form_Load() sub
Private Sub Form_Load()
Dim cControl As Control
On Error Resume Next
For Each cControl In Me.Controls
if cControl.ControlType = 109 'this is for text boxes
'Depending on what your code does you can use all or some of these:
cControl.OnExit = "RunMyCode"
cControl.OnEnter = "RunMyCode"
cControl.OnLostFocus = "RunMyCode"
cControl.OnGotFocus = "RunMyCode"
If cControl.OnClick = "" Then cControl.OnClick = "RunMyCode"
end if
Next cControl
On Error GoTo 0
You can use any of the attributes from the control I find the pairs of 'OnExit/OnEnter' and 'OnLostFocus/OnGotFocus' to be the most effective. I also like 'OnClick' but I use the if statement to not overwrite actions (for buttons and stuff). There are a dozen other methods you can assign the control action to -- I'm sure you'll be able to find one/several that meet your goal.
Note -- I use the on error enclosure because I wrap this code around multiple different types of controls and not all have all of the methods.
I'm pretty much a novice with crystal reports. We have another team making our reports, and I'm integrating it with the application. The problem is with paging and user defined parameters. Essentially, the document.table. setdatasource points at our query result set (set in code). The report, upon opening, will then prompt the user for the parameter, which will further reduce the displayed result to the user as the prompt parameter is part of the record selection formula.
AND
{#Age} >= 20 and
{#Age} < 30 and
{Report;1.Sector} = {?NewSector})
This will return a table of more than one page in length. However, requesting the next page will result in the user being prompted for the Sector again, and once provided, will take the user back to page 1 of the results again.
If I take out the reference to the parameter, then obviously the paging works fine.
Is there away to just take the parameter once from the user, and then reuse this value in the subsequent paging requests?
Appreciate your help...
Stupid me. I had the report refreshing every postback.
EDit
A bit more elaboration:
I have the report being created in a RefreshReports procedure, which sets up the report as follows:
Dim objConnectionInfo As CrystalDecisions.Shared.ConnectionInfo
Dim crConnectionInfo As CrystalDecisions.Shared.ConnectionInfo
Dim myconnection As New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings(ApplicationConstants.MyDB).ConnectionString)
Dim myConnBuilder As New SqlConnectionStringBuilder(myconnection.ConnectionString)
'// Log on credentials from web.config
objConnectionInfo = New CrystalDecisions.Shared.ConnectionInfo
objConnectionInfo.IntegratedSecurity = False
objConnectionInfo.ServerName = myconnection.DataSource
objConnectionInfo.UserID = myConnBuilder.UserID
objConnectionInfo.Password = myConnBuilder.Password
crConnectionInfo = objConnectionInfo
'// Get the report details needed
rep = Request.QueryString("Report")
crDoc.Load(Server.MapPath(rep))
crDoc.SetDatabaseLogon(myConnBuilder.UserID, myConnBuilder.Password)
Dim crDatabase As CrystalDecisions.CrystalReports.Engine.Database
Dim crTables As CrystalDecisions.CrystalReports.Engine.Tables
Dim aTable As CrystalDecisions.CrystalReports.Engine.Table
crDatabase = crDoc.Database
crTables = crDatabase.Tables
There is then a series of else ifs like the following:
ElseIf rep = "ExampleRep.rpt" Then
For Each aTable In crTables
If aTable.Name = "GetSectorRep;1" Then
Dim NewSector As String = ""
NewSector = Session("NewSector").ToString()
aTable.SetDataSource(DAL.GetSectorRepTable(NewSector))
End If
Next
In my Page_Init I grab the query strings I need and call RefreshReports. The problem I was having was that I was also requesting RefreshReports in a If Postback on page_load, which meant that every time the report posted back (eg. new page) it was requesting the parameter again, as the report was being created afresh.
So, silly me - quite an obvious mistake in the end!