Using Select Case in Excel VBA To Copy Worksheet from Closed Workbook - vba

Hello I am trying to write an excel VBA script that will copy a specific worksheet from a closed workbook to the active workbook. The sheet to copy is determined by a checkbox and the value of a variable that is set to be the text in a specific cell. My code:
Sub copysheet()
Dim WB, ClosedWB as Workbook
Set CheckBox = ActiveSheet.Shapes("CheckBox").DrawingObject
Model = ActiveSheet.Range("K3").Text
Const FilePath = "C:\..."
Set WB = Application.Workbooks.Open(FilePath, UpdateLinks:=False, ReadOnly:=False, AddToMRU:=False)
If CheckBox.Value = xlOn Then
With ClosedWB
Select Case Model.Text
Case Model = Sheet1
Sheets("Sheet 1 Name").Copy After:=Workbooks(WB).Sheets(Workbooks(WB).Sheets.Count)
Case Model = Sheet2
Sheets("Sheet 2 Name").Copy After:=Workbooks(WB).Sheets(Workbooks(WB).Sheets.Count)
End Select
End With
End If
If CheckBox.Value = xlOff Then
With ClosedWB
Select Case Model.Text
Case Model = Sheet3
Sheets("Sheet 3 Name").Copy After:=Workbooks(WB).Sheets(Workbooks(WB).Sheets.Count)
Case Model = Sheet4
Sheets("Sheet 4 Name").Copy After:=Workbooks(WB).Sheets(Workbooks(WB).Sheets.Count)
End Select
End With
End If
I am getting a Run-Time error '424': Object Required and it highlights the Select Case Model.Text line. I have also tried Select Case Model.Value, but that didn't work. If I create a MsgBox to display the variable model, then it displays whatever is written in the appropriate cell, so the variable value is being stored. Please help I am new at this.
Edit: Modifying Select Case Model.Text to just Select Case Model no longer results in a run-time error, but the sheets will not copy. When I run the script, it opens up the new workbook (ClosedWB), but does nothing after that. It doesn't even select the sheet that I specified it to. How can I get this to work?

Model is a string since you assigned it ActiveSheet.Range("K3").Text. I strongly suggest you insert Option Explicit at the top of your module, and always compile before running. (Tools, Options, Require variable Declaration will do it automatically in every new module).
Select Case Model

You are also trying to compare a string to a sheet. Either you need to put the sheet# part in quotes if that is the text that is being entered or use sheet1.name.

Related

Why does assigning a reference in my spreadsheet sometimes work and sometimes not?

I have a few cells in my excel workbook which are available for a client to put his own values. I wanted the workbook to initialize those cells with default values. In order to do so I have a worksheet "Arkusz do makr", where I store the values.
In a module "GM" I declare a variable to reference my worksheet easier like this:
Public M As Worksheet
Then I initialize this variable and set my default values like this (in ThisWorkbook):
Private Sub Workbook_Open()
Set M = Worksheets("Arkusz do makr")
Worksheets("Values").Range("Value1") = M.Range("Value1")
Worksheets("Values").Range("Value2") = M.Range("Value2")
Worksheets("Values").Range("Value3") = M.Range("Value3") `etc
End Sub
Now sometimes this works like a charm, and sometimes, when I open the workbook I get a
Run-time error '91': Object variable or With block variable not set.
Could someone please explain this behaviour to me? Additionally I would like to ask if my approach makes sense, since I have a hard time grasping the order of events in excel as well as the range of its objects.
EDIT: Additionally I should mention that the Debug function highlights the first Worksheets... line in my code. In specific worksheets I reference the M object as well, though I thought it changes anything here...
Try to change the code of this Sub like below.
I have added a simple error handling - if there is no worksheet "Arkusze do makr" or "Values" in your workbook, warning message is displayed and default values are not copied.
You can find more comments in code.
Private Sub Workbook_Open()
Dim macrosSheet As Excel.Worksheet
Dim valuesSheet As Excel.Worksheet
'------------------------------------------------------------------
With ThisWorkbook
'This command is added to prevent VBA from throwing
'error if worksheet is not found. In such case variable
'will have Nothing as its value. Later on, we check
'the values assigned to those variables and only if both
'of them are different than Nothing the code will continue.
On Error Resume Next
Set macrosSheet = .Worksheets("Arkusz do makr")
Set valuesSheet = .Worksheets("Values")
On Error GoTo 0 'Restore default error behaviour.
End With
'Check if sheets [Values] and [Arkusz do makr] have been found.
'If any of them has not been found, a proper error message is shown.
'In such case default values are not set.
If valuesSheet Is Nothing Then
Call VBA.MsgBox("Sheet [Values] not found")
ElseIf macrosSheet Is Nothing Then
Call VBA.MsgBox("Sheet [Arkusz do makr] not found")
Else
'If both sheets are found, default values are copied
'from [Arkusz do makr] to [Values].
'Note that if there is no Range named "Value1" (or "Value2" etc.)
'in any of this worksheet, another error will be thrown.
'You can add error-handling for this case, similarly as above.
With valuesSheet
.Range("Value1") = macrosSheet.Range("Value1")
.Range("Value2") = macrosSheet.Range("Value2")
.Range("Value3") = macrosSheet.Range("Value3")
End With
End If
End Sub

User Defined Function to VLookup into Closed Workbook

I'm attempting to create a User Defined Function to vlookup into a closed workbook on my machine. The below function works while testing it in VBA, but I get the #VALUE error in Excel when attempting to use the function. Any ideas on this? I believe I may be able to use the VBA Evaluate function to help, but so far have had no luck.
Function CUSIP_Deal_Map(CUSIP As String, DataField As String) As Variant
Dim colIndex As Integer ' for vlookup
Dim invalidDataField As Boolean
invalidDataField = False
' Switch statement, to transform from a "DataField" into a column number to be used in VLookUp
Select Case DataField
Case "Deal"
colIndex = 2
Case "Class"
colIndex = 5
Case "DealNum"
colIndex = 6
Case "Vintage"
colIndex = 11
Case "Pool"
colIndex = 12
Case "Index"
colIndex = 13
Case Else
invalidDataField = True
End Select
'Dim wbk As Workbook
Set wbk = Workbooks.Open("C:\CUSIP_Map.xlsx") 'hard code location
Dim VLU_data As Variant
VLU_data = wbk.Application.WorksheetFunction.VLookup(CUSIP, Worksheets("CUSIP_Map").Range("A:M"), colIndex, False) 'vlookup data from "database"
Call wbk.Close(False) 'close connection
' Return data
If invalidDataField Then
CUSIP_Deal_Map = "Invalid DataField"
Else
CUSIP_Deal_Map = VLU_data
End If
End Function
The intended use in Excel would be to utilize a formula like =CUSIP_Deal_Map("123ABC","Deal")
I can test this in VBA, using this code, which returns the value I'm expecting:
Sub test()
MsgBox CUSIP_Deal_Map("123ABC", "Deal")
End Sub
Still, this doesn't work within Excel itself. I found a "pull" UDF online which seems to do something similar, but have been unsuccessful modifying it for my purposes.
That is because a UDF() call from within a Sub can open a file, but the same UDF() called from a worksheet cell cannot.
EDIT#1:
insure the UDF is in a standard module.
at the very top of the module include Public wbk as Workbook
create a workbook Open event macro in your workbook code area to open the secondary workbook and initialize wbk
OK, with the limitation already brokered by Gary's Student, you may want to rethink the idea of a UDF altogether. With those values from the Select Case statement in the first row of the closed CUSIP_Map worksheet, either of these standard worksheet formulas will do.
=VLOOKUP(A1, 'C:\[CUSIP_Map.xlsx]CUSIP_Map'!$A:$M, MATCH("Vintage", 'C:\[CUSIP_Map.xlsx]CUSIP_Map'!$1:$1, 0), FALSE)
=VLOOKUP(A1, 'C:\[CUSIP_Map.xlsx]CUSIP_Map'!$A:$M, LOOKUP("Class", {"Class","Deal","DealNum","Index","Pool","Vintage"}, {5,2,6,13,12,11}), FALSE)
A1 would be value to look up in the CUSIP_Map's column A. Instead of a VBA select case, the column to return is determined by either a MATCH function of the first row column headers or a hard-coded LOOKUP function of text and column numbers. Note that the LOOKUP has its values in ascending order and that it may not have as much error control as the MATCH as it will attempt partial matches. An IFERROR function as a wrapper can return "Invalid DataField" on MATCH errors.

How can I pick values from an Excel workbook and return them by function on active workbook

My goal is to implement some of functions where I give them parameters of power, frequency and speed of an electric motor, and look in another workbook (in which I have motor data) and return the size, shaft diameter and other motor details.
As I have not mastered much VBA I tried to implement a function that simply goes to a cell in another workbook and returns the value:
Function Test() As String
Dim name As String
With Workbooks.Open("D:\ExcelTest\WbSource.xlsm").Sheets("Sheet1")
name = .Cells(2, 3)
End With
Test= name
ActiveWorkbook.Save
ActiveWorkbook.Close
End Function
The problem is that it gives me a #VALUE! error, but each variable used is defined as a string and the cells has general format (if I change cells format to text it gives me the same message).
Try as I might, I could not get workbooks.open to work in a function, even if the function calls a sub. You could open the catalogue file in the workbook open event, and close it again in the before close event.
In the VProject Explorer, right click on "ThisWorkBook," and "View code".
In the pick list at the top, select Workbook, and the sub Workbook_open() procedure should be created. If not, select "Open" in the right pick list. Put in the following:
Application.Workbooks.Open ("D:\ExcelTest\WbSource.xlsm")
ThisWorkbook.Activate 'restores the "focus" to your worksheet
Then click the right pick list and select "beforeClose" and put in
On Error Resume Next 'this keeps it from crashing if the catalogue is closed first
Workbooks("WbSource.xlsm").Close
As long as the worksheet opens the wbsource file first, the function will work.
Here is an approach with scheduling UDF execution in queue, and processing outside UDF that allows to get rid of UDF limitations. So the value from the closed workbook got via ExecuteExcel4Macro() by a link.
Put the following code into one of the VBAProject Modules:
Public Queue, QueueingAllowed, UDFRetValue
Function UDF(ParamArray Args())
If IsEmpty(Queue) Then
Set Queue = CreateObject("Scripting.Dictionary")
UDFRetValue = ""
QueueingAllowed = True
End If
If QueueingAllowed Then Queue.Add Application.Caller, (Args)
UDF = UDFRetValue
End Function
Function Process(Args)
If UBound(Args) <> 4 Then
Process = "Wrong args number"
Else
' Args(0) - path to the workbook
' Args(1) - filename
' Args(2) - sheetname
' Args(3) - row
' Args(4) - column
On Error Resume Next
Process = ExecuteExcel4Macro("'" & Args(0) & "[" & Args(1) & "]" & Args(2) & "'!R" & Args(3) & "C" & Args(4))
End If
End Function
Put the following code into ThisWorkbook section of VBAProject Excel Objects:
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
Dim Item, TempFormula
If Not IsEmpty(Queue) Then
Application.EnableEvents = False
QueueingAllowed = False
For Each Item In Queue
TempFormula = Item.FormulaR1C1
UDFRetValue = Process(Queue(Item))
Item.FormulaR1C1 = TempFormula
Queue.Remove Item
Next
Application.EnableEvents = True
UDFRetValue = ""
QueueingAllowed = True
End If
End Sub
After that you can get the values from closed workbook via worksheet formula using UDF:
=UDF("D:\ExcelTest\";"WbSource.xlsm";"Sheet1";2;3)
Anyway you can add Workbooks.Open() or any other stuff into Function Process(Args) to make it to work the way you want. The code above is just an example.
I've answered the similar questions here and here, so that descriptions might be helpful.
I suggest:
open WbSource.xlsm either manually or via VBA outside the UDF.
pass the parameters to the UDF
have the UDF search down the columns of the newly opened workbook to find the correct record
have the UDF pass the row number back to the worksheet
in the worksheet, use Match()/Index() formulas to retrieve other data.

How to refer to shapes in a module

I have created different shapes in excel and have assigned a macro to it which functions as activating another sheet. I want to put all these under one macro and then assign it to different shapes with different linking property. But this code doesn't work because obviously I am doing something stupid. Can someone please help?
Dim shp As ShapeRange, ws As Sheets, i As Integer
Set ws = ActiveWorkbook.Sheets(Array("Introduction", "S1 Fuel Consumption", "S1 Fugitive", "S2 Electricity Consumption"))
Set shp = ws(2).Shapes.Range(Array("Chevron1", "Chevron2"))
Select Case shp(i)
Case shp(1)
ws(1).Activate
Case shp(2)
ws(3).Activate
End Select
End Sub
There is a much easier way to do "buttons" in VBA (I assume this is what your trying to achieve)
First off, in a module, create the "Open Worksheet" code:
Sub Open_Sheet2
Sheets("Sheet2").visible = True
Sheets("Sheet2").Activate
End Sub
Then right click your shape, choose Assign Macro and assign Open_Sheet2 to that shape. Now when it is clicked, it will open Sheet2

How to add a new spreadsheet with VBA-Code, using VBA

I am creating a macro and part of the macros function is to make VBA create a new spreadsheet. Because of the nature of distribution the name will change. I need to add code to this spreadsheet. Is there anyway I can do this?
Jook has already explained how it works. I will take it a step further.
The syntax of adding a worksheet is
expression.Add(Before, After, Count, Type)
If you check inbuilt Excel's help then you can see what Before, After, Count, Type stands for
FROM EXCEL"S HELP
Parameters (All 4 parameters are Optional)
Before - An object that specifies the sheet before which the new sheet is added.
After - An object that specifies the sheet after which the new sheet is added.
Count - The number of sheets to be added. The default value is one.
Type - Specifies the sheet type. Can be one of the following XlSheetType constants: xlWorksheet, xlChart, xlExcel4MacroSheet, or xlExcel4IntlMacroSheet. If you are inserting a sheet based on an existing template, specify the path to the template. The default value is xlWorksheet.
Once the sheet is created then you need to use .insertlines to create the relevant procedure and to also embed the code that you want to run.
NOTE - IMP: If you want the code to embed code in the VBA project, you need to ensure that you have "Trust Access to the VBA Project Object Model" selected. See snapshot.
Here is an example where I am creating a sheet and then embedding a Worksheet_SelectionChange Code which will display a message "Hello World"
CODE - TRIED AND TESTED
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim nLines As Long
Dim VBP As Object, VBC As Object, CM As Object
Dim strProcName As String
Set ws = Worksheets.Add
Set VBP = ThisWorkbook.VBProject
Set VBC = VBP.VBComponents(ws.Name)
Set CM = VBC.CodeModule
strProcName = "Worksheet_SelectionChange"
With ThisWorkbook.VBProject.VBComponents( _
ThisWorkbook.Worksheets(ws.Name).CodeName).CodeModule
.InsertLines Line:=.CreateEventProc("SelectionChange", "Worksheet") + 1, _
String:=vbCrLf & _
" Msgbox ""Hello World!"""
End With
End Sub
This is how the new sheet code area looks once you run the above code.
the following code will add you a spreadsheet.
Public Sub Workbook_Add()
Dim wks As Worksheet
Set wks = ThisWorkbook.Worksheets.Add(, , 1, xlWorksheet)
With wks
'set codename of wks
ThisWorkbook.VBProject.VBComponents(.CodeName).Name = "tblWhatever"
'set tablename of wks
.Name = "whatever"
'add code (untested demo)
'ThisWorkbook.VBProject.VBComponents(.CodeName).CodeModule.InsertLines 1, "Option Explicit"
'add code (as of example from excel-help)
'Application.VBE.CodePanes(1).CodeModule.InsertLines 1, "Option Explicit"
End With
End Sub
If you need to add VBA-Code to this specific spreadsheet, you should further inspect the VBProject object - look for CodeModule and then i.e. InsertLines.
A further hint for you - I would try to use the CodeNames of your tables. It is less likely to be changed - BUT it might be not that comfortable to use in your code at first. I had to get used to it, but for me it has many advantages against using a tables name.
Hope this helps ;)
The default .Add method adds a sheet at the start of the list. Often you want to add it at the end before adding the code lines, as explained by Siddarth Rout. To do that anywhere you can use:
ActiveWorkbook.Worksheets.ADD After:=ActiveWorkbook.Sheets(ActiveWorkbook.Worksheets.Count)
It is easier to read if you have defined and set WB:
Dim WB as Excel.workbook
Set WB = ActiveWorkbook
WB.Sheets.ADD After:=WB.Sheets(WB.Sheets.Count)
Set VBC = ActiveSheet 'If using in Siddarth Rout's code above
Sheets and Worksheets are interchangeable, as illustrated.