Cell value of B6 = 'Trading Income
=VLOOKUP(B6,'\\myComp.myComp.com\abc\Treas\P&L\data\[DataUS.xls]Smith, Bob'!$A$1:$D$2000,2,FALSE)
This returns (5,555,529.00)
However, say I wanted to place
'\\myComp.myComp.com\abc\Treas\P&L\data\[DataUS.xls]Smith, Bob'!$A$1:$D$2000
in a cell (Lets say B7). How would I structure the VLOOKUP?
I tried:
VLOOKUP(B6, B7, 2, FALSE)
And it returns #N/A
Thank you
Try
=VLOOKUP(B6;'\\myComp.myComp.com\abc\Treas\P&L\data\[DataUS.xls]Smith, Bob'!$A$1:$D$2000;2;FALSE)
You have to use INDIRECT, see this.
In your case, use INDIRECT(B7) instead of B7.
Try this:
=VLOOKUP(B6, INDIRECT(B7), 2, FALSE)
Just ensure that cell B7 contains the exact path i.e. written exactly in the same manner it is written in the formula.
Now adding macros doe
sn't really increase the level of complexity, at least I have not seen any case as yet. On the contrary a macro, if done correctly, increase efficiency,
dependency and certainty of the results.
I suggest this solution which includes macros, provides the simplicity and flexibility of building the link to external data using a Name to hold the External Reference created using user input, which is split in the different parts of the external link to make it easier changes i.e. Path, Filename, Worksheet and Range.
This includes the creation of five Names to handle the linked formula, here is when you might feel like you got it right when mentioning “increasing the level of complexity”, however we can use the power and flexibility of macros not only to produce the expected outcome in a report, analysis, etc.; but also to build forms, reports, graphs, data, etc. and by using macros it also eliminates the need for insanity checks, which at times make us insane, providing and excellent tool to reinstate, review and even change the parameters of large projects when required.
The code provided below includes the creation of the Names, also the refresh the Name that holds the External Link Reference once the user changes any part of the external reference in the worksheet.
First we run this code to create the names (copy this in a module)
Option Explicit
Option Base 1
Sub FmlLnk_WshAddNames()
Const kRowIni As Byte = 2
Const kCol As Byte = 3
Const kWshTrg As String = "Sht(1)"
Dim aNames As Variant
aNames = fNames_Get
Dim WshTrg As Worksheet
Dim bRow As Byte
Dim b As Byte
Set WshTrg = ThisWorkbook.Worksheets(kWshTrg)
With WshTrg
For b = 1 To UBound(aNames)
bRow = IIf(b = 1, kRowIni, 1 + bRow)
.Names.Add Name:=aNames(b), RefersTo:=.Cells(bRow, kCol)
.Names(aNames(b)).Comment = "Name to create link to external range"
Next: End With
End Sub
Function fNames_Get() As Variant
fNames_Get = Array("_Path", "_Filename", "_Worksheet", "_Range")
End Function
Now that the Names to hold the parts of the external link are created we add the worksheet event to automatically update the name holding the External Link Reference (see https://msdn.microsoft.com/EN-US/library/office/ff198331.aspx)
To go to the event procedures for the Worksheet that contains the formula right-click the sheet tab and click “View Code” on the shortcut menu.
Copy the code below in the Worksheet code
Option Explicit
Option Base 1
Private Sub Worksheet_BeforeDoubleClick(ByVal RngTrg As Range, bCancel As Boolean)
Const kFmlLnk As String = "_FmlLnk"
Dim aNames As Variant, vName As Variant
aNames = fNames_Get
Dim WshThs As Worksheet
Dim bLnkExt As Boolean
Dim sLnkExt As String
Set WshThs = RngTrg.Worksheet
With WshThs
Application.Goto .Cells(1), 1
Rem Validate ActiveCell
bLnkExt = False
For Each vName In aNames
If .Names(vName).RefersToRange.Address = RngTrg.Address Then
bLnkExt = True
Exit For
End If: Next
Rem Reset Name Link External
If bLnkExt Then
Rem Built External Formula Link
sLnkExt = "=" & Chr(39) & .Names(aNames(1)).RefersToRange.Value2 & _
"[" & .Names(aNames(2)).RefersToRange.Value2 & "]" & _
.Names(aNames(3)).RefersToRange.Value2 & Chr(39) & Chr(33) & _
.Names(aNames(4)).RefersToRange.Value2
Rem Add External Formula Link Name
.Names.Add Name:=kFmlLnk, RefersTo:=sLnkExt
.Names(kFmlLnk).Comment = "Name to link external range in Formula"
End If: End With
End Sub
This procedure will run every time the users double-clicks in any of the four Names created in the worksheets that holds the External Link Formula
The Formula to use the external link name is:
=VLOOKUP($B9,_FmlLnk,3,0)
Related
I'm trying to enter formulas into tables.
Sometimes I get "Subscript out of range". It doesn't matter how I write the formula, it never works consistently.
You'll see a different formula commented out which doesn't work either.
Sub UpdateAccountTable()
'PURPOSE: Update table data with current data from CW Data Table
'Erik 2022
'
Dim tbl As ListObject
Dim tName As String
Dim warnCol As Long
Dim limitCol As Long
Range("L4").Select
tName = ActiveCell.ListObject.Name 'gets table name
Set tbl = ActiveSheet.ListObjects(tName)
warnCol = tbl.HeaderRowRange.Cells.Find("Current Warn").Column
limitCol = tbl.HeaderRowRange.Cells.Find("Current Limit").Column
' warn and limitcol gets column number because the columns are not always in the same place
StopExcelActions 'function to stop calculate, screen updating ect
With tbl
.ListColumns("warnCol").DataBodyRange.Formula = "=INDEX(CWdata[Warn Value],MATCH([#Helper],CWdata[Helper],0))"
' "=SUMIFS(CWdata[Warn Value], CWdata[Policy Name],[#[Policy Name]],CWdata[Rule Name],[#[Evaluator Description]])"
.ListColumns("limitCol").DataBodyRange.Formula = "=INDEX(CWdata[limit Value],MATCH([#Helper],CWdata[Helper],0))"
' "=SUMIFS(CWdata[Limit Value], CWdata[Policy Name],[#[Policy Name]],CWdata[Rule Name],[#[Evaluator Description]])"
End With
'Range("M4:N4").Select
With tbl.ListColumns("warnCol")
.EntireColumn.Copy
.EntireColumn.xlpastespecial Paste:=xlPasteValues
End With
With tbl.ListColumns("limitCol")
.EntireColumn.Copy
.EntireColumn.xlpastespecial Paste:=xlPasteValues
End With
StartExcelActions
Set tbl = Nothing
FormatData
End Sub
The error:
Consider how your use of `tbl.ListColumns(....) is one of those times where VBA is defaulting to the "Item Property" of the ListColumns object.
So:
ListColumns(StringName) or listColumns(IndexNumber)
are coding variations that point to the "Item" property: ListColumns.Item(variant)
Then notice how you must use the stringName for the column you have, not the String which is the name of the variable you used to store the string. Similarly, if you're using the "indexNumber" version of the object property, you use the value stored in the variable, not the string name of your indexNumber variable.
I give myself a heads up, as I code, and explicitly name all string variables as str_someName, and all indexes as j_someIndexName -- therefore:
Dim str_someName as string
Dim j_someIndexingName as Long
' you might have written Dim jLong_warnCol as Long
If I wrote ListColumns("str_someName") I would see right off that I'm using the string of my variable name, and not the value stored within the string variable. Similarly, if you wrote ListColumns("j_someIndexName"), you might notice that was weird.
Using the naming convention for Long or Integers becomes useful in Loops, so your variables have some expected range of values: Dim long_someName as Long or Dim j_someName' as Integer. You could extend this habit by using "t_..." for table variables, "obj_..." for objects, and on and on. Forced name conventions also helped me learn to avoid coding problems when crafting SQL statements, themselves stored into a str_sql string variable.
The use of OPTION EXPLICIT is an additional check.
I use VBA code to bold fragments of text in a multi-line cell. Everything works fine except that, when I double-click on the cell after the bolding was set, the bolding "jumps" by one character to the right, as seen on the picture below:
This is the code snippet I use to bold the dates:
rng.Characters(Start:=lngStart, Length:=lngEnd).Font.Bold = True
I have a gut feeling that this might have something to do with the line breaks. I use a concatenation with Chr(10) to create them. (E.g. strLine1 & Chr(10) & strLine2).
I'm running Office 365, Excel Version 1908.
EDIT:
This is the code I use to bold the cells:
Public Sub BoldActionDates(lsoTarget As ListObject, strColumnName As String, lngDataRow As Long)
Const LNG_LENGTH_DATE_STRING As Long = 10
Dim colStartingPoints As Collection
Dim varIndex As Variant
Dim rngAction As Range
Set rngAction = lsoTarget.ListColumns(strColumnName).DataBodyRange(lngDataRow)
Set colStartingPoints = GetStartingPointsDateStrings(rngAction.Value)
If colStartingPoints.Count = 0 Then Exit Sub
rngAction.Font.Bold = False
For Each varIndex In colStartingPoints
rngAction.Characters(Start:=CLng(varIndex), Length:=LNG_LENGTH_DATE_STRING).Font.Bold = True
Next varIndex
End Sub
GetStartingPointsDateStrings returns a Collection that contains the values of the starting points, e.g. for the example shown above the Collection looks like this:
Also, there is no BeforeDoubleClick-Event in the worksheet, only an unrelated one for BeforeRightClick.
It is very simple but yet I can't figure it out. Maybe because it cannot be done? Regardless here we go:
I would like to use a combobox that will, when selected, input cells with text values, functions and reference to external cells.
First line of the options would be to have the name populated.
Second line is a formula that would change from course to course.
Third line would provide a cell with a reference to another cell's content from another file. So if multiple course file are used I can have one master file that if I change the content of a cell the change will reflect on all the course file cells that are referring to it once updated.
This is in crude code form what I would like it to perform.
Private Sub ComboBox1_Change()
If Me.ComboBox1.Value = "ITCourse" Then
Worksheets("PARADE STATE").Range("I1").Value = "ITCourse"
Worksheets("Data Base").Range("C1").Value = IF(V9>70,"Prep Week",IF(V9>65,"Week 1",IF(V9>60,"Week 2",IF(V9>55,"Week 3",IF(V9>50,"Week 4",IF(V9>45,"Week 5",IF(V9>40,"Week 6",IF(V9>35,"Week 7",IF(V9>30,"Week 8",IF(V9>25,"Week 9",IF(V9>20,"Week 10",IF(V9>15,"Week 11",IF(V9>10,"Week 12",IF(V9>5,"Week 13",IF(V9>0,"Week 14")))))))))))))))
Worksheets("Week 1").Range("B2").Value = 'N:\ITcourse\00 - Data Base\[ITcourse.xlsx]Sheet'!$A$3
End If
If Me.ComboBox1.Value = "HRCourse" Then
Worksheets("PARADE STATE").Range("I1").Value = "HRCourse"
Worksheets("Data Base").Range("C1").Value = IF(V9>40,"Prep Week",IF(V9>35,"Week 1",IF(V9>30,"Week 2",IF(V9>25,"Week 3",IF(V9>20,"Week 4",IF(V9>15,"Week 5",IF(V9>10,"Week 6",IF(V9>5,"Week 7",IF(V9>5,"Week 8")))))))))
Worksheets("Week 1").Range("B2").Value = 'N:\ITcourse\00 - Data Base\[HRcourse.xlsx]Sheet'!$A$3
End If
End Sub
Thank you!
You need a function that returns the number of weeks for any given course name. This function should use a Dictionary to store the information, and the dictionary may be loaded from a dedicated worksheet.
Function WeeksPerCourse(courseName As String) As Long
Static dict As Scripting.Dictionary
If dict Is Nothing Then
' Fill the dictionary here. Note that it is better
' to load the values from a dedicated, hidden worksheet
Set dict = CreateObject("Scripting.Dictionary")
dict("ITCourse") = 14
dict("HRCourse") = 8
' Etc...
End If
WeeksPerCourse = dict(courseName)
End Function
With this function available, your procedure can be simplified like follows:
Private Sub ComboBox1_Change()
Dim course As Sting: course = Trim(ComboBox1.value)
Worksheets("PARADE STATE").Range("I1").value = course
'Dim nWeek As Long
'nWeek = WeeksPerCourse(course) - Worksheets("PARADE STATE").Range("V9").value / 5
'Worksheets("Data Base").Range("C1").value = IIf(nWeek < 1, "Prep Week", "Week " & nWeek)
Worksheets("Data Base").Range("C1").Formula = "= ""Week "" & INT((WeeksPerCourse('PARADE STATE'!I1) - 'PARADE STATE'!V9)/5)"
Worksheets("Week 1").Range("B2").Formula= "='N:\ITcourse\00 - Data Base\[" & course & ".xlsx]Sheet'!$A$3"
End Sub
I am trying to develop a function in VBA that returns the result to the current worksheet. It is intended that the function opens up another spreadsheet, extracts some data, does some processing and returns a value to the worksheet that called the function.
Everything works well when I call the function from the "immediate" window in the VBA editor. However, when I transfer the call to a worksheet the function behavior deviates from the expected when an attempt is made to open the other workbook (AreaBook). The object, AreaBook, remains as a pointer to nothing.
I've tried hard coding the filename; again a call to the function works from the immediate window but not when called from a workbook.
Any ideas?
Public Function pointInWhichArea(FileName As String, SheetName As String, areaID As String, ByVal pointLong As Single, ByVal pointLat As Single) As Variant ', testPointLon As Single, testPointLat As Single) As Variant
Dim a, b, c As Integer
Dim colAreaID, colLat, colLon As Integer
Dim AreaBook As Workbook
Dim AreaSheet As Worksheet
Dim polygonPoints() As pointType
Dim testPoint As pointType
Dim found As Boolean
' extract the point details
testPoint.x = pointLong
testPoint.y = pointLat
' set the workbook and sheet objects
FileName = filePath + FileName ' open the Area definition file
Set AreaBook = Workbooks.Open(FileName) ' <<<< PROBLEM HERE
Set AreaSheet = AreaBook.Sheets(SheetName)
a = 1 ' identify the Polygon ID, latitude and longitude columns column
While AreaSheet.Cells(1, a).Value <> ""
Select Case Worksheets(SheetName).Cells(1, a).Value
Case Is = areaID
colAreaID = a
Case Is = "Latitude"
colLat = a
Case Is = "Longitude"
colLon = a
End Select
a = a + 1
Wend
a = 2 ' loop through all points in the area list
b = a ' remember the beginning of the polygon
found = False
While (AreaSheet.Cells(a, colAreaID).Value <> "" And found = False)
If AreaSheet.Cells(a, colAreaID).Value <> AreaSheet.Cells(a + 1, colAreaID).Value Then ' test for the end of this polygon
c = a ' remember the end of the polygon
ReDim polygonPoints(b To c) As pointType ' array to capture the poylgon
For a = b To c ' loop through each point
polygonPoints(a).x = AreaSheet.Cells(a, colLon).Value ' extract the longitude of the point
polygonPoints(a).y = AreaSheet.Cells(a, colLat).Value ' extract the latitude of the point
Next a
b = a ' remember the beginning of the next polygon
If pointInArea(testPoint, polygonPoints) = True Then ' test if the point is in the current polygon
pointInWhichArea = AreaSheet.Cells(a - 1, colAreaID).Value ' return the area label
found = True
End If
Else
a = a + 1
End If
Wend
AreaBook.Close
End Function
I'm afraid a worksheet function cannot be used to affect other cells or workbooks - you can't add a function to cell A1 and expect the result to appear in cell B2.
In the same way you can't add a function in cell A1 and expect it to open another workbook to get its answer.
That's why it works in the immediate window and not as an Excel function.
You may be able to define a link to the other workbook and then reference that, but you can't get the function to physically open the other workbook.
Worksheet Functions (and user defined variants of these) are limited in terms of what they can and cannot do, here is a small excerpt from a Microsoft article regarding the matter:
https://support.microsoft.com/en-us/kb/170787
A user-defined function called by a formula in a worksheet cell cannot
change the environment of Microsoft Excel. This means that such a
function cannot do any of the following:
Insert, delete, or format cells on the spreadsheet.
Change another cell's value.
Move, rename, delete, or add sheets to a workbook.
Change any of the environment options, such as calculation mode or screen views.
Add names to a workbook.
Set properties or execute most methods.
The page goes on to state that:
Any environmental changes should be made through the use of a Visual Basic subroutine.
So in short, you cannot do what you're attempting to do with a worksheet UDF (User-Defined Function) and will need to change it to a sub routine.
The scenario:
Word documents that contain a selection of sentences (strings). There might be up to 30 possible strings (which vary from 5 to 20 words in length). The document will contain only a selection of these strings.
Aim:
Macro that searches through the document, finds each occurrence of a particular string and inserts a specific text code (such as " (ACWD2553)") after each occurrence. This is repeated for all the other strings in the set, with each different string having it's own distinct code. Some strings won't be in the document. The strings can be located in document body and table cells.
The macro would then be applied to other documents which would have different selections of the strings.
I have tried for many days using selection.find, content.find, target.list, insertafter and so on but only with one case and still ran into numerous problems (e.g. only inserting in one instance, or code repeatedly inserting until Word freezes).
Bonus feature ###
Be able to choose which set of strings which will be searched for (there are potentially up to 60 sets) and their corresponding codes. Each document would only have strings from one set.
An idea I had was for the strings to be listed in a column (in Excel?) and the matching codes in the a second column. The macro would then search the document for each string in the list (stopping at the end of the list since the number of strings varies between sets) finds the matching code in the cell in the next column and then inserts the code for each occurrence of the string in the word doc. When a different set is required, the Excel file could be swapped with the file containing the relevant set of stings, but with the same file name. Or all sets in the one Excel file on different worksheets and tab name entered in Word (userform?) which forces search of relevant set. This file would be located on a network drive.
Not sure if this is bigger then Ben Hur, last bit would be nice, but I can also manually enter the strings in the raw code from a template code.
Edited this post to include my poor attempt at the code. See my comment below. I just realised that I could add code to this pane. Tried a variety of iterations of the one below, none of which worked well and which does not approach what I require. I know there are obvious errors, as I said below I have played around with the code and made it worse in the process by mixing bits and pieces together.
Sub Codes()
Dim range As range
Dim i As Long
Dim TargetList
TargetList = Array("This is sentence 1", "This is string 2 which could be twenty words in length", "This is string three, there could be thirty more strings to search") ' put list of terms to find here
For i = 0 To UBound(TargetList)
Set range = ActiveDocument.range
With range.Find
.Text = TargetList(i)
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
Do While .Execute(Forward:=True) = True
range.Find.Execute
range.InsertAfter Text:=" (ACWD1234)"
Loop
End With
Next
End Sub
I think that this is a time to use replace rather than find, see implementation below. If the specific code changes depending on the target string you can hanlde this easily with a 2 dimensional array
Sub Codes()
Dim i As Long
Dim TargetList
Dim MyRange As range
TargetList = Array("This is sentence 1", "This is string 2 which could be twenty words in length", "This is string three, there could be thirty more strings to search") ' put list of terms to find here
Dim sStringToAdd As String
sStringToAdd = " (ACWD2553)"
For i = 0 To UBound(TargetList)
Set MyRange = ActiveDocument.Content
MyRange.Find.Execute FindText:=TargetList(i), ReplaceWith:=TargetList(i) & sStringToAdd, _
Replace:=wdReplaceAll
Next i
End Sub
The code below does exactly what you need. I dont know if replacing the whole Contents property of the document object has some weird effect into tabulation/formating and so on.
I'd rather not add any overhead with string/array/collection manipulations. Using find-replace is probably the most obvious route, but I don't like that whole lot of options you need to set (because I understand none of them =P)
You need to add a reference to "Microsoft scripting runtime"
Public Sub changeTokens()
Dim strContents As String
Dim mapperDic As Scripting.Dictionary
Dim thisTokenKey As String
Dim varKey As Variant
Set mapperDic = getTokenMapper()
For Each varKey In mapperDic.Keys
thisTokenKey = CStr(varKey)
ThisDocument.Content = Replace(ThisDocument.Content, thisTokenKey, mapperDic(thisTokenKey))
Next varKey
End Sub
Public Function getTokenMapper() As Scripting.Dictionary
' This function can fetch data from other sources to buidl up the mapping.
Dim tempDic As Scripting.Dictionary
Set tempDic = New Scripting.Dictionary
Call tempDic.Add("Token 1", "Token 1 changed!!")
Call tempDic.Add("Token 2", "Token 1 changed!!")
Call tempDic.Add("Token 3", "Token 1 changed!!")
Set getTokenMapper = tempDic
End Function
You can fetch your data to create the mapper dictionary from a excel worksheet with no problems.
Thanks to the two respondents. I don't have the skillset to progress the second code. I ended up searching for reading data from Excel into a word document and found code that worked perfectly.
Using Excel as data source in Word VBA
http://social.msdn.microsoft.com/Forums/office/en-US/ca9a31f4-4ab8-4889-8abb-a00af71d7307/using-excel-as-data-source-in-word-vba
Code produced by Doug Robbins.
This worked an absolute treat.
Also it means that I can edit the Excel file for the different sets of statements and their matching codes. Now it would be particularly sweet if I could work out a way to create a userform that would open when i run the macro and select the appropriate woprksheet based on the userform dropdown list item selected.