Read Cell properties in Visio using vb - vba

I am trying to create a VB macro in Visio that can read the data and properties of the shape. So say I have a Rectangle Shpae in Visio with Cells Name, Description, Type, Size.... and so on.
When I try to read the cells and their values I am only getting the first cell and its value.
Here is my code . I would appreciate some help here.
Sub Testing()
Dim excelObj As Object
Dim excelFile As String
Dim sheetName As String
' Dim excelBook As Excel.Workbook
' Set excelFile = "C:\Users\hbhasin\Documents\test.xls"
'Set sheetName = "New Sheet name"
Set excelObj = CreateObject("Excel.Application")
excelObj.Workbooks.Add
Dim pagObj As Visio.Page
Dim shpsObj As Visio.shapes
Dim shapes As Visio.shapes
Dim shpObj As Visio.Shape
Dim CellObj As Visio.Cell
Dim Storage() As String
Dim iShapeCount As Integer
Dim i As Integer
Dim j As Integer
Set pagObj = ActivePage
Set shpsObj = pagObj.shapes
iShapeCount = shpsObj.Count
Debug.Print iShapeCount
ReDim Storage(8, iShapeCount - 1)
For i = 1 To iShapeCount - 1
Set shpObj = shpsObj(i)
Storage(1, i - 1) = shpObj.Name
If shpObj.CellExists("Prop.Name", visExistsLocally) Then
Set CellObj = shpObj.CellsU("Prop.Name")
Storage(2, i - 1) = CellObj.ResultStr("")
End If
If shpObj.CellExists("Prop.Description", visExistsLocally) Then
Debug.Print "Test the IF statement"
Set CellObj = shpObj.CellsU("Prop.Description")
Storage(3, i - 1) = CellObj.ResultStr("")
End If
Next
For i = 0 To iShapeCount - 1
Debug.Print "Name- " & Storage(0, i)
Debug.Print "Description-" & Storage(1, i)
Next
End Sub
In fact, I have put a debug statement within the second if clause and that does not execute which tells me the compiler is not even seeing the second cell or any cell after.

If you're not getting the Description Shape Data it maybe that it's not local, but inherited from its master. Here's a slight modification of your code (with the Excel part removed as I don't think it's relevant here):
Sub Testing()
Dim shpsObj As Visio.shapes
Set shpsObj = ActivePage.shapes
Dim iShapeCount As Integer
iShapeCount = shpsObj.Count
'Assumes you want an array of shape data
Dim Storage() As String
ReDim Storage(iShapeCount - 1, 2)
'Visio shapes are 1 based so use full count
Dim i As Integer
Dim shpObj As Visio.Shape
For i = 1 To iShapeCount
Set shpObj = shpsObj(i)
Storage(i - 1, 0) = shpObj.Name 'Do you want NameU?
'Assumes you don't care whether the cell is local or inherited
If shpObj.CellExistsU("Prop.Name", visExistsAnywhere) Then
Storage(i - 1, 1) = shpObj.CellsU("Prop.Name").ResultStr("")
End If
If shpObj.CellExistsU("Prop.Description", visExistsAnywhere) Then
Storage(i - 1, 2) = shpObj.CellsU("Prop.Description").ResultStr("")
End If
Next
Dim j As Long
For j = LBound(Storage, 1) To UBound(Storage, 1)
Debug.Print "Shape Name- " & Storage(j, 0)
Debug.Print " Prop.Name- " & Storage(j, 1)
Debug.Print " Prop.Description- " & Storage(j, 2)
Next j
End Sub
If you're just running through all the shapes on the page, then you might want to look at For Each shp In shapes as an alternative. Check out this page for more details:
http://visualsignals.typepad.co.uk/vislog/2007/11/looping-through.html
Also, you might want to try look at the CreateSelection page method to narrow down your target shapes if you're dealing with a large number

Related

CATIA VBA - trying to list length of a protection

I want to change an already working macro that lists the part number and name of the protections in a listbox.Now I also try to list length of each protection that I can find in my cable. I looked it up and people told me you cannot access electrical properties of a protection where I can get the lenght, therefore I need to make a workarround. First I need to create a parameter for each protection. Then create a formula (length on curve and 2 points)to feed the parameter. Then list the value in a listbox.
selection1.Search "CATElectricalSearch.Protection,all"
Dim i As Integer
Dim oInstProd As Product
Dim strpartno As String
For i = 1 To selection1.Count
Set oInstProd = selection1.Item(i).LeafProduct
strpartno = oInstProd.ReferenceProduct.PartNumber
'test
selection1.Item(1).Document.Activate
Dim part1 As Part
Set part1 = selection1.Item(1).Document.Part
Dim parameters1 As Parameters
Set parameters1 = part1.Parameters
On Error Resume Next
Err.Clear 'Clear any previous error messages
Set ParamV = parameters1.Item("Lungime")
If Err.Number = 0 Then
parameters1.Remove "Lungime"
Else
'TODO Stuff if parameter does not Exist
'create a new length type parameter, set its value to 0 for now
Dim length1 As Dimension
Set length1 = parameters1.CreateDimension("", "LENGTH", 0)
'if you want to rename the parameter
length1.Rename "Lungime"
Dim hybridBodies1 As HybridBodies
Set hybridBodies1 = part1.HybridBodies
Dim hybridBody1 As HybridBody
Set hybridBody1 = hybridBodies1.Item("External References")
Set hybridShapes1 = hybridBody1.HybridShapes
Dim reference1 As Reference
Set reference1 = hybridShapes1.Item(1) 'get curve
Dim reference2 As Reference
Set reference2 = hybridShapes1.Item(2) 'get first point
Dim reference3 As Reference
Set reference3 = hybridShapes1.Item(3) 'get second point
'create a new formula to link to the parameter
Dim relations1 As Relations
Set relations1 = part1.Relations
'make sure points are labeled MyEndPt1 and MyEndPt2 respectively
Dim formula1 As Formula
Set formula1 = relations1.CreateFormula("Formula.47", "", length1, "length( `External References\" & reference1.Name & "` ,`External References\" & reference2.Name & "` , `External References\" & reference3.Name & "` ) ")
'MsgBox length1.ValueAsString
End If
'end test
With UserFormTapeCheck.ListBox1
.AddItem
.List(i - 1, 0) = selection1.Item(i).LeafProduct.Name
.List(i - 1, 1) = strpartno
'test
.List(i - 1, 2) = length1.ValueAsString
'end test
End With
'test
relations1.Remove "Formula.47"
parameters1.Remove "Lungime"
'end test
Next
selection1.Clear
The macro works perfect if I only list the part number and leafproduct.name.
I need to get inside the part. Think I am still inside the product and therefore macro can't get the parameters nor the hybrid bodies.
I manage to make to make it work. Found the way to get the part from a electrical component Set oPart = oInstProd.ReferenceProduct.Parent.Part
Dim productDocument1 As ProductDocument
Set productDocument1 = CATIA.ActiveDocument
Dim selection1 As Selection
Set selection1 = productDocument1.Selection
selection1.Search "CATElectricalSearch.Protection,all"
Dim i As Integer
Dim oInstProd As Product
Dim strpartno As String
Dim oPart As Part
For i = 1 To selection1.Count
Set oInstProd = selection1.Item(i).LeafProduct
strpartno = oInstProd.ReferenceProduct.PartNumber
'test
Set oPart = oInstProd.ReferenceProduct.Parent.Part
Dim parameters1 As Parameters
Set parameters1 = oPart.Parameters
On Error Resume Next
Err.Clear 'Clear any previous error messages
Set ParamV = parameters1.Item("Lungime")
If Err.Number = 0 Then
parameters1.Remove "Lungime"
Else
'TODO Stuff if parameter does not Exist
'create a new length type parameter, set its value to 0 for now
Dim length1 As Dimension
Set length1 = parameters1.CreateDimension("", "LENGTH", 0)
'if you want to rename the parameter
length1.Rename "Lungime"
Dim hybridBodies1 As HybridBodies
Set hybridBodies1 = oPart.HybridBodies
Dim hybridBody1 As HybridBody
Set hybridBody1 = hybridBodies1.Item("External References")
Set hybridShapes1 = hybridBody1.HybridShapes
Dim reference1 As Reference
Set reference1 = hybridShapes1.Item(1) 'get curve
Dim reference2 As Reference
Set reference2 = hybridShapes1.Item(2) 'get first point
Dim reference3 As Reference
Set reference3 = hybridShapes1.Item(3) 'get second point
'create a new formula to link to the parameter
Dim relations1 As Relations
Set relations1 = oPart.Relations
'make sure points are labeled MyEndPt1 and MyEndPt2 respectively
Dim formula1 As Formula
Set formula1 = relations1.CreateFormula("Formula.47", "", length1, "length( `External References\" & reference1.Name & "` ,`External References\" & reference2.Name & "` , `External References\" & reference3.Name & "` ) ")
End If
'end test
With UserFormTapeCheck.ListBox1
.AddItem
.List(i - 1, 0) = selection1.Item(i).LeafProduct.Name
.List(i - 1, 1) = strpartno
'test
.List(i - 1, 2) = Round(Abs(length1.Value), 2)
'end test
End With
'test
relations1.Remove "Formula.47"
parameters1.Remove "Lungime"
'end test
Next
selection1.Clear
Found another way to do it. In the relations there are some values saved , like: length of the electrical route and the distances from the each end of the route. With this 3 values you can calculate the length of the protection.
selection1.Search "CATElectricalSearch.Protection,all"
Dim i As Integer
Dim oInstProd As Product
Dim strpartno As String
Dim oPart As Part
Dim routelength As Double
Dim parameters1 As Parameters
Dim ParamPoint4 As Dimension
Dim ParamPoint3 As Dimension
Dim ParamTotal As Dimension
For i = 1 To selection1.Count
Set oInstProd = selection1.Item(i).LeafProduct
strpartno = oInstProd.ReferenceProduct.PartNumber
'test
Set oPart = oInstProd.ReferenceProduct.Parent.Part
Set parameters1 = oPart.Parameters
Set ParamPoint4 = parameters1.Item("ElecRouteBody.1\Point.4\Length.2")
Set ParamPoint3 = parameters1.Item("ElecRouteBody.1\Point.3\Length.1")
Set ParamTotal = parameters1.Item(oPart.Name & "\Elec_Length")
routelength = ParamTotal.Value - ParamPoint4.Value - ParamPoint3.Value
With UserFormTapeCheck.ListBox1
.AddItem
.List(i - 1, 0) = selection1.Item(i).LeafProduct.Name
.List(i - 1, 1) = strpartno
'test
.List(i - 1, 2) = Round(routelength, 2)
'end test
End With
I also encountered some small problems where the Elec_Length had a wrong value and I had to deleted & place the protection again for it to be updated and the macro to draw the correct values. For now I'm very pleased with how it turned out. Enjoy

Dictionary is empty after declaring it and populating

I'm not so good with VBA, thus I'm suspecting an issue with declaring and later on using the dictionary.
I've taken a different approach. Created two functions to creat the dicts.
The for loop first is checking if the Control in userform is textbox, than is getting column number (dict_col) and checks if needs to be formatted as date (dict_for).
However each time the second dict seems to be empty... When I check content of each dict separately (before the loop), it shows correct values.
Public Function import_columns(rng As Variant) As Dictionary
Dim dict As New Dictionary
Dim i As Long
Dim count_rows As Long
Dim dict_k As String, dict_i As String
count_rows = rng.Rows.Count
For i = 1 To count_rows
dict_k = rng(i, 2)
dict_i = rng(i, 1)
dict.Add dict_k, dict_i
Next i
Set import_columns = dict
End Function
Public Function import_format(rng As Variant) As Dictionary
Dim dict_f As New Dictionary
Dim i As Long
Dim count_rows As Long
count_rows = rng.Rows.Count
For i = 1 To count_rows
dict_f(rng(i, 1)) = 0
Next i
Set import_format = dict_f
End Function
Private Sub UserForm_Initialize()
'On Error GoTo ErrorHandle
Dim wb As Workbook
Dim rng_col As Range
Dim rng_format As Range
Dim dc_value As Integer
Dim ctrl As Control
Dim ctrlType As String
Dim ctrl_name As String
Dim key As Variant
Dim dict_col As Dictionary
Dim dict_for As Dictionary
Set rng_col = Application.Union(Range("columns_mark").Columns(3), Range("columns_mark").Columns(2))
Set rng_format = Arkusz25.Range("H1").CurrentRegion
Set dict_col = import_columns(rng_col)
Set dict_for = import_format(rng_format)
'Me.Results.Enabled = False
ListBox1.RowSource = "lista"
txt_results = ListBox1.ListCount
For Each key In dict_col.Keys
'If dict_col.Exists(key) Then
Debug.Print key
Debug.Print dict_col(key)
'End If
Next key
ctrlType = "TextBox"
For Each ctrl In Results.Controls
ctrl_name = ctrl.Name
If TypeName(ctrl) = ctrlType Then
dc_value = dict_col(ctrl_name)
If dict_for.Exists(ctrl_name) Then
ctrl = Format(Val(ListBox1.List(0, dc_value - 1)), "dd.mm.yyyy")
Else
ctrl = ListBox1.List(0, dc_value - 1)
End If
ctrl.Enabled = False
End If
Next ctrl

Can't delete module from VBA

I have a problem updating certain modules. In some modules I can delete and import the modules, but on others what happens is that the module is imported first and the original deleted later which adds a 1 at the end of the module name and messes up the code.
Here's how I do it:
I have the following Excel file which I can track who needs or has updated to the new module version. When I update the module version I just type on the correct username column Not Updated. Once the user opens his MS Project it runs the following code and changes the value to Updated.
Then I run the following on Project.Activate in VBA - MS Project 2016 to check if the module needs to update.
Dim xlapp As Object
Dim xlbook As Object
Dim sHostName As String
Dim modulesList_loc As String
Dim projectVBA_loc As String
Dim modulesVBA_loc As String
projectVBA_loc = "\\sharedNetwork\Project\VBA\"
modulesVBA_loc = projectVBA_loc & "Modules\"
modulesList_loc = projectVBA_loc & "Modules Updates.xlsx"
' Get Host Name / Get Computer Name
sHostName = Environ$("username")
Set xlapp = CreateObject("Excel.Application")
SetAttr modulesList_loc, vbNormal
Set xlbook = xlapp.Workbooks.Open(modulesList_loc)
Dim rng_modules As Range
Dim rng_usernames As Range
Dim username As Range
Dim atualizado As Range
Dim module_name As Range
Dim lastcol As Long
Dim lastcol_letter As String
Dim linha As Integer
Dim len1 As Integer
Dim len2 As Integer
Dim module_name_short As String
Dim actualizar As Integer
'LAST USERNAME COLUMN
With xlbook.Worksheets(1)
'Last Column
lastcol = .Cells(2, .Columns.Count).End(xlToLeft).Column
lastcol_letter = GetColumnLetter(lastcol, xlbook.Worksheets(1))
End With
'Usernames range
Set rng_usernames = xlbook.Worksheets(1).Range("E2:" & lastcol_letter & "2")
'Finds the correct username
Set username = rng_usernames.Find(sHostName)
Set rng_modules = xlbook.Worksheets(1).Range("A3") 'First module
Do While rng_modules.Value <> Empty
'Adds module if necessary
linha = rng_modules.Row
Set atualizado = username.Offset(linha - 2)
Set module_name = rng_modules.Offset(, 1)
If atualizado.Value = "Not Updated" Then
With ThisProject.VBProject
len1 = Len(module_name.Value)
len2 = len1 - 4
module_name_short = Left(module_name.Value, len2)
On Error Resume Next
.VBComponents.Remove .VBComponents(module_name_short)
.VBComponents.import modulesVBA_loc & module_name.Value
End With
atualizado.Value = "Updated"
End If
Set rng_modules = rng_modules.Offset(1)
Loop
xlbook.Close (True)
SetAttr modulesList_loc, vbReadOnly
Add DoEvents after the Remove method is called to give time for the Remove method to complete.
'On Error Resume Next
.VBComponents.Remove .VBComponents(module_name_short)
DoEvents
.VBComponents.import modulesVBA_loc & module_name.Value
If the Remove method is failing, there is likely an error occurring, but the On Error Resume Next line is hiding the error. Comment out the On Error... line and see what the error is and handle it rather than ignore it.

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
'...

How to close a freeform path with VBA

I have a series of freeform shapes copied from a third party application.
These freeform shapes are made of open paths, which cannot be "combined" in PowerPoint (only freeforms made with closed paths can be combined).
The following macro goes through every shape selected, and if it is a freeform, it will create a copy with a closed path and then delete the original shape.
Sub close_poly()
Dim myshp As Shape
Dim mycol As String
Dim mynode As ShapeNode
Dim myxvals As Variant
Dim myyvals As Variant
Dim myxcol As String
Dim myycol As String
Dim myffb As FreeformBuilder
Dim mynewshp As Shape
Dim myname As String
For Each myshp In ActiveWindow.Selection.ShapeRange
With myshp
If .Type = msoFreeform Then
'################ set all line segments to straight
'(makes things easier in my specific case but will not work in many)
nodecount = 1
While nodecount <= .Nodes.Count
.Nodes.SetSegmentType nodecount, msoSegmentLine
nodecount = nodecount + 1
Wend
'############## collect coordinates
myxcol = ""
myycol = ""
For Each mynode In myshp.Nodes
myxcol = myxcol & mynode.Points(1, 1) & ","
myycol = myycol & mynode.Points(1, 2) & ","
Next
myxcol = Left(myxcol, Len(myxcol) - 1)
myycol = Left(myycol, Len(myycol) - 1)
myxvals = Split(myxcol, ",")
myyvals = Split(myycol, ",")
'##############create new freeform
Set myffb = ActiveWindow.View.Slide.Shapes.BuildFreeform(msoEditingAuto, myxvals(0), myyvals(0))
For i = 1 To UBound(myxvals)
myffb.AddNodes msoSegmentLine, msoEditingAuto, myxvals(i), myyvals(i)
Next i
myffb.AddNodes msoSegmentLine, msoEditingAuto, myxvals(0), myyvals(0)
Set mynewshp = myffb.ConvertToShape
myshp.PickUp
mynewshp.Apply
myname = myshp.Name
myshp.Delete
mynewshp.Name = myname
End If
End With
Next myshp
End Sub
Question: is there a simpler way to mimic the program's "close path" function in VBA?
Cheers