Accessing a form button - excel

I am trying to access a form button...as a test. But when I do so like this; I get the error that it doesn't support this property or method? What is the proper way to go about accessing a button like this?
With ActiveSheet.Shapes("FH_btnHideShowCNC")
MsgBox .Caption
Exit Sub
End With

Because ActiveSheet isn't necessarily a Worksheet, that property wraps your sheet in an Object, and then you basically have to guess what the correct incantations are and cross your fingers that there aren't any typos (Option Explicit can't save you from late-bound code).
Restore early binding as soon as possible, don't chain member calls to an Object or Variant.
Dim sheet As Worksheet
Set sheet = ActiveSheet
With sheet.Shapes("FH_btnHideShowCNC")
'Shapes.Item(String) returns a Shape object, so we're early-bound now.
'...
End With
Now that the interface of the With block variable is known at compile-time, you'll get the list of available members, and the code will blow up at compile-time if you try to invoke a member that doesn't exist (like .Caption), rather than blow up at run-time.

Is this what you are trying?
With ActiveSheet.Shapes("FH_btnHideShowCNC")
MsgBox .TextFrame.Characters.Text
Exit Sub
End With
And MsgBox .Name will give you the name of the button which is FH_btnHideShowCNC

Related

Public variable to save the sheet last clicked on

I have a workbook that has several sheets that contain cells that open up the same userform to be filled out.
Currently, closing the form returns the workbook to one page in particular.
I am trying to return to the page that was previously being worked on.
I set up a public worksheet variable (wsWorking) that would be set to the sheet last clicked on before opening the UserForm.
I get:
Run Time Error '9': Subscript out of range
and the debug message shows my wsWorking variable as empty.
If I put in the name of a sheet instead of trying to use the variable, I can open it to the page I want.
Declaration: '(in my ThisWorkbook module, (General) (Descriptions)
Public wsWorking As Worksheet
Attempted Set: '(In a private module, extraneous code removed)
Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Set wsWorking = ThisWorkbook.Worksheets("MRL 1")
Dim clickRow As Integer
Dim ClickCol As Integer
Attempted Use: (in a different, private module)
Private Sub CloseForm_Click()
Call SaveFormToScorecard_Click
Unload Me
ActiveWorkbook.Sheets(wsWorking).Activate
End Sub
I tried setting the wsWorking in the general declarations for the module, when the worksheet is activated, and created a public sub in the module solely for the purpose of setting that variable, and none of it works.
Changing
ActiveWorkbook.Sheets(wsWorking).Activate
to
wsWorking.Activate
gives me a new error
Run-Time Error '424': Object Required
I don't believe the setting of the variable in the one module is transferring to the other one.
ThisWorkbook is a class module with a PredeclaredId attribute that makes it accessible from anywhere, but its members are still its members. You can access any public member of ThisWorkbook from anywhere in your code, by qualifying it with the predeclared ThisWorkbook object:
Debug.Print ThisWorkbook.wsWorking.Name
If you want a global variable, then you cannot use an object module, because objects require an instance (the ThisWorkbook instance being automagically created doesn't make it any less of an object instance). Declare a public variable in a standard module instead, and then you can access it unqualiifed from anywhere in your code, whether to read it or to write it.
Wait. Say this out loud.
and then you can access it unqualiifed from anywhere in your code, whether to read it or to write it.
This probably isn't a good idea. Consider declaring it Private, and only exposing it as a Public Property Get so that it cannot be overwritten at any time by anything anywhere.
This should work if it's Public in ThisWorkbook:
Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Set ThisWorkbook.wsWorking = ThisWorkbook.Worksheets("MRL 1")
Dim clickRow As Integer '<~ this will explode at row 32,768. Use a Long!
Dim ClickCol As Integer '<~ should still be a Long
A few severe issues here:
Call SaveFormToScorecard_Click '<~ event handlers aren't supposed to be invoked like this
Unload Me '<~ self-destructing object, danger!
ActiveWorkbook.Sheets(wsWorking).Activate '<~ will throw error 1004 End Sub ```
ActiveWorkbook is whatever workbook happens to be active at the time, and while that's probably ThisWorkbook, there's a good chance it's not. And whenever it isn't, this will blow up. wsWorking is a Worksheet reference, it's redundant to dereference it (by name, implicitly!) from a Sheets collection - just ThisWorkbook.wsWorking.Activate would do.
Note that if wsWorking exists in ThisWorkbook at compile-time, you're probably better off just using its own predeclared instance.
Every Worksheet module has a (Name) property that is the name of the VB project component; this value becomes the name of a global object, just like ThisWorkbook. See implicit containing workbook reference Rubberduck inspection for more info.

How to Use the Find Function

I have recently Written some code to take an input from a userform text box and search for it in my database. If found I would like it to return the value and insert it into cell A1149; I have written the below code but it gives me error #424 "Object Required". I am very new to VBA so any and all help is greatly appreciated.
Private Sub CMDSearch_Click()
Dim pesquisa As Range
Set pesquisa = Worksheets("Petrobras").Activate.Range("$W:$W") _
.Find(What:=Opp_Num_Search.Value, LookIn:=xlValues, Lookat:=xlWhole).Activate
Worksheets("Petrobras").Range(A1149).Value = pesquisa.Value
UserForm1.Hide
End Sub
Range.Activate doesn't return anything, it's like a Sub procedure:
Public Sub DoSomething()
' does something...
End Sub
If you did Set foo = DoSomething.Something, you'd get the same error: an object is required, otherwise that .Something member call is illegal (except now the error would be at compile-time and not run-time, because of how binding works).
Set pesquisa = Worksheets("Petrobras").Activate...
You don't want to Activate any sheets.
Part of the problem is the implicit late-binding going on: Worksheets returns an Object, so everything you wrote after that, all these chained member calls, can only be resolved at run-time.
Make it early-bound, by declaring an explicit Worksheet variable:
Dim sheet As Worksheet
Set sheet = Worksheets("Petrobras")
Now if you want to activate it, you can do sheet.Activate (but you don't need to). And if you want to get a Range from that worksheet, you can make a .Range member call, and the IDE will now help you do it:
Dim result As Range
Set result = sheet.Range("$W:$W").Find(...)
NEVER chain any member calls to what Range.Find returns. If the search turned up a result, you have a Range object. If it didn't, you have Nothing - and any member call made against Nothing will always raise run-time error 91.
Validate the search result first:
If result Is Nothing Then
MsgBox "Could not find '" & Opp_Num_Search.Value & "' in column W."
Exit Sub
End If
Or:
If Not result Is Nothing Then
sheet.Range("A1149").Value = result.Value
End If
Note that A1149 is a string literal representing a cell address, and as such it must be surrounded with double quotes ("). If it's not in double quotes and it looks like a valid variable name, VBA will treat it as ..a variable... and that will cause yet another error (1004), because Range will be rather unhappy to work with a Variant/Empty value.
To prevent VBA from "declaring" on-the-fly variables with a typo (and causing hard-to-find bugs), make sure you have Option Explicit at the very top of every module in your project.
One last thing:
UserForm1.Hide
This hides the default instance of UserForm1, which may or may not be the current object / instance that's shown - and the form itself has no way to know how it was shown:
UserForm1.Show '<~ shows the default instance
With New UserForm1
.Show '<~ shows a new (non-default) instance
End With
For this reason, you should avoid referring to the default instance in a form's code-behind. Use Me instead:
Me.Hide
That way you're referring to whatever instance of the form is currently shown, whether that's the default instance or not. See UserForm1.Show for more information, tips, pitfalls and common mistakes involving userforms.
Note that Rubberduck has quite a few inspections that can identify and warn you about (and often, automatically fix) most of these problems. Rubberduck is a free and open-source VBIDE add-in project I manage.

I'm struggling to get the value from a datepicker into a variable

I have a tool with Sub that saves and send the workbook. That all works fine but I want to add the date from the datepicker on the front page of the tool to the subject of the email. For this, I need to get that date into a variable as a string.
I've looked up a few methods and pasted a few of them below but nothing is working. Both have been cribbed from other threads where the OP was happy with the result and worked for them, so I'm unsure why it's not working for me.
Private Sub testdateGet()
Worksheets("TEST").Range("A7").Value = DTPicker1.Value
End Sub
Public Sub dateGet()
Dim dateVal2 As String
dateVal2 = DTPicker1.Value
Worksheets("TEST").Range("A7").Value = dateVal2
End Sub
The first method results in an error 'Run-time error '424' Object required but I'm not sure what that means. The second method runs without error but doesn't paste anything in the designated cell.
Is there a specific place I have to put the code for it to work as intended? Would I be able to set a public variable for it to update, that can then be used in another module?
Error 424 is a tell-tale sign that you're not specifying Option Explicit, and so the DTPicker1 identifier is just an undefined Variant/Empty - and you can't make a member call against a Variant/Empty, an object is required.
Where is the DatePicker control? On the TEST sheet? Get the control from the sheet's Shapes collection:
Private Property Get DatePickerControl() As DTPicker
Dim oleObj As OLEObject
Set oleObj = Worksheets("TEST").Shapes("DTPicker1").OLEObject
Set DatePickerControl = oleObj.Object
End Property
Now you can access it from anywhere in the module:
Public Sub DateGet()
With Worksheets("TEST").Range("A7")
.Value = DatePickerControl.Value
.NumberFormat = "yyyy-mm-dd"
End With
End sub
Or, if the sheet exists in ThisWorkbook at compile-time, get it from the sheet directly:
Worksheets("TEST").Range("A7").Value = Sheet1.DTPicker1.Value
You can set the sheet's (Name) property (here "Sheet1") in the properties toolwindow (F4); that makes the name identifier accessible (from anywhere in the VBA project) to refer to that particular sheet.
Assuming you have put up a DatePicker similar as stated here.
Open the editor and open the worksheet code (if the datapicker is located on Sheet1 you should open that sheet) and select the datepicker from the object selection menu (it states (General) by default).
In the drop down menu top right you can select the different actions a user can perform on the datepicker. In this case select Click.
Now add some code
Private Sub DTPicker1_Click()
Foo = DTPicker1.Value
End Sub
Every time a click is made on the DatePicker it's value is passed to Foo. You can insert a Cell here or pass it as an argument to another function.

VBA - Method 'List' of objectr '_CommandBarComboBox' failed

I have a sub in VBA that calls another sub to perform some analysis on data if the user pastes it in. The code below errors as:
Run-time error'-2147467259 (80004005)':
Method 'List' of object'_CommandBarComboBox' failed
Private Sub Worksheet_Change(ByVal Target As Range)
Dim UndoList As String
UndoList = Application.CommandBars("Standard").Controls("&Undo").List(1) 'Errors Here!
If Left(UndoList, 5) = "Paste" Then
Dim Annotations() As String
FindAnnots
End If
End Sub
Any idea as to why the object's list doesn't exist?
After using Bryan's answer about it being a CommandBarComboBox, I think you can check its enabled status to see if a list exists. Its the only way I've got it to work without using On Error Resume Next.
Use the following loop to capture the status of the ComboBox
Dim UndoList As String
If Application.CommandBars("Standard").Controls("&Undo").Enabled = True Then
UndoList = Application.CommandBars("Standard").Controls("&Undo").List(1)
If Left(UndoList, 5) = "Paste" Then
'Code to run here after paste action'
End If
End If
Short Answer: The .List isn't there yet because the user hasn't performed any actions that get saved in the Undo queue. The code is trying to access something that hasn't been created yet. I had to create an error handler for it.
Long Answer: In my search, I found that the Controls object in VBA doesn't officially have a List property. However, Controls("&Undo") Is not a Controls object. It's a CommandBarComboBox.
So in
Application.CommandBars("Standard").Controls("&Undo").List(1)
The .List property of Controls("&Undo") doesn't actually show up in the Excel VBA intellisense. It's looking at the Controls object for it's intellisense drop-down. However, if you try
? TypeName(Application.CommandBars("Standard").Controls("&Undo"))
In the immediate window, you'll see that it's of the type CommandBarComboBox, which does have a list property. However, like other ComboBox style controls, no list is created until a list member is added. In this case, when the user performs an action worthy of being stored in the Undo queue.

Modifying a spreadsheet using a VB macro

I have two spreadsheets... when one gets modified in a certain way I want to have a macro run that modifies the second in an appropriate manner. I've already isolated the event I need to act on (the modification of any cell in a particular column), I just can't seem to find any concrete information on accessing and modifying another spreadsheet (this spreadsheet is located on a different LAN share also... the user has access to both, though).
Any help would be great. References on how to do this or something similar are just as good as concrete code samples.
In Excel, you would likely just write code to open the other worksheet, modify it and then save the data.
See this tutorial for more info.
I'll have to edit my VBA later, so pretend this is pseudocode, but it should look something like:
Dim xl: Set xl = CreateObject("Excel.Application")
xl.Open "\\the\share\file.xls"
Dim ws: Set ws = xl.Worksheets(1)
ws.Cells(0,1).Value = "New Value"
ws.Save
xl.Quit constSilent
You can open a spreadsheet in a single line:
Workbooks.Open FileName:="\\the\share\file.xls"
and refer to it as the active workbook:
Range("A1").value = "New value"
After playing with this for a while, I found the Michael's pseudo-code was the closest, but here's how I did it:
Dim xl As Excel.Application
Set xl = CreateObject("Excel.Application")
xl.Workbooks.Open "\\owghome1\bennejm$\testing.xls"
xl.Sheets("Sheet1").Select
Then, manipulate the sheet... maybe like this:
xl.Cells(x, y).Value = "Some text"
When you're done, use these lines to finish up:
xl.Workbooks.Close
xl.Quit
If changes were made, the user will be prompted to save the file before it's closed. There might be a way to save automatically, but this way is actually better so I'm leaving it like it is.
Thanks for all the help!
Copy the following in your ThisWorkbook object to watch for specific changes. In this case when you increase a numeric value to another numeric value.
NB: you will have to replace Workbook-SheetChange and Workbook-SheetSelectionChange with an underscore. Ex: Workbook_SheetChange and Workbook_SheetSelectionChange the underscore gets escaped in Markdown code.
Option Explicit
Dim varPreviousValue As Variant ' required for IsThisMyChange() . This should be made more unique since it's in the global space.
Private Sub Workbook-SheetChange(ByVal Sh As Object, ByVal Target As Range)
' required for IsThisMyChange()
IsThisMyChange Sh, Target
End Sub
Private Sub Workbook-SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
' This implements and awful way of accessing the previous value via a global.
' not pretty but required for IsThisMyChange()
varPreviousValue = Target.Cells(1, 1).Value ' NB: This is used so that if a Merged set of cells if referenced only the first cell is used
End Sub
Private Sub IsThisMyChange(Sh As Object, Target As Range)
Dim isMyChange As Boolean
Dim dblValue As Double
Dim dblPreviousValue As Double
isMyChange = False
' Simple catch all. If either number cant be expressed as doubles, then exit.
On Error GoTo ErrorHandler
dblValue = CDbl(Target.Value)
dblPreviousValue = CDbl(varPreviousValue)
On Error GoTo 0 ' This turns off "On Error" statements in VBA.
If dblValue > dblPreviousValue Then
isMyChange = True
End If
If isMyChange Then
MsgBox ("You've increased the value of " & Target.Address)
End If
' end of normal execution
Exit Sub
ErrorHandler:
' Do nothing much.
Exit Sub
End Sub
If you are wishing to change another workbook based on this, i'd think about checking to see if the workbook is already open first... or even better design a solution that can batch up all your changes and do them at once. Continuously changing another spreadsheet based on you listening to this one could be painful.

Resources