Copy Powerpoint Shapes to Excel - identically looking slides, different shape order - vba

I have a presentation with 32 identically looking slides (initally macro generated, later had human touch).
Simplified look:
Title (not formatted as a headline, though)
picture
Content1
Content2
Content3
I now want to copy the text back to Excel. Although all slides look identical, the order of the shapes in slide.Shapes seems different.
For every slide I want a row, with the colums in the same order: Title, Content1, Content2,Content3 but some are Content1,Content3,Title,Content2
(or any other order)
Why is this?
My code:
Sub CopyFromPowerpoint()
'Prepare variables
Dim PowerPoint As PowerPoint.Application
Dim activeSlide As PowerPoint.Slide
Dim curShape As PowerPoint.shape
Dim RowCounter As Integer
Dim ColumnCounter As Integer
Dim tmp As String
'Set powerPoint
Set PowerPoint = GetObject(, "PowerPoint.Application")
tmp = "XXX" 'this should never be pasted
RowCounter = 1
ColumnCounter = 1
For Each Slide In PowerPoint.Presentations(1).Slides
Set activeSlide = PowerPoint.Presentations(1).Slides(RowCounter)
For Each shape In activeSlide.Shapes
Set curShape = activeSlide.Shapes(ColumnCounter)
If curShape.TextFrame.HasText Then tmp = curShape.TextFrame.TextRange
If curShape.TextFrame.HasText Then Worksheets("nameofsheet").Cells(RowCounter, ColumnCounter).Value = tmp
ColumnCounter = ColumnCounter + 1
Next
ColumnCounter = 1
RowCounter = RowCounter + 1
Next
End Sub

What helped me in the end was multiplying the left and top position for each textbox. That value was unique enough for the relevant content to end up in the same column for each slide. Ordering the columns themselves in Excel, I still needed to do manually but that was an easy task. The quick sort algorithm I got from another stackoverflow question
Sub CopyFromPowerpoint()
'Prepare variables
Dim PowerPoint As PowerPoint.Application
Dim activeSlide As PowerPoint.Slide
Dim curShape As PowerPoint.shape
Dim RowCounter As Integer
Dim ColumnCounter As Integer
Dim shapeCounter As Long
Dim tmp(20) As String
Dim arr(20) As Long
Dim tmpMult As Long
'Set powerPoint
Set PowerPoint = GetObject(, "PowerPoint.Application")
RowCounter = 1
ColumnCounter = 1
For Each Slide In PowerPoint.Presentations(1).Slides
Set activeSlide = PowerPoint.Presentations(1).Slides(RowCounter)
'Loop through shapes, note their position from top and left, multiply them and sort it
shapeCounter = LBound(arr)
For Each shape In activeSlide.Shapes
arr(CInt(shapeCounter)) = shape.Top * shape.Left
shapeCounter = shapeCounter + 1
Next
Call QuickSort(arr, LBound(arr), UBound(arr))
'Loop through shapes again and copy shape text into relevant position in text array
For Each shape In activeSlide.Shapes
If shape.TextFrame.HasText Then
For i = LBound(arr) To UBound(arr)
tmpMult = shape.Top * shape.Left
If arr(i) = tmpMult Then tmp(i) = shape.TextFrame.TextRange
tmpMult = 0
Next i
End If
Next
'Loop through text array and paste into worksheet
For i = LBound(tmp) To UBound(tmp)
Worksheets("uebergabe").Cells(RowCounter, i + 1).Value = tmp(i)
Next i
'Reset for next slide
RowCounter = RowCounter + 1
shapeCounter = 0
For i = LBound(arr) To UBound(arr)
arr(i) = 0
tmp(i) = ""
Next i
Next
End Sub
Public Sub QuickSort(vArray As Variant, inLow As Long, inHi As Long)
Dim pivot As Variant
Dim tmpSwap As Variant
Dim tmpLow As Long
Dim tmpHi As Long
tmpLow = inLow
tmpHi = inHi
pivot = vArray((inLow + inHi) \ 2)
While (tmpLow <= tmpHi)
While (vArray(tmpLow) < pivot And tmpLow < inHi)
tmpLow = tmpLow + 1
Wend
While (pivot < vArray(tmpHi) And tmpHi > inLow)
tmpHi = tmpHi - 1
Wend
If (tmpLow <= tmpHi) Then
tmpSwap = vArray(tmpLow)
vArray(tmpLow) = vArray(tmpHi)
vArray(tmpHi) = tmpSwap
tmpLow = tmpLow + 1
tmpHi = tmpHi - 1
End If
Wend
If (inLow < tmpHi) Then QuickSort vArray, inLow, tmpHi
If (tmpLow < inHi) Then QuickSort vArray, tmpLow, inHi
End Sub

Related

How to insert an Input Box with non constant number of strings to enter based on selected shapes

I would like to have an InputBox that allows to enter text in a table, for a number of cells taht depends on a previous selection of shapes, however I do not know how to setup the array, could someone show me how?
EDIT:
I added the below loop with array but I get Wrong number of arguments error
Sub InputBox()
Dim iRow As Integer
Dim iColumn As Integer
Dim MasterTitle As Shape
Dim oShapeNavigator As Shape
Dim oSlide As Slide
Dim oSlides As slides
Set oSlides = ActivePresentation.slides
Set MasterTitle = ActivePresentation.SlideMaster.Shapes.Placeholders(1)
Dim Shapesarray() As Shape
Dim TextTable As String
Dim nCounter As Long
ReDim Shapesarray(1 To ActiveWindow.Selection.ShapeRange.Count)
Dim V As Long
For V = 1 To ActiveWindow.Selection.ShapeRange.Count - 1
Set Shapesarray(V) = ActiveWindow.Selection.ShapeRange(V)
Next V
Dim p As Integer
p = 1
ReDim TextArray(1 To V)
Do While p <= V
TextArray(p) = InputBox(Prompt:="Enter Text for cell N." & p)
TextTable = TextArray(p)
Debug.Print TextTable
p = p + 1
Loop
For Each oSlide In oSlides ' ActivePresentation.Slides
If oSlide.CustomLayout.Name = "Section Header" Then
nCounter = nCounter + 1
ElseIf nCounter < V Or nCounter = V Then
Set oShapeNavigator = oSlide.Shapes.AddTable(1, V * 2, Left:=10, Top:=10, Width:=MasterTitle.Width * 11 / 12, Height:=2)
With oShapeNavigator.Table '## TABLE ##
For iRow = 1 To .Rows.Count
For iColumn = 2 To .Columns.Count Step 2
For p = 1 To TextTable.Count
With .Cell(iRow, iColumn).Shape.TextFrame.TextRange
.Text = TextTable
End With
Next
Next iColumn
Next iRow
End With
End If
Next oSlide
End Sub

Align shapes flush/stacked/touching

I'm trying to get a selection of shapes in order from right to left. I found a routine by John Wilson on vbaexpress on which I based my code.
The sorting works perfectly when I select item by item by clicking on the shapes but it doesn't respect the "visible order" of shapes if I select them by "lassoing" with my mouse.
In case of dragging my mouse over the shapes to select them, the routine should respect the visible order of shapes.
Thanks in advance.
Sub AlignFlush()
Dim oshpR As ShapeRange
Dim oshp As Shape
Dim L As Long
Dim rayPOS() As Single
Set oshpR = ActiveWindow.Selection.ShapeRange
ReDim rayPOS(1 To oshpR.Count)
'add to array
For L = 1 To oshpR.Count
rayPOS(L) = oshpR(L).Left
Next L
'sort
Call sortray(rayPOS)
'apply
For L = 1 To oshpR.Count
If L = 1 Then
Set oshp = Windows(1).Selection.ShapeRange(1)
PosTop = oshp.Top
PosNext = oshp.Left + oshp.Width
Else
Set oshp = Windows(1).Selection.ShapeRange(L)
oshp.Top = PosTop
oshp.Left = PosNext
PosNext = oshp.Left + oshp.Width
End If
Next L
End Sub
Sub sortray(ArrayIn As Variant)
Dim b_Cont As Boolean
Dim lngCount As Long
Dim vSwap As Long
Do
b_Cont = False
For lngCount = LBound(ArrayIn) To UBound(ArrayIn) - 1
If ArrayIn(lngCount) > ArrayIn(lngCount + 1) Then
vSwap = ArrayIn(lngCount)
ArrayIn(lngCount) = ArrayIn(lngCount + 1)
ArrayIn(lngCount + 1) = vSwap
b_Cont = True
End If
Next lngCount
Loop Until Not b_Cont
End Sub
Some comments on your existing code:
Array counts always start at 0 unless you use the Option Base statement to set it to a different number.
When you use ReDim, most of the time, you want to use the Preserve keyword, or the ReDim obliterates the existing array contents. But in this case, we know the array size ahead of time, so Preserve is not necessary.
You call sortray, but didn't include it in your listing. I've added a sorting routine.
But then you make no use of the sorted array in the section where you position the shapes.
Working macro (based on your description of what you mean by "visible order" being the left-to-right sequence):
Since you use the left position of the leftmost shape to apply to the others, here's a simpler way to do that:
Sub AlignFlushLeftWithLeftmostShape()
Dim ShpCount As Long
Dim oshpR As ShapeRange
Dim L As Long
Dim rayPOS() As Single
Set oshpR = ActiveWindow.Selection.ShapeRange
ShpCount = oshpR.Count
ReDim rayPOS(ShpCount - 1)
For L = 0 To ShpCount - 1
rayPOS(L) = oshpR(L + 1).Left
Next L
Call BubbleSort(rayPOS)
For x = 1 To ShpCount
oshpR(x).Left = rayPOS(0)
Next x
End Sub
Sub BubbleSort(arr)
Dim lTemp As Long
Dim i As Long
Dim j As Long
Dim lngMin As Long
Dim lngMax As Long
lngMin = LBound(arr)
lngMax = UBound(arr)
For i = lngMin To lngMax - 1
For j = i + 1 To lngMax
If arr(i) > arr(j) Then
lTemp = arr(i)
arr(i) = arr(j)
arr(j) = lTemp
End If
Next j
Next i
End Sub

How to unlock columns in Excel using VB.Net?

Good Morning
I have a program in VB.Net that exports a file from Datagridview into Excel file
and it looks like this.
My goal here is how can I lock some columns? based on the Image above? Lock all columns except the column that has a color yellow? I mean all the columns except the yellow are uneditable.
Here is my code in exporting excel
Try
If DataGridView1.Rows.Count = 0 Then
MsgBox("Nothing to Export")
Else
Dim ExcelApp As Object, ExcelBook As Object
Dim ExcelSheet As Object
Dim i As Integer
Dim J As Integer
Dim rowIndex As Integer = 1
Dim total As Double = 0
Dim indexTotal As Integer
ExcelApp = CreateObject("Excel.Application")
ExcelBook = ExcelApp.WorkBooks.Add
ExcelSheet = ExcelBook.WorkSheets(1)
With ExcelSheet
rowIndex += 2
For Each column As DataGridViewColumn In DataGridView1.Columns
.cells(rowIndex, column.Index + 1) = column.HeaderText
Next
.Range(.Cells(rowIndex, 1), .Cells(rowIndex, DataGridView1.Columns.Count)).Font.Bold = True
rowIndex += 1
For i = 0 To Me.DataGridView1.RowCount - 1
.cells(rowIndex, 1) = Me.DataGridView1.Rows(i).Cells("ItemCode").Value
For J = 1 To DataGridView1.Columns.Count - 1
If IsNumeric(DataGridView1.Rows(i).Cells(J).Value) Then
.cells(rowIndex, J + 1).NumberFormat = "#,##0.00"
.cells(rowIndex, J + 1) = DataGridView1.Rows(i).Cells(J).Value
Else
.cells(rowIndex, J + 1) = DataGridView1.Rows(i).Cells(J).Value
End If
'You can test also by index for example : if J = indexofTotalColumn then
If DataGridView1.Columns(J).Name = "Total" Then
total += DataGridView1.Rows(i).Cells(J).Value
indexTotal = J
End If
Next
rowIndex += 1
.Columns("A:Z").EntireColumn.AutoFit()
.Columns("L").ColumnWidth = 0
.cells(5).Locked = False
Next
.Protect("fakepwd")
End With
ExcelApp.Visible = True
ExcelSheet = Nothing
ExcelBook = Nothing
ExcelApp = Nothing
End If
Catch
End Try
TYSM for help
Set the Locked property of the cell to false, where J+1 is the desired column number.
For example to unlock column 5 :
For J = 1 To DataGridView1.Columns.Count - 1
If J=5 then
.cells(rowIndex, J + 1).Locked=False
End if
If IsNumeric(DataGridView1.Rows(i).Cells(J).Value) Then
..........
In the code, once you are done populating data in sheet, protect the sheet
Next
.Protect ("fakepwd")
End With

Remove ALL duplicates from column A in Excel

I am looking for a macro that can remove ALL duplicates from column A.
Input:
John
Jimmy
Brenda
Brenda
Tom
Tom
Todd
Output:
John
Jimmy
Todd
I am working with a large set of data, and Excel isn't cooperating. Can't seem to find a solution online that works.
Thanks!
When you want to de-duplicate your list, that is make sure you only have ONE item left of each, you can to this:
In Excel 2007 and above you have a Remove Duplicates in the Data menu, which will do it for you.
In Excel 2003 and earlier you can use the Advanced Filter in the Data/Filter menu:
And then copy-paste the results in a new sheet.
You can see the full procedure here.
Otherwise it is a tedious macro to write (a recursive loop to check if the value exist in the set). It can be done, but do you really need it?
But if you want to actually delete all entries that are the same then using #Eoins's macro will do the job, but a bit modified as follows:
Option Explicit
Sub DeleteDuplicate()
Dim x, Y As Long
Dim LastRow As Long
Dim myCell As String
LastRow = Range("A1").SpecialCells(xlLastCell).Row
For x = LastRow To 1 Step -1
myCell = Range("A" & x).Text
If Application.WorksheetFunction.CountIf(Range("A1:A" & x), myCell) > 1 Then
For Y = x To 1 Step -1
If Range("A" & Y).Text = myCell Then
Range("A" & Y).EntireRow.Delete
End If
Next Y
End If
Next x
End Sub
As your request is for a macro, please try this:
Excel 2007+
ActiveSheet.Range("A:A").RemoveDuplicates
Here is your option for Excel 2003
Option Explicit
Sub DeletDuplicate()
Dim x As Long
Dim LastRow As Long
LastRow = Range("A65536").End(xlUp).Row
For x = LastRow To 1 Step -1
If Application.WorksheetFunction.CountIf(Range("A1:A" & x), Range("A" & x).Text) > 1 Then
Range("A" & x).EntireRow.Delete
End If
Next x
End Sub
Here is a recursive loop just in case you want it :)
It's actually 2 procedures, the first one sorts the list and the second one removes duplicates
'----------------------------------------------------------------------
'--SORT A 1D ARRAY NUMERICALLY-ALPHABETICALLY(TAKEN FROM StackOverflow)
'----------------------------------------------------------------------
Public Sub QuickSort(vArray As Variant, inLow As Long, inHi As Long)
Dim pivot As Variant
Dim tmpSwap As Variant
Dim tmpLow As Long
Dim tmpHi As Long
tmpLow = inLow
tmpHi = inHi
pivot = vArray((inLow + inHi) \ 2)
While (tmpLow <= tmpHi)
While (vArray(tmpLow) < pivot And tmpLow < inHi)
tmpLow = tmpLow + 1
Wend
While (pivot < vArray(tmpHi) And tmpHi > inLow)
tmpHi = tmpHi - 1
Wend
If (tmpLow <= tmpHi) Then
tmpSwap = vArray(tmpLow)
vArray(tmpLow) = vArray(tmpHi)
vArray(tmpHi) = tmpSwap
tmpLow = tmpLow + 1
tmpHi = tmpHi - 1
End If
Wend
If (inLow < tmpHi) Then QuickSort vArray, inLow, tmpHi
If (tmpLow < inHi) Then QuickSort vArray, tmpLow, inHi
End Sub
'---------------------------------------
'--REMOVE DUPLICATES AND BLANKS FROM SORTED 1D ARRAY
'---------------------------------------
Public Function RemoveDuplicatesBlanks_1DSorted(Arr As Variant) As Variant
Dim i As Long, iMin As Long, iMax As Long, Cnt As Long
Dim TArr As Variant, TArr2() As Variant
TArr = Arr
iMin = LBound(TArr)
iMax = UBound(TArr)
i = iMin
Do While i <= iMax
If TArr(i) = vbNullString Then
Cnt = Cnt + 1
ElseIf i < iMax Then
If TArr(i) = TArr(i + 1) Then
TArr(i) = Empty
Cnt = Cnt + 1
End If
End If
i = i + 1
Loop
ReDim TArr2(iMin To (iMax - Cnt))
Cnt = iMin
For i = iMin To iMax
If Not TArr(i) = vbNullString Then
TArr2(Cnt) = TArr(i)
Cnt = Cnt + 1
End If
Next i
RemoveDuplicatesBlanks_1DSorted = TArr2
End Function
The way these are setup you would use them like this.....
QuickSort MyArray, LBound(MyArray), UBOUND(MyArray)
MyArray = RemoveDuplicatesBlanks_1DSorted(MyArray)
These work only for 1 dimensional arrays, I also have them for 2 dimensional arrays if you need those.
I've used these many times and they are very fast, a lot faster than most methods so if you have large lists its worth using these methods.
----ADDITIONAL INFORMATION----
The ExtractArrayColumn function is beneath this code....This code here is how you would use all these procedures
Private sub RemoveDuplicate()
Dim MyRangeArray As Variant, MyArray As Variant
MyRangeArray = Range("A1:A100").Value
MyArray = ExtractArrayColumn(MyRAngeArray,1)
QuickSort MyArray, LBound(MyArray), UBOUND(MyArray)
MyArray = RemoveDuplicatesBlanks_1DSorted(MyArray)
Range("A1:A100").Value = MyArray
End Sub
Public Function ExtractArrayColumn(Array_Obj As Variant, Column_Index As Long) As Variant
Dim TArr() As Variant
Dim L1 As Long, H1 As Long
Dim i As Long
L1 = LBound(Array_Obj, 1)
H1 = UBound(Array_Obj, 1)
ReDim TArr(L1 To H1)
For i = L1 To H1
TArr(i) = Array_Obj(i, Column_Index)
Next i
ExtractArrayColumn = TArr
End Function

How To Make Selected Columns the Same Width in a PowerPoint Table?

Is there a way to programmatically make only some columns in a PowerPoint table the same width? It should be their combinded width, divided by the number of columns, but I can't figure out a way to do this.
I've answered this before somewhere, couldn't seem to find that reference. Here's the code you'll need, just make sure you have the columns selected that you want to be distributed evenly
Sub DistributeSelectedColumnsEvenly()
Dim sel As Selection
Set sel = ActiveWindow.Selection
Dim fColumn As Integer
fColumn = 0
Dim lColumn As Integer
Dim columnsWidth As Integer
With sel
If .Type = ppSelectionShapes Then
If .ShapeRange.Type = msoTable Then
Dim tbl As Table
Set tbl = .ShapeRange.Table
Dim tblColumnCount As Integer
tblColumnCount = tbl.Columns.Count
For colNum = 1 To tblColumnCount
If tbl.Cell(1, colNum).Selected Then
columnsWidth = columnsWidth + tbl.Cell(1, colNum).Parent.Columns(colNum).Width
If fColumn = 0 Then
fColumn = colNum
End If
lColumn = colNum
End If
Next
Dim columnCount As Integer
columnCount = (lColumn - fColumn) + 1
Dim columnWidth As Integer
columnWidth = columnsWidth / columnCount
For columnIndex = fColumn To lColumn
tbl.Columns(columnIndex).Width = columnWidth
Next
End If
End If
End With
End Sub