Can someone confirm if there is an event in VBA for when a textbox drawn from the insert menu in Excel 2010 has changed? I tried RelevantTextBoxName_Change()but the sub is not called even though the contents of the textbox has changed.
Its a "Shapes" textbox.
Thanks.
I suggest you use this implementation
The above library uses a VBA implementation of what #lbo suggested. It's essentially a way of creating 'fake events' from existing events which already capture the functionality you desire. In this case we use CommandBars_OnUpdate event (which appears to trigger whenever shapes are changed / deleted / selected / deselected / created. Then we filter down the on the current state of the excel workbook to try to understand what actually occurred. By detecting the selected object is or was a shape, we can adequately determine if this event is occurring on a shape or not.
Public WithEvents bars As commandBars
Public old_selection As Object
Private Sub InitialiseEvents()
Set bars = Application.commandBars
End Sub
Private Sub bars_OnUpdate()
'This will call on each user action, here we can check our shapes:
If DetectShape Then
'Shape selected and changed event:
If GetName(old_selection) = GetName(Selection) Then
Debug.Print "Shape Changed"
Else
Debug.Print "Shape Selected"
End If
End If
Set old_selection = Selection
End Sub
Private Function GetName(ByVal obj As Object) As String
On Error Resume Next
GetName = obj.Name
End Function
Private Function DetectShape() As Boolean
On Error GoTo endDetect
DetectShape = Selection.ShapeRange.Count > 0
endDetect:
End Function
Running InitialiseEvents() will print "Shape Selected" whenever a shape is selected and "Shape Changed" whenever a shape might have changed. Note I leave detecting whether the shape has indeed changed to you.
The excel object model doesn't have any events to control manipulations with shapes. You need visual studio to make it happen. See this:
Create new events for shape in Excel
Related
This is the first time I use VBA.
I want to create simple checkboxes using Activex label (not using form label because then I wouldn't be able to edit the font, etc).
I use this code along with Wingding font:
Private Sub Label1_Click()
If Label1.Caption = Chr(254) Then
Label1.Caption = Chr(168)
Else
Label1.Caption = Chr(254)
End If
End Sub
I have multiple labels to act like checkboxes.
So I applied the code above to one label and copy paste that label.
However, the code did not adjust to the new labels' name.
How do I copy paste labels along with its vba?
Or is there a line of code that I can add to mine so that the vba refers to all labels in my worksheet, not just one particular label?
Well, for such a purpose it was easier to handle Form labels. In such a case you could assign to all of them the same Sub and identify the object using Application.Caller...
In order to handle the ActiveX control you need a class:
Insert a class module, name it and paste the next code:
Option Explicit
Public WithEvents lbl_Control As msForms.Label
Public Sub lbl_Control_Click()
MsgBox lbl_Control.Caption ' you can do here whatever you want with the object...
End Sub
All labels for the sheet in discussion should be (somehow) initialized to use the above class for their Click event. So, please copy the next code in the Activate event or the respective worksheet:
Option Explicit
Private arrEvents As Collection, lblEvent As ClsLabels
Private Sub Worksheet_Activate()
Dim shP As Shape
Set arrEvents = New Collection
For Each shP In Me.Shapes
If shP.Type = msoOLEControlObject Then
If TypeOf shP.OLEFormat.Object.Object Is msForms.Label Then
Set lblEvent = New ClsLabels
Set lblEvent.lbl_Control = shP.OLEFormat.Object.Object
arrEvents.Add lblEvent
End If
End If
Next
End Sub
Activate another sheet and go back to the one where labels in discussion exist, in order to trigger the Worsheet_Activate event.
Now, click on the respective labels and enjoy the message...
Change their caption, dezactivate - activate the sheet and click on them.
In case of using Form labels, you should simple create a Sub:
Sub ChangeLBlCaption()
MsgBox "Label name: " & Application.Caller
Dim lbl As Label
Set lbl = ActiveSheet.Labels(Application.Caller)
MsgBox "label Caption: " & lbl.Caption
End Sub
Then, only assign this sub to all Form labels you need to return their name. It can be done automatically, too...
This second label type assures a more reliable handling for such a purpose. Since, in ActiveX case all labels must be reinitialized, from event class allocation point of view, after a VBA error and code stop, putting the label in Design mode to modify its caption (for instance) etc.
Excel allows to start with a Modeless form and then display a Modal (but not the other way around)
I have an app with 4 Userforms : UF1 - Select a partner, UF2 - List existing transactions, UF21 - Display an existing transaction, UF22 - Make a new transaction. UF21 and UF22 both stem from UF2.
UF21 needs to be Modeless in order to display more than one transactions and compare side by side, therefore UF1, UF2 and UF21 are all Modeless. But I want UF22 to be Modal in order to issue one new transaction at a time.
My problem is that after I close UF22, even just ESCaping from the form right off the bat, all previous forms close. I should be able to return to UF2. If I make UF22 Modeless all is ok.
I have written a function to traverse the UserForms Collection and I am able to get a reference to the object of the Form I want to activate. So, I am able to return (in debug mode) to UF2 which is a listbox, activate the list box, but after the last pending statement both UF2 and UF1 close.
Is what I am trying to do impossible due to the nature of the Modal and Modeless forms or should I keep pushing for the correct code?
Since my original question is still open and my tested implementation of the proposed solution by #PeterT is not working properly, I include the code I have for the moment, based on #PeterT 's suggestion.
'===============
' Form UF1
'===============
Private Sub UserForm_Activate()
If ActivateUF22(FormID) = True Then Exit Sub
'.... more commands
End Sub
'============
' Form UF2
'============
Private Sub UserForm_Activate()
If ActivateUF22(FormID) = True Then Exit Sub
'.... more commands
End Sub
'----------------
Private Sub Cbn_OpenUF22_Click()
If ActivateUF22() = True Then
Exit Sub
Else
With New UF22
.Show vbModeless
End With
End If
End Sub
'================
' In a Module...
'================
Public Function ActivateUF22() As Boolean
Dim frm As Object
Set frm = GetFormFromID("UF22*") ' Custom function to get a form Object based on
' some criterion (FormID in a hidden TextBox)
If Not frm Is Nothing Then
' the only way I know to *Activate* an already .Show(n) form and compensate
' for the fact that the Close CommandButton may already have Focus
frm.TBx_UF22_CODE.SetFocus
frm.CBn_UF22_CLOSE.SetFocus
ActivateUF22 = True
Else
ActivateUF22 = False
End If
End Function
Well I finally managed to get the workaround to behave.
The remaining problem was the fact that clicking twice in a row on the same userform, besides the "Modal" one, would succeed and allow the user to break out.
I even tried the "AppActivate Application.caption" approach found in another SO thread but that didn't work either.
The only solution that works and does not bother me is to insert a MsgBox with a warning to the user, as such:
Public Function ActivateUF22() As Boolean
Dim frm As Object
Set frm = GetFormFromID("UF22*") ' Custom function to get a form Object based on
' some criterion (FormID in a hidden TextBox)
If Not frm Is Nothing Then
' the only way I know to *Activate* an already .Show(n) form and compensate
' for the fact that the Close CommandButton may already have Focus
frm.TBx_UF22_CODE.SetFocus
frm.CBn_UF22_CLOSE.SetFocus
ActivateUF22 = True
MsgBox("You cannot move away from this form until it is either completed or cancelled")
Else
ActivateUF22 = False
End If
End Function
Displaying the MsgBox does the trick internally, switches the focus to a different form from the one clicked and, upon return, the UserForm.Activate event fires normally and the ActivateUF22 function prevents the user from escaping the Pseudo-Modal form.
Thanks #PeterT for pointing me to a workaround. I managed to do what I set out to do, albeit in a different manner.
PS I still believe that there is a way to switch from a Modeless form to a Modal one. After all the MsgBox I use is obviously a Modal form and works just as I would like ;-)
I've inserted a large number of form controll buttons (with the text "") into an excel worksheet by copy'n'pasting (from another workbook).
These buttons are connected to this macro (which is located in PERSONAL.XLSB):
Option Explicit
Sub ChangeSomething()
' The button which called the macro.
Dim b As Button
Set b = ActiveSheet.Buttons(Application.Caller)
' Do run the code if the button was not already active.
If b.Text <> "x" Then ' SEEMS TO BE THE PROBLEM
' Do something
' Mark the button as activated.
b.Text = "x"
b.Font.Bold = True
' If the button was already activated, deactivate it.
Else
'Mark the button as deactivated.
b.Text = " "
End If
End Sub
This set up worked properly before. But since copying, I get Runtime Error 1004 "Unable to set the Text property of the Button class".
When handled, the exception seems to be Error 438 "Object Doesn't Support This Property or Method".
The Debugging marks the line:
If b.Text <> "x" Then
What puzzles me is that getting the text property seems to throw the runtime error, but setting the value runs just fine:
b.Text = "x"
The correct Button in the correct Worksheet of the correct Workbook is changed.
As soon as I change the text of the button manually to something other than "", the macro also seems to work.
Unfortunately, the inserted buttons do not appear to be included in the list returned by ActiveSheet.Buttons, so I can not loop over them to change their values.
I'm not sure if that's appropriate, but I've uploaded here a sample file.
I'm not sure your question has the right details. If these buttons are indeed Form Controls - and I think they must be because I believe ActiveX controls don't return a Caller - then their parent object is Shapes, and you would call the Text property from the Shape.TextFrame.Characters object.
I wonder if your original worksheet had aCollection object called Buttons
which contained, perhaps, a list of the buttons in the form of a class object, called Button (hence your error message) and which exposes the properties that are called in your code.
Without that collection and class, the code would be more like this:
Public Sub ChangeSomething()
Dim btn As Shape
With Application
'Check for correct data types.
If Not TypeOf .ActiveSheet Is Worksheet Then Exit Sub
If IsObject(.Caller) Then Exit Sub
'Acquire button
On Error Resume Next
Set btn = .ActiveSheet.Shapes(.Caller)
On Error GoTo 0
'Check for a found button
If btn Is Nothing Then Exit Sub
End With
'Check for non-actionable button.
With btn.TextFrame.Characters
If .Count = 0 Then
.Text = "x"
Exit Sub
End If
End With
'If we've reached here, it's an actionable button.
Debug.Print "Do something."
'Clear the button text.
btn.TextFrame.Characters.Text = ""
End Sub
I rather suspect some half-copied code here, and the rest of the project remains elsewhere.
I created a userform in Excel 2016 with two ListBoxes, using the Tools menu. I double clicked them to create subs and inserted code to check whenever one is selected.
Here is the code:
Private sub SaleType_Click ()
If SaleType.Value ="Core" then
'make sale label visible
QTDV.visible =true
' show core option btn
Core.Visible = true
End if
End sub
When I have a ListBox created from the toolbox this works, but every other time the form is run the saletype ListBox will be value null and this is a problem because I have a check to make sure the ListBox is not empty. Code follows:
If saletype = "" then
Dim msg as string Msg = " please select sale type"
Msgbox msg, and vbcritical
End if
If the ListBox presents value null it will not see it as empty and skip the check if I try saletype = null it still skips it.
I searched and it seems creating ListBoxes on the tool box is weird because Excel does not know what kind of control it is. I opted for creating the ListBoxes in VBA.
Private sub userform_initialize()
Dim saletype as msforms.Listbox
Set saletype = me.Controls.Add("Forms.ListBox.1", "SaleType")
But when running the form and selecting any option on the ListBox the SaleType_Click sub does not trigger.
If you want to implement event handling (like SaleType_Click) you need to declare the object with the WithEvents keyword:
Dim WithEvents saletype as msforms.Listbox
And if a variable/property is not set then its value doesn't exist, so instead of empty string ("") you need to validate for NULL - the IsNull function can do that (= NULL doesn't work as = only works with values):
If IsNull (saletype.Value) then
I just had a problem with my listbox_Click not being fired "every other time". Maybe it was when the same selection was desired twice in a row. Anyway, put this in the sheet code for the sheet that is "Show"ing the userform:
userformXXX.listboxYYY.ListIndex = -1
userformXXX.Show
This doesn't work if it is in the userform code.
I have a worksheet that runs a weightlifting meet. Last year I had created a tab that would give information on the current lifter and on who was lifting next.
left side - Display, right side - input tab
When I input an "x" in columns R, S, T, or W on the data tab, it changes the information in the BenchGenerator tab, like so:
Updated Display tab
What I want to do is make a userform display to run on a different screen so people can see this information. I had accomplished this last year by widening excel and using two view windows - display on the second screen and running the meet on the computer. It was ok but very clunky looking. With a floating userform tab, it would look fantastic. I am new to this, but got the form floating:
Private Sub Worksheet_Change(ByVal Target As Range)
UserForm1.Show (vbModeless)
End Sub
And got the labels to initially populate:
Userform Display
Using this code:
Private Sub UserForm_Activate()
UserForm1.Label1.Caption = Sheets("BenchGenerator").Range("c4").Value
UserForm1.Label2.Caption = Sheets("BenchGenerator").Range("c5").Value
UserForm1.Label3.Caption = Sheets("BenchGenerator").Range("c6").Value
UserForm1.Label4.Caption = Sheets("BenchGenerator").Range("d3").Value
UserForm1.Label5.Caption = Sheets("BenchGenerator").Range("d4").Value
UserForm1.Label6.Caption = Sheets("BenchGenerator").Range("d5").Value
UserForm1.Label7.Caption = Sheets("BenchGenerator").Range("d6").Value
End Sub
What it doesn't currently do is update the captions when I input the "x" in the data tab.
As I mentioned, this is my first foray into userforms and looking through mountains of code trying to figure this out, it will not be my last as there is lots to accomplish with them.
Thanks in advance for any help!
You're pretty close to getting this to work. The problem is that your calling a new form every time a change occurs.
Declare your form as an object outside of the Sub that creates (Show) it.
You can then access it to update labels from another Sub that has the same scope.
Create an UpdateForm sub for example and call it from your Worksheet_Change event.
Try this, place the following code in a new Module:
Dim myForm As Object
Sub launchForm()
Set myForm = UserForm1
myForm.Show (vbModeless)
End Sub
Sub updateForm()
Dim wks As Worksheet
Set wks = Sheets("BenchGenerator")
'Update label values here
myForm.Label1.Caption = wks.Range("C4").Value
myForm.Label2.Caption = wks.Range("C5").Value
myForm.Label3.Caption = wks.Range("C6").Value
myForm.Label4.Caption = wks.Range("D3").Value
myForm.Label5.Caption = wks.Range("D4").Value
myForm.Label6.Caption = wks.Range("D5").Value
myForm.Label7.Caption = wks.Range("D6").Value
End Sub
If you use Worksheet_Change to update the form you'll want to verify the form exist or just skip any errors in the event if it doesn't.
Private Sub Worksheet_Change(ByVal Target As Range)
On Error Resume Next
updateForm
End Sub
Not sure if there is an easier way to do it, but I made a concatenation routine to batch out my label setup and updates to quickly create/copy/paste all the code.
Just in case anyone didn't know how to do this