I know we can detect if a key is pressed e.g. KeyAscii = 8, however I have only seen this done on events whilst the userform app is the window in operation.
Is it possible to have the window in the background e.g. whilst I am using IE, I can press CTRL + 0 and my app recognizes it and does an action?
At least to my own knowledge the answer is - it's not possible, but..
1. Things we need to know
Let's get some facts straight before we do any coding:
The excel application will not be able to execute any code while the Window object is in xlMinimzed state
So basically as soon as the user will close, minimize or even tab out of the Application, we no longer will be able to detect any OnKey (or any other) events, as the currently active application (ie. browser) takes precedence over the excel Application
hence CTRL+0 will now zoom out of the page as it's the browser's default behaviour.
With that being said, we can do at least some things to get close to at least some form of usefulness out of this.
2. The Application Layout
Since you have not provided specific application details, I created this mockup:
A Worksheet consisting of a single CommandButton, that launches the UserForm1
Private Sub CommandButton1_Click()
UserForm1.Show
End Sub
A Module named Module1 which contains a simple hello world message (for the OnKey event)
Public Sub hello()
MsgBox "Hello world!"
End Sub
And the UserForm1 which contains the code for our OnKey handling
Private Sub UserForm_Activate()
With ActiveWindow
.WindowState = xlNormal
' we need to change window state to xlNormal
' xlMaximized would result in width/height property changing error
' and with xlMinimized our Onkey would not work
.Width = 0
.Height = 0
End With
Range("A1").Select
' we need a range selected, otherwise Application OnKey will not fire
Application.OnKey "^0", "Module1.hello" ' launches hello world procedure
Me.Hide
End Sub
Now that's enough to have the window (almost) minimized and respond to a keypress
3. If we want to show the UserForm while maintaing functionality
Now let's say we want the UserForm while hiding the rest of the excel Application in the background.
In order to do this, we need to:
Change the UserForm to vbModeless
To do this, select the UserForm object and show properties (F4)
Remove the Me.Hide line from our UserForm1 code
Private Sub UserForm_Activate()
With ActiveWindow
.WindowState = xlNormal
.Width = 0
.Height = 0
End With
Range("A1").Select
Application.OnKey "^0", "Module1.hello"
' Me.Hide <- remove me
End Sub
That leaves us with the following fucntionality
If there are any suggestions for improvement / optimization I'd be
happy to know as this question intrigued me quite a bit.
I'll try to keep the answer updated!
Problem: I am building a Userform that has a 'Submit' and 'Cancel' button. I want the entire form to clear any entered data and close the form if the user hits the 'Cancel' button but I also am trying to build in the same functionality if the user hits the red 'X' in the top right corner. I'm unclear where I need to unload the form. I currently have it placed within the btnCancel_Click() method and I'm able to launch the form, enter some data and hit Cancel and it will close the form down.
But when I try to re-launch the form a 2nd time I get an error (I attached a picture of that message) that says
"Run-Time error '-2177418105 (80010007): Automation Error - The Callee (server [not server application]) is not available and disappeared; all connections are invalid. The call may have executed.
If I remove Unload Me from btnCancel_Click() then the form can close and re-open just fine, but any data I entered the first time will still be on the form and isn't cleared properly. I'm wondering if this is an Unload Me error or do I need to reset all form controls when I initialize the form?
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
' how was the form closed?
' vbFormControlMenu = X in corner of title bar
If CloseMode = vbFormControlMenu Then
' cancel normal X button behavior
Cancel = True
' run code for click of Cancel button
btnCancel_Click
End If
End Sub
'******************************************************************
Private Sub btnCancel_Click()
mbCancel = True
Me.Hide
Unload Me
End Sub
'*********************************************************************
Private Sub UserForm_Initialize()
'Populate values for 2 combo boxes
lastEmp = Sheets("Form_Ref").Cells(Rows.Count, 1).End(xlUp).Row
Me.cmbBoxEmpName.List = Sheets("Form_Ref").Range("A2:A" & lastEmp).Value
lastBld = Sheets("Form_Ref").Cells(Rows.Count, 2).End(xlUp).Row
Me.cmbBoxBuildingName.List = Sheets("Form_Ref").Range("B2:B" & lastBld).Value
End Sub
'******************************************************************
Public form As New CheckOutForm
Sub testFormOptions()
'Button pressed within Excel will start program and show the userform
form.Show
End Sub
This is the easiest quick and dirty solution:
Delete Public form As New CheckOutForm from the code. Then add it in the testFormOptions():
Sub testFormOptions()
Dim form As New CheckOutForm
form.Show
End Sub
Some not-that-good VBA books/tutorials would even go a bit like this, but this is brutal:
Sub testFormOptions()
CheckOutForm.Show
End Sub
Anyway, now the problem with the predefined values in the form is solved.
For the clean and not-so-easy solution, consider writing a MVC framework around the form:
https://codereview.stackexchange.com/questions/154401/handling-dialog-closure-in-a-vba-user-form
this blogpost (disclaimer - mine!), which pretty much says what the above link proposes, but it does not have the errors from the question.
the old StackOverflow tutorial for UserForms
If you execute Unload, you destroy the form object. With other words, your (global) variable form gets invalid and if you issue a new form.show, you get the runtime error.
When, on the other hand, you just unhide the form, the form-object stays valid (it's just currently not visible) and all controls keep their value.
Either you do some housekeeping by resetting all controls when a form is displayed (use the UserForm_Activate-event), or you have to create a new form-object every time you want to display it (I would strongly advice not to use the name form as variable name to avoid confusion).
Sub testFormOptions()
dim myForm as CheckOutForm
myForm.Show
End Sub
Having a weird issue with Excel Userforms.
I've got 2 userforms, one with a textbox and the other with a button. The one with the textbox uses Textbox_Change event to track what's in the box and after 10 characters have been entered it loads Userform2.
When Userform2 initialises Userform1 is unloaded. Userform2 has a button which then reloads Userform1.
This is a very slimmed down version of my end result, but I needed to see it in it's most basic form to make sure it wasn't my code that was causing an issue.
Anyway, when I'm typing in Userform1's text box it tracks the changes of TextBox1, but after it's reloaded it stops tracking.
Below is my code.
Userform1:
Dim Iteration As Integer
Private Sub TextBox1_Change()
Debug.Print Me.TextBox1.Value
Iteration = Iteration + 1
If Iteration = 10 Then
UserForm2.Show
End If
End Sub
Private Sub UserForm_Initialize()
Iteration = 0
End Sub
Userform2:
Private Sub CommandButton1_Click()
UserForm1.Show
End Sub
Private Sub UserForm_Initialize()
Unload UserForm1
End Sub
It's as simple as it could possibly be.
When I first loaded Userform1 I entered into the text box "1234567890", which outputted the console:
1
12
123
1234
12345
123456
1234567
12345678
123456789
1234567890
As expected.
However after I've loaded Userform2 and then click the button to reload Userform1, when I enter "abcdefghij" into the textbox it doesn't output anything to the console.
Is there some fundamental concept of userforms that I'm missing here? Or something I'm getting wrong? I can't see anything that would be causing this.
I've been reading up on what unloading and show actually does and I can't find anything that'd affect whether or not the event should fire. From what I know when you run Userform.show it reinitialises everything, but even if it didn't it should still output SOMETHING to the console.
Can anyone help?
EDIT:
even if I change Userform 1 to be:
Dim Iteration As Integer
Private Sub TextBox1_Change()
Debug.Print Me.TextBox1.Value
Iteration = Iteration + 1
If Iteration = 10 Then
Unload Me
UserForm1.Show
End If
End Sub
Private Sub UserForm_Initialize()
Iteration = 0
End Sub
It still doesn't track changes on the second time round.
Okay, I fixed the issue.
I'm not sure why it works, need to look into it further, but if I add vbModeless to the .show function when initialising a userform it allows me to track changes on the second form.
In Excel 2013 (Office 15) a combination of a shortcut and a very simple userform (for formatting) is sometimes causing the Active Workbook to disappear and another workbook to show for no reason (only happens sometimes, often after just having changed activeworkbook). I am trying to keep the current workbook window in the foreground while the formatting userform is shown but without success.
Here is my code:
Sub AssignKeys()
Application.OnKey "^%,", "MakeBlue"
End Sub
Sub MakeBlue()
frmFormat.Show
End Sub
frmFormat is a very simple and small form that only has one textbox where a formatting code is entered. It does work to restore the previously active workbook after hiding the modal form using this code:
Private Declare PtrSafe Function SetForegroundWindow Lib "USER32" (ByVal hWnd As LongPtr) As LongPtr
Public Sub MyAppActive(Handle As Long)
Dim lngStatus As LongPtr
lngStatus = SetForegroundWindow(Handle)
End Sub
Private Sub tbShortcut_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If tbShortcut.Text <> "xn" Then
Dim mySel As Range
Dim myWb As workBook
Dim myWnd As Window
Set myWnd = ActiveWindow
Set mySel = Selection
Set myWb = ActiveWorkbook
Debug.Print mySel.Address
Me.Hide
'mySel.Parent.Parent.Activate
myWb.Activate
myWnd.Activate
MyAppActive myWnd.hWnd
end if
end sub
So once the form is hidden, the original workbook window is restored. But while the userform is shown, the other unwanted Workbook window is displayed. Would be very greatful for any hints. Thank you!
The following seems to work most of the time. The OnTime seems to be necessary. I suspect the Microsoft bug is with the ribbon.
fsShow myUserForm
Sub fsShow(form As Object)
' Work around bug that form.show changes active window rather randomly
' (Only the displayed window is wrong, not the ActiveCell.parent.parent.)
Set fsActiveWindow = Application.ActiveWindow
form.Show
Application.OnTime Now(), "fsResetActiveWindowOnTime"
End Sub
Sub fsResetActiveWindowOnTime()
If Not fsActiveWindow Is Nothing Then
fsActiveWindow.Activate
Set fsActiveWindow = Nothing
End If
End Sub
However, I occasionally get a type mismatch error just by calling this, i.e. a userform is not an object(!). This whole area is buggy so I avoid using this method.
Sometimes just recreating a userform fixes the problem. It is not for all userforms. And I have not figured out a trigger.
I have this simple Userform, where I only have TextBox1 and TextBox2. I enter some text in both of them. Assume the focus is on (the cursor is in) the TextBox2. When I click on TextBox1, I want the whole text in this control to be highlighted (selected). Thus I use this code:
Private Sub TextBox1_Enter()
With TextBox1
.SetFocus
.SelStart = 0
.SelLength = Len(.Text)
End With
MsgBox "enter event was fired"
End Sub
There is a MsgBox at the end which is loaded, that means the event works. However, the text is not highlighted. How to fix this?
I use the Enter event and don't want to use the MouseDown event, because I need the code to also work when the TextBox1 is activated programatically, so I feel the Enter event to be the best choice, as it's fired in both cases! Another drawback of the MouseDown event is: when I click for the second time on the TextBox1, I would not expect the whole text to be highlighted anymore, because the focus was set on the first click and it was not changed after I clicked on the same control for the second time; so in this case I would like the cursor to act normally (not to keep the text marked).
Update
When I click once on the TextBox1, I expect to have this result:
If clicked again, the highlight would be removed and the cursor would be placed in the place where it was clicked.
Can't be more simple than this I guess...
Private Sub TextBox1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
With TextBox1
.SelStart = 0
.SelLength = Len(.Text)
End With
End Sub
Whether you click on the textbox or you tab into it, it will do what you want. To deselect the text, use the arrow keys.
Explanation
If you debug the code you will see that even though you have said .SetFocus, the focus is not on the Textbox. .SetFocus doesn't work in TextBox1_Enter() and you need to have focus for the rest of the code to work. And hence my alternative...
Alternative
You may also like this version :) This overcomes the limitation of using the mouse in the TextBox
Dim boolEnter As Boolean
Private Sub TextBox1_Enter()
boolEnter = True
End Sub
Private Sub TextBox1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
If boolEnter = True Then
With TextBox1
.SelStart = 0
.SelLength = Len(.Text)
End With
boolEnter = False
End If
End Sub
Pff, took me a while. Actually, your code works, but it highlights the text BEFORE the click event happens. So you clicking in the box instantly overrides the selection created by the code.
I have used a delayed selection, and it works, though it is a bit disgusting...
The code for the textboxes:
Private Sub TextBox1_Enter()
Application.OnTime Now + TimeValue("00:00:01"), "module1.SelectText1"
End Sub
Private Sub TextBox2_Enter()
Application.OnTime Now, "module1.SelectText2"
End Sub
Note that it works even withouth the {+ TimeValue("00:00:01")} part, but it might theoretically stop it from working at times. Hmm, on a second thought, just leave it out. I doubt it would ever cause a problem.
Now the code in module1:
Sub SelectText1()
UserForm1.TextBox1.SelStart = 0
UserForm1.TextBox1.SelLength = Len(UserForm1.TextBox1.Text)
End Sub
Sub SelectText2()
UserForm1.TextBox2.SelStart = 0
UserForm1.TextBox2.SelLength = Len(UserForm1.TextBox2.Text)
End Sub
Hope this works for you too. Ineresting problem. :) Cheers!
I couldn't manage to select/highlight text in the Enter event as the the mousedown and mouseup events coming after are somewhat resetting the selection.
I think the most proper way of achieving what you want is this :
' if you want to allow highlight more then once, reset the variable LastEntered prior to call SelectTboxText:
' LastEntered = ""
' SelectTboxText TextBox2
Dim LastEntered As String
' Button to select Textbox1
Private Sub CommandButton1_Click()
SelectTboxText TextBox1
End Sub
' Button to select Textbox2
Private Sub CommandButton2_Click()
SelectTboxText TextBox2
End Sub
Private Sub TextBox1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
SelectTboxText TextBox1
End Sub
Private Sub TextBox2_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
SelectTboxText TextBox2
End Sub
Public Sub SelectTboxText(ByRef tBox As MSForms.TextBox)
If LastEntered <> tBox.Name Then
LastEntered = tBox.Name
With tBox
.SetFocus
.SelStart = 0
.SelLength = Len(.Text)
End With
End If
End Sub
So each time you want to activate one of the textbox programmatically, you should call the sub SelectTboxText, which is not really annoying IMO. I made 2 buttons for this as an example.
This is somewhat an enhancement of what #vacip posted. The benefit you get is that you don't need to add a separate method in the Module for each new textbox.
The following code in your User Form:
'===== User Form Code ========
Option Explicit
Private Sub TextBox1_Enter()
OnTextBoxEnter
End Sub
Private Sub TextBox2_Enter()
OnTextBoxEnter
End Sub
Private Sub TextBox3_Enter()
OnTextBoxEnter
End Sub
The following code goes in a Module:
'===== Module Code ========
Sub SelectAllText()
SendKeys "{HOME}+{END}", True
End Sub
Sub OnTextBoxEnter()
Application.OnTime Now + 0.00001, "SelectAllText", Now + 0.00002
End Sub
Private Sub UserForm_Initialize()
TextBox1.SetFocus
TextBox1.SelStart = 0
TextBox1.SelLength = Len(TextBox1.Text)
End Sub
Add this to the form's code
I know this is well out of date but I'm leaving this here in case it helps someone in my position.
What I want is:
If I click on the box for the first time: select all the text
If I click on it a subsequent time: put the cursor where the mouse is and allow me to use the mouse to select a substring
Firstly it is important to know that "Select all the text" is the default behaviour when tabbing into a TextBox and that "Put the cursor here" is the default behaviour when clicking on a TextBox so we only need to worry about what the mouse is doing.
To do this, we can keep track of the Active Control, but only while the mouse is moving over our TextBox (ie. before the Click)
Code:
Private m_ActiveControlName As String
Private Sub Text1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
m_ActiveControlName = Me.ActiveControl.Name
End Sub
Private Sub Text1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If m_ActiveControlName <> Me.Text1.Name Then
Call Text1_Enter 'we don't have to use Text1_Enter for this, any method will do
Exit Sub 'quit here so that VBA doesn't finish doing its default Click behaviour
End If
End Sub
Private Sub Text1_Enter()
With Text1
.SelStart = 0
.SelLength = Len(.Text)
End With
End Sub
There's another solution than the one given by Siddharth.
EDIT: but there's this bug of SendKeys, so the solution I propose below is a lot worse than Siddharth one. I keep it in case one day the bug is corrected...
It relies on the property EnterFieldBehavior of the TextBox field. This property works only when the Tab key is pressed to enter that field, and if this property is fmEnterFieldBehaviorSelectAll (0) the whole field text is automatically selected.
So a dummy caret movement between fields when the form is shown, will activate the feature automatically. For instance this movement can be achieved by pressing Tab to move to the next field, and pressing Shift+Tab to move to the previous field (so back to the original field):
Private Sub UserForm_Activate()
SendKeys "{TAB}+{TAB}"
End Sub
The (very little) advantage of this solution is that you can tune your user form by editing manually the properties EnterFieldBehavior, TabIndex, TabKeyBehavior and TabStop without changing the VBA code anymore to set "select all" on the field with the initial focus.
In short, the VBA code above tells to consider the property EnterFieldBehavior of the field which has the initial focus when the user form is displayed (provided that it's a TextBox or ComboBox field of course).
use this
Private Sub TextBox1_Enter()
With TextBox2
.ForeColor = vbBlack
.Font.Bold = False
End With
With TextBox1
.ForeColor = vbRed
.Font.Bold = True
End With
End Sub
Private Sub TextBox2_Enter()
With TextBox1
.ForeColor = vbBlack
.Font.Bold = False
End With
With TextBox2
.ForeColor = vbRed
.Font.Bold = True
End With
End Sub
The behavior you're trying to implement is already built in to the TextBox. When you move the mouse over the left side of the text box, the mouse pointer will point to the right. If you click, it will select all the text in the field. Clicking anywhere else will deselect the text.
I will try a few other strategies to see if I can get this to work in one Sub.
Try the same code with TextBox1_MouseDown. It should work.
Private Sub TextBox1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
With TextBox1
.SetFocus
.SelStart = 0
.SelLength = Len(.Text)
End With
MsgBox "Text in TextBox1 is selected"
End Sub