Return the Worksheet that an Excel Chart is referencing using VBA - vba

I need to be able to identify the worksheet that an excel chart (on a worksheet) is getting it's data from. I only need the data sheet which series 1 is referencing. I've started trying to extract the sheet name from .SeriesCollection(1).Formula but it gets realy complex. here's what I've got so far:
Sub GetChartDataSheet()
Dim DataSheetName As String
Dim DataSheet As Worksheet
DataSheetName = ActiveChart.SeriesCollection(1).Formula
DataSheetName = Left(DataSheetName, InStr(1, DataSheetName, "!$") - 1)
DataSheetName = WorksheetFunction.Replace(DataSheetName, 1, Len("=series("), "")
If Left(DataSheetName, 1) = "'" And Right(DataSheetName, 1) = "'" Then DataSheetName = Mid(DataSheetName, 2, Len(DataSheetName) - 2)
DataSheetName = Replace(DataSheetName, "''", "'")
Set DataSheet = Sheets(DataSheetName)
End Sub
this works in a lot of cases, but if my users have a strange worksheet name (eg Sh'e e$,,t!3!$) it fails. the same goes if series 1 has been named (eg .SeriesCollection(1).Formula = "=SERIES(**"Hell,o !"**,'Sh''e e$,,t!3!$'!$B$2:$B$18,'Sh''e e$,,t!3!$'!$C$2:$C$18,1)".
Is there a simple way to solve this?

I thought this is an easy one, turns out it's not. One of the cases where Excel has the information but will not give it away for free. I ended up with a function like this - maybe this helps:
Function getSheetNameOfSeries(s As Series) As String
Dim f As String, i As Integer
Dim withQuotes As Boolean
' Skip leading comma if not all parts of series is filled. Check if sheetname is in single quotes
For i = 9 To Len(s.Formula)
If Mid(s.Formula, i, 1) <> "," Then
If Mid(s.Formula, i, 1) = "'" Then
withQuotes = True
f = Mid(s.Formula, i + 1)
Else
withQuotes = False
f = Mid(s.Formula, i)
End If
Exit For
End If
Next i
' "f" now contains a part of the formula with the sheetname as start
' now we search to the end of the sheet name.
' If name is in quotes, we are looking for the "closing" quote
' If not in quotes, we are looking for "!"
i = 1
Do While True
If withQuotes Then
' Sheet name is in quotes, found closes quote --> we're done
' (but if next char is also a quote, we have the case the the sheet names contains a quote, so we have to continue working)
If Mid(f, i, 1) = "'" Then
If Mid(f, i + 1, 1) <> "'" Then
getSheetNameOfSeries = Mid(f, 1, i - 1)
Exit Do
Else
i = i + 1 ' Skip 2nd quote
End If
End If
Else
' Sheet name is quite normal, so "!" will indicate the end of sheetname
If Mid(f, i, 1) = "!" Then
getSheetNameOfSeries = Mid(f, 1, i - 1)
Exit Do
End If
End If
i = i + 1
Loop
getSheetNameOfSeries = Replace(getSheetNameOfSeries, "''", "'")
End Function

You can use the Find function to look for the values of SeriesCollection(1).
In the worksheet that hold the data of SeriesCollection(1), you will be able to find all the values in that array.
More explanations inside the code below.
Code
Option Explicit
Sub GetChartDataSheet()
Dim DataSheetName As String
Dim DataSheet As Worksheet
Dim ws As Worksheet
Dim ValuesArr As Variant, Val As Variant
Dim FindRng As Range
Dim ShtMatch As Boolean
Dim ChtObj As ChartObject
Dim Ser As Series
' if you want to use ActiveChart
Set ChtObj = ActiveChart.Parent
Set Ser = ChtObj.Chart.SeriesCollection(1)
ValuesArr = Ser.Values ' get the values of the Series Collection inside an array
' use Find to get the Sheet's origin
For Each ws In ThisWorkbook.Sheets
With ws
ShtMatch = True
For Each Val In ValuesArr ' loop through all values in array
Set FindRng = .Cells.Find(what:=Val) ' you need to find each value in the worksheet that SeriesCollection data is tied to
If FindRng Is Nothing Then
ShtMatch = False
Exit For
End If
Set FindRng = Nothing ' reset
Next Val
If ShtMatch = True Then
Set DataSheet = ws
Exit For
End If
End With
Next ws
DataSheetName = DataSheet.Name
End Sub

Related

How to clean a Word table before saving to a Word bookmark?

I am writing Word VBA that:
(1) assigns values from a Word table to VBA variables,
(2) cleans the variables' values of non-text,
(3) uses the variables' names and values to create Bookmarks in that same bookmark_value cell of the table, and
(4) repeats 1-2-3 until the end of table.
This table is the first table in the document and has two columns, something like this:
_________________________________
| bookmark_name | bookmark_value|
| bm1 | 88 |
| foo | 66 |
|_____bar_______|______44_______|
The code picks up the bookmark_names and posts into Word Bookmarks, and also picks up the bookmark_values but fails to clean the table coding out of the value.
The result is the Bookmarks displaying these unwanted cells in Word with the value inside it. It is strange that first column works and not the second.
Some things I tried:
I found on the Internet and on this site, what I thought were solutions, those are marked in the code below with comments, the header saying, "tried and failed".
I am nearly sure I need to "unformat" the text, or something like that.
Public Sub BookmarkTable()
Dim selectedTable As Table
Dim curRow As Range
Dim rngSelect1 As Range
Dim rngSelect2 As Range
Dim intTableIndex As Integer
Dim rng As Range
Dim Cell1 As Cell, Cell2 As Cell
Dim strBookmarkName As String, strBookmarkValue As String, strBV As String
Dim strTstBookmark As String
Dim Col1 As Integer, Col2 As Integer
Dim i As Integer, t As Integer
Dim intRow As Integer
' Dim
Col1 = 1 'set the bookmark name from column 1
Col2 = 2 'set the bookmark's value from column 2
'For t = 1 To ActiveDocument.Tables.Count
t = 1 'select the Table to use(only using the first table right now)
Set selectedTable = ActiveDocument.Tables(t)
selectedTable.Select 'selects the table
For intRow = 2 To selectedTable.Rows.Count 'iterate through all rows
If Selection.Information(wdWithInTable) Then
Set Cell1 = ActiveDocument.Tables(t).Cell(intRow, Col1)
Set Cell2 = ActiveDocument.Tables(t).Cell(intRow, Col2)
Cell2.Select
intTableIndex = ActiveDocument.Range(0, Selection.Tables(1).Range.End).Tables.Count
rngColumnStart = Selection.Information(wdStartOfRangeColumnNumber)
rngRowStart = Selection.Information(wdStartOfRangeRowNumber)
End If
strTstBookmark = "BM_Table" & CStr(intTableIndex) & "_R" & CStr(rngRowStart) & "_C" & CStr(rngColumnStart)
' strBookmarkValue = strTstBookmark
Set rngSelect1 = ActiveDocument.Range(Start:=Cell1.Range.Start, End:=Cell1.Range.End - 1)
strBookmarkName = Strip(rngSelect1.Text)
Set rngSelect2 = ActiveDocument.Range(Start:=Cell2.Range.Start, End:=Cell2.Range.End - 1)
strBookmarkValue = Strip(rngSelect2.Text)
Set rng = ActiveDocument.Tables(intTableIndex).Cell(rngRowStart, rngColumnStart).Range
rng.End = rng.End - 1
'--------------------------------------------------------------------------
'tried and failed)
'--------------------------------------------------------------------------
'Stop
If ActiveDocument.Bookmarks.Exists(strBookmarkName) = True Then
ActiveDocument.Bookmarks(strBookmarkName).Delete
End If
If ActiveDocument.Bookmarks.Exists(strTstBookmark) = True Then
ActiveDocument.Bookmark(strTstBookmark).Delete
End If
ActiveDocument.Bookmarks.Add Name:=strTstBookmark
ActiveDocument.Bookmarks.Add Name:=strBookmarkName
ActiveDocument.Bookmarks(strBookmarkName).Range.Text = strBookmarkValue
Next intRow
'Next t
End Sub
'--------------------------------------------------------------------------
'tried and failed
Private Function Strip(ByVal fullest As String)
' fuller = Left(fullest, Len(s) - 2)
Strip = Trim(Replace(fullest, vbCr & Chr(7), ""))
End Function
'--------------------------------------------------------------------------
That's truly horrible code you're using. Try:
Sub BkMkDemo()
Application.ScreenUpdating = False
Dim r As Long, BkMkNm As String, BkMkTxt As String
With ActiveDocument
For r = 2 To .Tables(1).Rows.Count
BkMkNm = Split(.Tables(1).Cell(r, 1).Range.Text, vbCr)(0)
BkMkTxt = Split(.Tables(1).Cell(r, 2).Range.Text, vbCr)(0)
If Not .Bookmarks.Exists(BkMkNm) Then .Bookmarks.Add BkMkNm, .Range.Characters.Last
Call UpdateBookmark(BkMkNm, BkMkTxt)
Next
End With
Application.ScreenUpdating = True
End Sub
Sub UpdateBookmark(BkMkNm As String, BkMkTxt As String)
Dim BkMkRng As Range
With ActiveDocument
If .Bookmarks.Exists(BkMkNm) Then
Set BkMkRng = .Bookmarks(BkMkNm).Range
BkMkRng.Text = BkMkTxt
.Bookmarks.Add BkMkNm, BkMkRng
End If
End With
Set BkMkRng = Nothing
End Sub
If all you want to do is to apply the bookmark to the content of the second cell, you need nothing more complex than:
Sub BkMkDemo()
Application.ScreenUpdating = False
Dim r As Long, BkMkNm As String, BkMkRng As Range
With ActiveDocument
For r = 2 To .Tables(1).Rows.Count
BkMkNm = Split(.Tables(1).Cell(r, 1).Range.Text, vbCr)(0)
Set BkMkRng = .Tables(1).Cell(r, 2).Range
BkMkRng.End = BkMkRng.End - 1
.Bookmarks.Add BkMkNm, BkMkRng
Next
End With
Application.ScreenUpdating = True
End Sub
After a great deal of research and learning by this VBA neophyte, here is the solution that I finally got to work. I found the fix by accident on the Windows Dev Center at msdn dot microsoft dot com posted by Cindy Meister...thank you. Turns out there are a combination of three characters needing to be cleaned when extracting text from a Word table cell: Chr(10) & Chr(13), Chr(11).
I simplified the code using the suggestions of macropod above. Thank you.
Sub aBookmarkTable()
'
'a subroutine compiled by Steven McCrary from various sources
'on the Internet, to use values in the second column of the
'first table in a Word document to create Bookmarks in that second
'column, in place of the value input there.
'
'To use the macros, modify the values in the table and run the macro.
'Then place Field Code references in Word to use the Bookmarks.
'The Bookmarks can be seen through Word menu: Insert>Links>Bookmark
'
'The table has just two columns, looking something like this:
'_________________________________
'| bookmark_name | bookmark_value|
'| bm1 | 88 |
'| foo | 66 |
'|_____bar_______|______44_______|
'
'The code places each Bookmark in the second column of each row, using
'the name given in the first column.
'
'The two critical functions of the macro occur in these two lines of code:
' rngBM.End = rngBM.End - 1
' Strip = Replace(fullest, Chr(10) & Chr(13), Chr(11))
'
' both are explained below where they are used.
Application.ScreenUpdating = False
Dim rng1 As Range, rng2 As Range, rngBM As Range
Dim Cell_1 As Cell, Cell_2 As Cell
Dim strBMName As String, strBMValue As String
Dim r As Integer
Call RemoveBookmarks 'removing bookmarks helped to simlify the coding
With ActiveDocument
For r = 2 To .Tables(1).Rows.Count 'iterate through all rows
Set Cell_1 = ActiveDocument.Tables(1).Cell(r, 1)
Set Cell_2 = ActiveDocument.Tables(1).Cell(r, 2)
Cell_2.Select
Set rng1 = .Range(Cell_1.Range.Start, Cell_1.Range.End - 1)
strBMName = Strip(rng1.Text)
Set rng2 = .Range(Cell_2.Range.Start, Cell_2.Range.End - 1)
Set rngBM = ActiveDocument.Tables(1).Cell(r, 2).Range
'When using data contained in a cell of a Word table,
'grabbing the cell's contents also grabs several other
'characters, which therefore need removed in two steps.
'
'The first step is to clean the extra characters from the text.
strBMValue = Strip(rng2.Text)
'
'The second step is to decrease the range size to put in the
'Bookmark.
rngBM.End = rngBM.End - 1
rngBM.Text = strBMValue
.Bookmarks.Add strBMName, rngBM
Next r
End With
Application.ScreenUpdating = True
Selection.WholeStory
ActiveDocument.Fields.Update
End Sub
Sub RemoveBookmarks()
Dim bkm As Bookmark
For Each bkm In ActiveDocument.Bookmarks
bkm.Delete
Next bkm
End Sub
Private Function Strip(ByVal fullest As String)
' the next line of code is the tricky part of the clean
' process because of how Word formats tables and text
' ASCII code Chr(10) is Line Feed
' Chr(13) is Carriage Return
' Chr(13) + Chr(10): vbCrLf or vbNewLine New line character
' Chr (11) is Vertical Tab, but per Word VBA Manual -
' manual line break (Shift + Enter)
'
Strip = Replace(fullest, Chr(10) & Chr(13), Chr(11))
End Function
Thank you again.
SWM

Collection storing more than it's intended to causing problems for a Union statement

For some reason every column with data is being stored into columnsToCopy and unionVariable. At the top levels in Locals, I can see that it recognizes the column I actually want, but when I go deeper into say Cells -> WorkSheet -> UsedRange -> Value2 it will now show that all columns in my workbook are stored. This is the piece of code that I have assigning columnsToCopy, all the way to assigning unionVariable and then Copying it:
checkOne = iq_Array(0)
hasIQs = Left(checkOne, 3) = "iq_"
Dim columnsToCopy As Collection
Set columnsToCopy = New Collection
If hasIQs Then
' paste inital column into temporary worksheet
columnsToCopy.Add ShRef.Columns(1)
End If
' loop for each iq_ in the array
For arrayLoop = LBound(iq_Array) To UBound(iq_Array)
' Take copy of potential ref and adjust to standard if required
checkStr = iq_Array(arrayLoop)
If hasIQs And Left(checkStr, 3) <> "iq_" Then checkStr = "iq_" & checkStr
' Look for existence of corresponding column in local copy array
pCol = 0
For iCol = 2 To colNumb
If checkStr = IQRef(iCol) Then
pCol = iCol
Exit For
End If
Next iCol
If pCol > 0 Then
' Paste the corresponding column into the forming table
columnsToCopy.Add ShRef.Columns(pCol)
End If
Next arrayLoop
If columnsToCopy.Count > 1 Then 'data was added
' Copy table
Dim unionVariable As Range
Set unionVariable = columnsToCopy(1)
For k = 2 To columnsToCopy.Count
Set unionVariable = xlApp.Union(unionVariable, columnsToCopy(k))
Next k
unionVariable.Copy ' all the data added to ShWork
The reason I'm looking into this, is because when I Union(unionVariable, columnToCopy(k)) I'm not getting something that would be equivalent to Range("A:A","D:D","Z:Z") , instead I'm getting Range("A:Z").
Any help is appreciated
My full code:
Option Explicit
Private Sub averageScoreRelay()
' 1. Run from PPT and open an Excel file
' 2. Start at slide 1 and find a box that contains the words "iq_", if it has those words then it will have numbers after it like so "iq_43" or "iq_43, iq_56,iq_72".
' 3. find those words and numbers in the opened Excel file after splitting and re-formating string.
' 3. Copy column into a new sheets and repeat for all "iq_'s" until sheets 2 has a table.
' 4. Copy table from xl Paste Table into ppt
' 5. Do this for every slide
'Timer start
Dim StartTime As Double
Dim SecondsElapsed As Double
StartTime = Timer
'Create variables
Dim xlApp As Excel.Application
Dim xlWB As Excel.Workbook
Dim ShRef As Excel.Worksheet
Dim pptPres As Object
Dim colNumb As Long
Dim rowNumb As Long
' Create new excel instance and open relevant workbook
Set xlApp = New Excel.Application
'xlApp.Visible = True 'Make Excel visible
Set xlWB = xlApp.Workbooks.Open("C:\Users\Andre Kunz\Desktop\Gate\Macros\averageScores\pptxlpratice\dummy2.xlsx", True, False, , , , True, Notify:=False) 'Open relevant workbook
If xlWB Is Nothing Then ' may not need this if statement. check later.
MsgBox ("Error retrieving Average Score Report, Check file path")
Exit Sub
End If
xlApp.DisplayAlerts = False
'Find # of iq's in workbook
Set ShRef = xlWB.Worksheets("Sheet1")
colNumb = ShRef.Cells(1, ShRef.Columns.Count).End(xlToLeft).Column
rowNumb = ShRef.Cells(ShRef.Rows.Count, 1).End(xlUp).Row
Dim IQRef() As String
Dim iCol As Long
Dim IQRngRef() As Range
ReDim IQRef(colNumb)
ReDim IQRngRef(colNumb)
' capture IQ refs locally
For iCol = 2 To colNumb
Set IQRngRef(iCol) = ShRef.Range(ShRef.Cells(1, iCol), ShRef.Cells(rowNumb, iCol))
IQRef(iCol) = ShRef.Cells(1, iCol).Value
Next iCol
'Make pptPres the ppt active
Set pptPres = PowerPoint.ActivePresentation
'Create variables for the slide loop
Dim pptSlide As Slide
Dim Shpe As Shape
Dim pptText As String
Dim iq_Array As Variant
Dim arrayLoop As Long
Dim myShape As Object
Dim i As Long
Dim lRows As Long
Dim lCols As Long
Dim k As Long
'Loop through each pptSlide and check for IQ text box, grab avgScore values and create pptTable
For Each pptSlide In pptPres.Slides
i = 0
pptSlide.Select
'searches through shapes in the slide
For Each Shpe In pptSlide.Shapes
If Not Shpe.HasTextFrame Then GoTo nextShpe 'boom, one less nested If statement
If Not Shpe.TextFrame.HasText Then GoTo nextShpe ' boom, another nested If statement bites the dust
'Set pptText as the Text in the box, then make it lowercase and trim Spaces and Enters
pptText = Shpe.TextFrame.TextRange
pptText = LCase(Replace(pptText, " ", vbNullString))
pptText = Replace(Replace(Replace(pptText, vbCrLf, vbNullString), vbCr, vbNullString), vbLf, vbNullString)
'Identify if within text there is "iq_"
If InStr(1, pptText, "iq_") <= 0 Then GoTo nextShpe
'set iq_Array as an array of the split iq's
iq_Array = Split(pptText, ",")
Dim hasIQs As Boolean
Dim checkStr As String
Dim pCol As Long
Dim checkOne
checkOne = iq_Array(0)
hasIQs = Left(checkOne, 3) = "iq_"
Dim columnsToCopy As Collection
Set columnsToCopy = New Collection
If hasIQs Then
' paste inital column into temporary worksheet
columnsToCopy.Add ShRef.Columns(1)
End If
' loop for each iq_ in the array
For arrayLoop = LBound(iq_Array) To UBound(iq_Array)
' Take copy of potential ref and adjust to standard if required
checkStr = iq_Array(arrayLoop)
If hasIQs And Left(checkStr, 3) <> "iq_" Then checkStr = "iq_" & checkStr
' Look for existence of corresponding column in local copy array
pCol = 0
For iCol = 2 To colNumb
If checkStr = IQRef(iCol) Then
pCol = iCol
Exit For
End If
Next iCol
If pCol > 0 Then
' Paste the corresponding column into the forming table
columnsToCopy.Add ShRef.Columns(pCol)
End If
Next arrayLoop
If columnsToCopy.Count > 1 Then 'data was added
' Copy table
Dim unionVariable As Range
Set unionVariable = columnsToCopy(1)
For k = 2 To columnsToCopy.Count
Debug.Print k & " : " & unionVariable.Address & " + " & columnsToCopy(k).Address
Set unionVariable = xlApp.Union(unionVariable, columnsToCopy(k))
Debug.Print " --> " & unionVariable.Address
Next k
unionVariable.Copy ' all the data added to ShWork
tryAgain:
ActiveWindow.ViewType = ppViewNormal
ActiveWindow.Panes(2).Activate
Set myShape = pptSlide.Shapes.PasteSpecial(DataType:=ppPasteHTML, Link:=msoFalse)
On Error GoTo tryAgain
'Set position:
myShape.Left = -200
myShape.Top = 150 + i
i = i + 150
End If
nextShpe:
Next Shpe
nextSlide:
Next pptSlide
xlWB.Close
xlApp.Quit
xlApp.DisplayAlerts = True
'End Timer
SecondsElapsed = Round(Timer - StartTime, 2)
MsgBox "This code ran successfully in " & SecondsElapsed & " seconds", vbInformation
End Sub
Output from Debugger:
2 : $A:$A + $B:$B
--> $A:$B
3 : $A:$B + $AF:$AF
--> $A:$B,$AF:$AF
2 : $A:$A + $C:$C
--> $A:$A,$C:$C
2 : $A:$A + $D:$D
--> $A:$A,$D:$D
3 : $A:$A,$D:$D + $L:$L
--> $A:$A,$D:$D,$L:$L
Here is another option doesn't have the additional overhead of creating a temporary workbook/worksheet.
Note: It may not be perfect -- in my testing it does not preserve cell background color but it does preserve text/font formats, and this appears consistent with the PasteSpecial(ppPasteHtml) method.
Note also: this assumes you can use a Table in PowerPoint to store the pasted data, and that all columns in your union range have the same number of rows. If you're just dumping the data in to a textbox or whatever sort of shape, this won't work.
But the idea is that once we have our "union", we can iterate over the Areas, and the Columns in each area, performing the Copy and Paste operation against each individual column.
Here is my data in Excel, I will create a union of the highlighted cells:
Here is the output in PowerPoint where I removed the borders from the table, note the text formatting preserved as well as cell alignment:
Option Explicit
Sub foo()
Dim ppt As PowerPoint.Application
Dim pres As PowerPoint.Presentation
Dim sld As PowerPoint.Slide
Dim shp As PowerPoint.Shape
Dim tbl As PowerPoint.Shape
Dim unionRange As Range
Dim ar As Range, c As Long, i As Long
Set unionRange = Union([A1:B2], [D1:D2], [F1:F2])
Set ppt = CreateObject("PowerPoint.Application")
ppt.Visible = True
Set pres = ppt.ActivePresentation
Set sld = pres.Slides(1)
' Create initial table with only 1 column
With unionRange
Set tbl = sld.Shapes.AddTable(.Rows.Count, 1)
End With
For Each ar In unionRange.Areas()
For c = 1 To ar.Columns.Count
i = i + 1
With tbl.Table
' Add columns as you iterate the columns in your unionRange
If .Columns.Count < i Then .Columns.Add
.Columns(i).Cells.Borders(ppBorderBottom).Transparency = 1
.Columns(i).Cells.Borders(ppBorderTop).Transparency = 1
.Columns(i).Select
ar.Columns(c).Copy '// Copy the column from Excel
ppt.CommandBars.ExecuteMso ("Paste") '// Paste the values to PowerPoint
End With
Next
Next
End Sub
Maybe more efficient to handle the Areas like so:
For Each ar In unionRange.Areas()
c = ar.Columns.Count
Dim tCol
tCol = .Columns.Count
With tbl.Table
' Add columns as you iterate the columns in your unionRange
While .Columns.Count < (tCol + c)
.Columns.Add
Wend
.Columns(tCol).Cells.Borders(ppBorderBottom).Transparency = 1
.Columns(tCol).Cells.Borders(ppBorderTop).Transparency = 1
.Columns(tCol).Select
ar.Copy '// Copy the columns in THIS Area object from Excel
ppt.CommandBars.ExecuteMso ("Paste") '// Paste the values to PowerPoint
End With
Next
But I still think performance on large data set will suffer vs the other answer.
The issue seems to be caused by the pasting of the non-contiguous range into PowerPoint.
I don't know enough PowerPoint VBA to know whether it has some other paste method you could use, but a work-around would be to create a new Excel worksheet containing just the info you want to copy, and then to copy that worksheet to PowerPoint:
'...
Next k
unionVariable.Copy ' all the data added to ShWork
'Create a temporary sheet (the workbook is being closed without saving
'so the temporary worksheet will be "lost" after we finish)
xlWB.Worksheets.Add Before:=xlWB.Worksheets(1)
'Paste the data into the temporary sheet
xlWB.Worksheets(1).Range("A1").PasteSpecial xlPasteValues
'Copy the temporary sheet
xlWB.Worksheets(1).UsedRange.Copy
tryAgain:
ActiveWindow.ViewType = ppViewNormal
'...

Use a string as part of an If statement (VBA)

I have an If/Then loop in VBA that checks if the same cell in each tab are equal, and I can create a string that works in the If/Then loop given a known number of tabs (3 tabs); however, the macro needs to look at an arbitrary number of tabs and I need a dynamic If/Then statement. I tried to create a string that essentially writes the code based on the number of tabs, but I get Type Mismatch because the string is a variable.
For example, this works given 3 tabs:
If Worksheets(loc(1)).Cells(TseriesLine, 15) = Worksheets(loc(2)).Cells(TseriesLine, 15) _
And Worksheets(loc(1)).Cells(TseriesLine, 15) = Worksheets(loc(3)).Cells(TseriesLine, 15) Then
....
But this doesn't work:
ifline = "Worksheets(loc(1)).Cells(TseriesLine, 15) = Worksheets(loc(2)).Cells(TseriesLine, 15) _
And Worksheets(loc(1)).Cells(TseriesLine, 15) = Worksheets(loc(3)).Cells(TseriesLine, 15)"
If ifline Then ....
I also tried using Evalulate(ifline) and StrConv(ifline) to no success. Any help would be appreciated.
Thanks
Try something like this.
You can easily test against other sheet names if there are sheets you know you don't want to check against.
Dim sValue As String
Dim ws1 As Worksheet
Set ws1 = Worksheets("loc(1)")
sValue = ws1.Cells(TseriesLine, 15).Value2
Dim bifline As Boolean
bifline = True
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> ws1.Name Then
If sValue <> ws.Cells(TseriesLine, 15).Value2 Then
bifline = False
Exit For
End
End If
Next
If bifline Then
'more code
End If
You can loop over each sheet with the worksheet collection in each workbook object.
Function doesRangeMatch(rangeAddress As String) As Boolean
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If ThisWorkbook.Worksheets(1).Range(rangeAddress).Value <> ws.Range(rangeAddress).Value Then
doesRangeMatch = False
Exit Function 'early exit if match not found
End If
Next
doesRangeMatch = True 'if loop goes through then all must match
End Function
Thanks everyone so much! I used a combination of suggestions to come up with the loop. Here is the solution:
For ss = 2 To numloc
If Worksheets(loc(1)).Cells(TseriesLine, 15) <> Worksheets(loc(ss)).Cells(TseriesLine, 15) Then
doNumMatch = False
Exit For
Else: doNumMatch = True
End If
Next
If doNumMatch Then

Not able to get the cell value in foreach loop using excel vba

Hi,
I have enclosed the sheet image.
My requirement is:
I want to get all the "G" column values for the organization matching to a specific organization name (Ex:360 evaluations).
I am getting null value after first loop for the G Column
Sub UsageWeekTrend()
Dim customerName As String
Dim sheetName As String
Dim dataFound As Boolean
Dim selectedCell As Range
Dim rowNumber As Integer
Dim weekMinutes As Double
Dim trendsFile As Workbook
Dim trendsSheet As Worksheet
On Error GoTo errorHandling
sheetName = ActiveSheet.Name
customerName = ActiveSheet.Range("A" & (ActiveCell.row)).Value
dataFound = False
For Each selectedCell In ActiveSheet.Range("A1:A1000")
If UCase(selectedCell.Value) = UCase(customerName) Then
weekMinutes = ActiveSheet.Range("G" & selectedCell.row).Value
Debug.Print weekMinutes
Debug.Print "G" & selectedCell.row
If dataFound = False Then
If trendsFile Is Nothing Then
Set trendsFile = Workbooks.Add()
trendsFile.Activate
Set trendsSheet = trendsFile.ActiveSheet
Else
' add a new sheet to the trends workbook
trendsFile.Activate
Set trendsSheet = Sheets.Add
End If
dataFound = True
rowNumber = 1
trendsSheet.Name = Left(customerName, 10) + " " + Format(Date, "MMDD")
trendsSheet.Cells(rowNumber, 1) = "Users"
trendsSheet.Cells(rowNumber, 2) = "Minutes"
rowNumber = rowNumber + 1
End If
' if a sheet has been created, then we have at least one non-zero value so add data
If dataFound = True Then
trendsSheet.Cells(rowNumber, 1) = customerName
trendsSheet.Cells(rowNumber, 2) = weekMinutes
rowNumber = rowNumber + 1
End If
End If
Next selectedCell
' if we have data, create the chart
If dataFound = True Then
' make sure the trends sheet is active for chart insertion
trendsSheet.Activate
Dim chtChart As ChartObject
Dim chartName As String
Dim endRange As String
' define the end of the range for the chart
endRange = "C" & CStr(rowNumber - 1)
' add chart to current sheet
Set chtChart = ActiveSheet.ChartObjects.Add(Left:=200, Top:=200, Width:=900, Height:=400)
chtChart.Activate
ActiveChart.ChartType = xlLineStacked
ActiveChart.SetSourceData Source:=trendsSheet.Range("A2", endRange)
ActiveChart.HasTitle = True
ActiveChart.ChartTitle.Text = customerName
ActiveChart.ApplyLayout (5)
Else
MsgBox ("No usage data found for customer " + customerName)
End If
Exit Sub
errorHandling:
MsgBox (Err.Description)
End Sub
When you run this line:
trendsFile.Activate
You change the Activesheet, so the 2nd time on the loop you again look at the activesheet
weekMinutes = ActiveSheet.Range("G" & selectedCell.row).Value
but the activesheet has changed. I would change those Activesheet calls to a worksheet object that you assign at the top.
This is always a good read for those new to VBA programming: How to avoid using Select in Excel VBA macros
The issue is that you're using ActiveSheet, and the active sheet is being changed in your code.
As soon as trendsFile.Activate is executed, these two references will have new meanings ActiveSheet.Range("A1:A1000") and ActiveSheet.Range("G" & selectedCell.row).Value.
You've created workbook & worksheet variables for your Trends file, and use those, you also need to create a worksheet variable for your "source" worksheet (not sure how you'd refer to it).
Also, I'd be a bit concerned about this section of code:
If trendsFile Is Nothing Then
Set trendsFile = Workbooks.Add()
trendsFile.Activate
Set trendsSheet = trendsFile.ActiveSheet
Else
' add a new sheet to the trends workbook
trendsFile.Activate
Set trendsSheet = Sheets.Add
End If
I believe you'll be adding a new sheet every time through the loop.
Try something like this:
Sub UsageWeekTrend()
Dim customerName As String
Dim sheetName As String
Dim dataFound As Boolean
Dim selectedCell As Range
Dim rowNumber As Integer
Dim weekMinutes As Double
Dim trendsFile As Workbook
Dim trendsSheet As Worksheet
Dim SourceSheet as worksheet 'this is the place where you start, call it what you will
On Error GoTo errorHandling
set SourceSheet = activesheet 'this will now always be THIS sheet, and won't change
sheetName = SourceSheet.Name
customerName = SourceSheet.Range("A" & (ActiveCell.row)).Value
dataFound = False
For Each selectedCell In SourceSheet.Range("A1:A1000")
If UCase(selectedCell.Value) = UCase(customerName) Then
weekMinutes = SourceSheet.Range("G" & selectedCell.row).Value
Debug.Print weekMinutes
Debug.Print "G" & selectedCell.row
If dataFound = False Then
If trendsFile Is Nothing Then
Set trendsFile = Workbooks.Add()
'trendsFile.Activate - never needed
Set trendsSheet = trendsFile.Sheets("Sheet1") 'use the first sheet, since you just created a brand new workbook
Else
' add a new sheet to the trends workbook
'trendsFile.Activate -- you never need this when you're working with an object instead of "Active"
'you'll find that this line will add a new sheet every time you execute the loop
'once you've created your "trendsFile" workbook. you'll need to do some tweaking here
'to prevent you getting one loop worth of data on each sheet
Set trendsSheet = Sheets.Add
End If
dataFound = True
rowNumber = 1
trendsSheet.Name = Left(customerName, 10) + " " + Format(Date, "MMDD")
trendsSheet.Cells(rowNumber, 1) = "Users"
trendsSheet.Cells(rowNumber, 2) = "Minutes"
rowNumber = rowNumber + 1
End If
' if a sheet has been created, then we have at least one non-zero value so add data
If dataFound = True Then
trendsSheet.Cells(rowNumber, 1) = customerName
trendsSheet.Cells(rowNumber, 2) = weekMinutes
rowNumber = rowNumber + 1
End If
End If
Next selectedCell
'The rest of your routine here...
End Sub

Éxcel-VBA Open Workbook in macro that have been called in a cell

I have some trouble with a function that I call from a cell in Excel.
The macro shall open a workbook get some data and then return a mathematical results to the cell.
But when I use the the following code it does not open the wordbook, just return #VALUE! to the cell. It break out of the code right after I tried to open the workbook.
' This Interpolation function is used to get data from other Excel sheets
Public Function DatasheetLookup(ExcelFile As String, ExcelSheet As String, xVal As Double, Optional isSorted As Boolean = True) As Variant
' abosolute or relative path?
If Not (Left(ExcelFile, 3) Like "[A-Z]:\") Or (Left(ExcelFile, 2) = "\\") Then
ExcelFile = ThisWorkbook.path & "\" & ExcelFile
End If
' does file exits?
If Dir(ExcelFile, vbDirectory) = vbNullString Then
DatasheetLookup = "No such file!"
Exit Function
End If
' open the source workbook, read only
Dim Wbk As Workbook
Dim WS As Worksheet
' Application.ScreenUpdating = False ' turn off the screen updating
Set Wbk = Workbooks.Open(ExcelFile)
' Run through all sheets in the source workBook to find "the one"
For Each WS In Wbk.Worksheets ' <-- Here it exit the code and return #VALUE!
If WS.Name <> ExcelSheet Then
DatasheetLookup = "Sheet not found!"
Else
Dim xRange As Range
Dim yRange As Range
xRange = WS.Range("A1", "A" & WS.UsedRange.Rows.Count)
yRange = WS.Range("B1", "B" & WS.UsedRange.Rows.Count)
Dim yVal As Double
Dim xBelow As Double, xAbove As Double
Dim yBelow As Double, yAbove As Double
Dim testVal As Double
Dim High As Long, Med As Long, Low As Long
Low = 1
High = WS.UsedRange.Rows.Count
If isSorted Then
' binary search sorted range
Do
Med = Int((Low + High) \ 2)
If (xRange.Cells(Med).Value) < (xVal) Then
Low = Med
Else
High = Med
End If
Loop Until Abs(High - Low) <= 1
Else
' search every entry
xBelow = -1E+205
xAbove = 1E+205
For Med = 1 To xRange.Cells.Count
testVal = xRange.Cells(Med)
If testVal < xVal Then
If Abs(xVal - testVal) < Abs(xVal - xBelow) Then
Low = Med
xBelow = testVal
End If
Else
If Abs(xVal - testVal) < Abs(xVal - xAbove) Then
High = Med
xAbove = testVal
End If
End If
Next Med
End If
xBelow = xRange.Cells(Low): xAbove = xRange.Cells(High)
yBelow = yRange.Cells(Low): yAbove = yRange.Cells(High)
DatasheetLookup = yBelow + (xVal - xBelow) * (yAbove - yBelow) / (xAbove - xBelow)
Exit For
End If
Next WS
Wbk.Close Savechanges:=False
Set Wbk = Nothing
Application.ScreenUpdating = True
End Function
I am not sure the reason for this specifically, but you cannot open a file in a user defined function. There are many additional actions that cannot be performed in a Function as well. This is also discussed in this Stack Overflow answer here.
However, in your case, you can easily cheat this limitation by opening the file you want to read before you call the function. I prepared a very basic demonstration of this, you will need to modify the code as needed to fit your particular example:
Code in "ThisWorkbook":
' when the workbook opens, also open the companion spreadsheet so it is available to use
Private Sub Workbook_Open()
Set Wbk = Workbooks.Open("C:\Users\lrr\Desktop\Myworkbook.xlsx")
End Sub
Code in "Module1":
Global Wbk As Workbook
Public Function testFunc()
' the workbook is already opened, so you may perform this iteration operation w/o any problems.
For Each WS In Wbk.Worksheets
testFunc = 1
Exit Function
Next WS
End Function
Code in Cell A1:
=testFunc()