Excel 2010: Pull Images Contained in Folder into Excel Cells - vba

I have a folder which contains a spreadsheet. Alongside this spreadsheet (within the same folder which the spreadsheet is contained in), is a folder named "images". This folder may have anywhere between 0 and 10,000 images contained within it as separate png files.
The files are named like this:
00001.png
...
00010.png
...
00100.png
...
01000.png
...
10000.png
(where "..." symbolised a gap between multiple files, keep in mind that the files increase in increments of 1, e.g: 00001.png is directly followed by 00002.png).
I require that the aforementioned spreadsheet (contained within the same folder as the "images" folder, but not within the "images" folder itself) is able to pull through however many of these images happen to be present to the 10,000 long cell range of 'C3:C10002' (C3 to C10002).
The images should only be pulled through if present and if not present, the spreadsheet / VBA macro/script should not crash to the detriment of the user.
This is extremely likely to require an Excel VBA macro of some sort that can be run at the press of a button (I know how to insert macro buttons).
The script should not alter the size/dimensions of the image(s). Containing cells should have their width and height adjusted to fit the images perfectly.
I understand that Excel cells have a maximum height / width and that the images will have to be pre-optimised to fit the cells. I'd like the images to display as thumbnails of roughly 3 inches wide by 1.6 inches tall (unsure what that is in pixels!)
I'd greatly appreciate any help... Even if you can't suggest something which accomplishes all of this, "best shots" will be warmly welcome.

I have this code that helps add an image to the sheet, you could modify it to loop within multiple files and add the image file based on the file name:
Sub AddPictures()
DirForImages = "S:\TCarnevale\Overdrive Images\"
Dim counter As Integer
Dim vsn As Boolean
Dim myrange As Range
ActiveSheet.Pictures.Delete
Range("Y15").Select
For I = 0 To 400
DoEvents
'Set picture range depending on count, *modify below set of code to add/remove styles*
Set Rng = Range("A8")
'get the style number to pull the image from the directory
styleinfo = ActiveCell.Value
If Dir(DirForImages & styleinfo & ".jpg") <> "" Then
Set pic = ActiveSheet.Shapes.AddPicture(DirForImages & styleinfo & ".jpg", False, True, 1, 1, 1, 1)
'resize the image
With pic
.Height = 100
.Width = 75
.Left = Rng.Left
.Top = Rng.Top
End With
counter = counter + 1
ElseIf Dir(DirForImages & styleinfo & ".png") <> "" Then
Set pic = ActiveSheet.Shapes.AddPicture(DirForImages & styleinfo & ".png", False, True, 1, 1, 1, 1)
'resize the image
With pic
.Height = 100
.Width = 75
.Left = Rng.Left
.Top = Rng.Top
'
End With
counter = counter + 1
Else
counter = counter + 1
End If
ActiveCell.Offset(1, 0).Select
Next
Range("Y17").Select
Range("A1").Select
End Sub
This code is checking for PNG files as well as JPG files and re sizing them in pixels. You can use this calculator to convert inches to pixels.

I don't see this working out the way you expect.
First of all, trying to display 10,000 PNG files in a spreadsheet strikes me as a seriously bad idea. The memory requirements could easily lock up your computer or crash Excel.
Secondly, settings cell sizes in VBA can be confusing. Cell height is specified in points by default, while width is measured based on the default font.
https://support.office.com/en-ca/article/Change-the-column-width-and-row-height-72f5e3cc-994d-43e8-ae58-9774a0905f46
Third, you'll need to have images with the correct dimensions and ppi/dpi resolution. Excel respects printing dimensions, so a 300 pixel image at 96ppi will display differently than a 300 pixel image at 150ppi.
Fourth, displays aren't measured in inches like paper sizes. Modern displays have a variety of resolutions, from 72dpi on the low end to over 300dpi for some mobile devices and Retina/HiDPI screens. This will affect how large on-screen a picture will look.
Having said all that:
I would batch process images in a program like Photoshop to set them (for your needs) at 300 pixels wide, 96ppi.
I would NOT try to show 10,000 images in a spreadsheet. Divide it up into several files.
Finally, some quickly cobbled-together code that might be a starting point:
Dim InsertLoc As Range
Dim i As Integer
Dim PName As String
Sub Macro1()
i = 2
PName = ""
Columns("A:A").ColumnWidth = 43
Application.ScreenUpdating = False
For i = 1 To 10
Set InsertLoc = Range("A" & CStr(i))
InsertLoc.Select
PName = "C:\Users\user\Desktop\" & i & ".jpg"
Rows(i).RowHeight = 150
On Error Resume Next
ActiveSheet.Pictures.Insert PName
Next i
Application.ScreenUpdating = True
End Sub

Related

Changing the Aspect Ratio without distortion and without black bars around the edges (depending upon the screen size it is played on)

I have a set of two PowerPoint presentations that I have coded using VBA to interact together (one has buttons that allow navigation in another). Originally these were going to be displayed on static screens and so I had defined the sizes of these to fit the screens perfectly so that they filled the screens and sat next to each other to look like one application. However, due to Covid this is now going to be displayed in lecture theatres on a projector, using different laptops each time, and so I need the presentations to be dynamic and adjust to the screens of the user.
I managed to code such so that the presentations fill the screens depending upon the user. See my code (note that both presentations aren't opened by this code as I also have code externally so that the entire thing can be opened externally with a click of a VBScript File):
Sub Resize_Presentations()
Dim PPT1 As Object
Set PPT1 = CreateObject("PowerPoint.Application")
Set PPT1 = Presentations("Stand-up Wall - From Drive\Stand Up Title Page - With Macros.pptm")
Dim PPT2 As Object
Set PPT2 = CreateObject("PowerPoint.Application")
Set PPT2 = Presentations("Stand Up Summary and Breakdowns - With Macros.pptm")
'define screen height and width for resizing'
PPT1.Application.ActiveWindow.WindowState = ppWindowMaximized
windWidth = Application.Width
windHeight = Application.Height
'minimise windows for playing presentations'
PPT1.Application.ActiveWindow.WindowState = ppWindowMinimized
PPT2.Application.ActiveWindow.WindowState = ppWindowMinimized
'play title presentation in defined size'
With PPT1.SlideShowSettings
.ShowType = ppShowTypeSpeaker
With .Run
.Top = 0
.Left = 0
.Width = windWidth / 3
.Height = windHeight
End With
End With
'play breakdown and summary presentation in defined size'
With PPT2.SlideShowSettings
.ShowType = ppShowTypeSpeaker
With .Run
.Top = 0
.Left = windWidth / 3
.Width = (windWidth / 3) * 2
.Height = windHeight
End With
End With
End Sub
Sub Open_Presentation()
Dim Ret
Dim Ret2
Dim PPT1 As Object
Set PPT1 = CreateObject("PowerPoint.Application")
Dim PPT2 As Object
Set PPT2 = CreateObject("PowerPoint.Application")
Ret = IsWorkBookOpen("C:\Users\RobinsonH7\Desktop\Stand-up Wall - From Drive\Stand Up Title Page - With Macros.pptm")
Ret2 = IsWorkBookOpen("C:\Users\RobinsonH7\Desktop\Stand-up Wall - From Drive\Stand Up Summary and Breakdowns - With Macros.pptm")
If Ret = True And Ret2 = False Then
Set PPT1 = Presentations("Stand-up Wall - From Drive\Stand Up Title Page - With Macros.pptm")
Set PPT2 = Presentations.Open(FileName:="C:\Users\RobinsonH7\Desktop\Stand-up Wall - From Drive\Stand Up Summary and Breakdowns - With Macros.pptm")
Call Resize_Presentations
Else: MsgBox "Close all stand-up wall slides"
End If
End Sub
Function IsWorkBookOpen(FileName As String)
Dim ff As Long, ErrNo As Long
On Error Resume Next
ff = FreeFile()
Open FileName For Input Lock Read As #ff
Close ff
ErrNo = Err
On Error GoTo 0
Select Case ErrNo
Case 0: IsWorkBookOpen = False
Case 70: IsWorkBookOpen = True
Case Else: Error ErrNo
End Select
End Function
Things I have tried adding to this:
Code that resizes the actual slides at the same time depending upon the screen of who is opening the presentation, this didn't work as it re-scaled all the items on the pages and made it a complete mess. Also tried grouping all objects and doing this and it still didn't work.
I tried trying to access the MasterSlide object and resize this to see if this would work in just increasing the slide size and not the objects. However, I couldn't get this to work, it seems the MasterSlide object can't be accessed directly.
Using a blank presentation that resizes and then copies and pastes everything from the other presentation so things are positioned correctly, however this messed up the layout and was very very very slow and I would prefer to avoid this option.
Ideally I want to just adjust the way the presentations are presented when played, almost like the presentation is stretched or zoomed to the fit the space. Any help would be much apprieciated!
Thanks in advance!
The syntax to reach the slide master is a little arcane due to PowerPoint development history. You refer to Designs instead of SlideMaster! Here is a statement that assesses whether there is more than one slide master:
If ActivePresentation.Designs.Count > 1 Then
To act on slide master shapes in a presentation with more than one slide master:
For Z = 1 To ActivePresentation.Designs.Count
For Each objShape In ActivePresentation.Designs(Z).SlideMaster.Shapes
After resizing, your code then has to find every shape to resize them back to their original proportions. In all versions of PowerPoint after 2010, presentation height remains constant at 7.5" and only the width varies. 10" wide for 4x3, 12" for 16x10 and 13.33" for 16x9. So you really only have to change the width of graphics to bring them back to their correct proportions.
As an alternative approach, you can use an add-in like PPTools Resize. Or you can use a SuperTheme that has aspect ratio variants for 4x3, 16x10 and 16x9, which will cover 99.99% of the displays out there. Here's more information about how SuperThemes work: SuperThemes

Understanding format of tables in PowerPoint (VBA 2010) (resize text to cell)

Following issue:
I declare tbl as Table in VBA. I want to show some tables in PowerPoint.
If the text of the cells are too long, the cells get big and they go beyond the slide limits. I want to avoid that. I just want to resize the text, that means, I just want that the text gets smaller, in order to fit within the cell. That means, cell-table size should not be changed!
How would you do that? I've tried:
ppPres.Slides(NumSlide).Shapes(NumShape).Table.Columns(col).Cells(1).Shape.TextFrame2.AutoSize = msoAutoSizeTextToFitShape
without success. Could you please tell me what's wrong and how would you proceed?
The error message is as follows:
Run-Time error '2147024809 (80070057)'
The specified value is out of range.
This is one of the oddities of the PowerPoint OM. The Shape object has all of the properties listed by IntelliSense, including the AutoSize property, yet when referenced within a table, some properties are not available. AutoSize is one of them. For example, if you place your cursor within a cell and open the the Format Shape pane in PowerPoint, you can see that the 3 AutoSize radio buttons are greyed out as well as the Wrap text in shape checkbox:
In the above example, which was created by adding the table via the PowerPoint UI rather than programmatically, I then copied the text from cell 2,1 to 1,2 with this code and the cell didn't change width but does change height, potentially forcing the table off of the bottom of a slide:
ActiveWindow.Selection.ShapeRange(1).Table.Cell(1,2).Shape.TextFrame.TextRange.Text=_
ActiveWindow.Selection.ShapeRange(1).Table.Cell(2,1).Shape.TextFrame.TextRange.Text
If it's this that you're trying to control, you'll need to do it manually in code via examining the table cell and/or table height after inserting your text and reducing the font size iteratively and rechecking each reduction level to see if the table is still out of the slide area.
This code does that for you:
Option Explicit
' =======================================================================
' PowerPoint Subroutine to iteratively reduce the font size of text
' in a table until the table does not flow off the bottom of the slide.
' Written By : Jamie Garroch of YOUpresent Ltd. http://youpresent.co.uk/
' Date : 05DEC2016
' Inputs : Table object e.g. ActiveWindow.Selection.ShapeRange(1).Table
' Outputs : None
' Dependencies : None
' =======================================================================
Sub FitTextToTable(oTable As Table)
Dim lRow As Long, lCol As Long
Dim sFontSize As Single
Const MinFontSize = 8
With oTable
Do While .Parent.Top + .Parent.Height > ActivePresentation.PageSetup.SlideHeight
For lRow = 1 To .Rows.Count
For lCol = 1 To .Columns.Count
With .Cell(lRow, lCol).Shape
sFontSize = .TextFrame.TextRange.Font.Size
If sFontSize > MinFontSize Then
.TextFrame.TextRange.Font.Size = sFontSize - 1
Else
MsgBox "Table font size limit of " & sFontSize & " reached", vbCritical + vbOKOnly, "Minimum Font Size"
Exit Sub
End If
End With
' Resize the table (effectively like dragging the bottom edge and allowing PowerPoint to set the table size to the text.
.Parent.Height = 0
Next
Next
Loop
End With
End Sub

Excel - Fill a cell with different colors

I need to fill a cell with different colors as in this picture (3 rows are merged vertically and colors are drawn manually in this picture using 3 rectangular shapes):
The only way I could find to fill part of a cell is using conditional formatting (by setting style as data bar and fill as solid) but it support only one color.
Is this possible with or without VBA?
It is possible.
I have found two ways to do that.
1- Using a black square shaped character (character code 2588 – vba: ActiveSheet.Cells(1, 1) = ChrW(&H2588)) and color them according to percentage. This character fills the cell height and also there is no spacing between them which allows filling a cell completely (Sure you should consider left indent in a cell). Only issue here that you cannot use a lot of characters in one cell; I use 30 of them and scale the number of characters according to 30 (ie. 50% red means 15 red character-2588).
2- It is same as what #Doktor Oswaldo has suggested: Inserting a plot in a cell using cell's position and size in pixels. This method has one big advantage: you can show the ratios exactly. In addition, you can fill a data series with a pattern as well. However if you have a lot of plots, you will sacrifice from Excel performance. For plot settings, I use following VBA code:
'Define var's
Dim src As Range, targetCell As Range
Dim chacha As ChartObject
'Set var's
Set src = Worksheets("Sheet1").Range("B1:B3")
Set targetCell = Worksheets("Sheet1").Range("C2")
'Create plot at the target cell
Set chacha = Sheets("Sheet1").ChartObjects.Add(targetCell.Left, targetCell.Top, targetCell.Width, targetCell.Height)
'Change plot settings to fill the cell
With chacha.Chart
.ChartType = xlBarStacked
.SetSourceData Source:=src, PlotBy:=xlRows
.Axes(xlValue).MinimumScale = 0
.Axes(xlValue).MaximumScale = 100
.Axes(xlCategory).Delete
.Axes(xlValue).Delete
.Legend.Delete
.PlotArea.Top = -50
.PlotArea.Left = -50
.PlotArea.Width = targetCell.Width
.PlotArea.Height = targetCell.Height
.ChartGroups(1).GapWidth = 0
End With
chacha.Chart.SeriesCollection(1).Format.Fill.ForeColor.RGB = RGB(255, 0, 0)
chacha.Chart.SeriesCollection(2).Format.Fill.ForeColor.RGB = RGB(0, 0, 255)
chacha.Chart.SeriesCollection(3).Format.Fill.ForeColor.RGB = RGB(255, 255, 0)
In the code I modified the series colors manually which can also be automatized. Following is the screenshot of both methods. The Cell "C1" is filled with block characters and "C2" is a chart.
Note: You might get an error at the line ".PlotArea.Top". To solve this issue, please check: Error setting PlotArea.Width in Excel, VBA (Excel 2010)

How can I "force" Excel to count page breaks correctly?

I am supposed to export some large data ranges from Excel to Powerpoint, one page per slide, and of course I should treat the page breaks to avoid "orphan" rows or columns.
I am trying to check how many pages I would have, vertically and horizontally, with a given zoom, by reading HPageBreaks.Count and VPageBreaks.Count, and then manually define the position of each break. The idea is to have approximately the same width and height on each page.
When I debug my code step-by-step, it runs nicely, and the logic seems ok, but if I run it "freely", the page breaks are completely off. Adding some MsgBox instructions, I can see that when I read HPageBreaks.Count (or vertical) I get the wrong values. (I can check the correct ones if I do manually what the code should do).
Searching on many many forums, I see some ugly workarounds like forcing a reset of PaperSize (ws.PageSetup.PaperSize = ws.PageSetup.PaperSize). After trying some of them, what seemed to work a bit better was to turn off PrintCommunication before a change to PageSetup, and then turn it back on. This worked well on most of my sheets, but on the really large ones (~750 rows x 80 columns, almost all cells with formulas), it simply doesn't.
Here an extract of the code:
'Reset page breaks
.ResetAllPageBreaks
'Set minimum acceptable zoom factor
Application.PrintCommunication = False 'This is the ugly workaround
.PageSetup.Zoom = 60
Application.PrintCommunication = True
MsgBox "Zoom = " & .PageSetup.Zoom 'Only for debugging
'Calculate the number of pages in width
Application.PrintCommunication = False
NPagesWide = .VPageBreaks.Count + 1
Application.PrintCommunication = True
MsgBox "NPagesWide = " & NPagesWide
'Find the higher zoom factor that can fit that number of pages
Application.PrintCommunication = False
.PageSetup.Zoom = 100
Application.PrintCommunication = True
Do While .VPageBreaks.Count > NPagesWide - 1
Application.PrintCommunication = False
.PageSetup.Zoom = .PageSetup.Zoom - 5
Application.PrintCommunication = True
Loop
MsgBox "Zoom = " & .PageSetup.Zoom
'Set average width per page and initialize column pointer
If HasTitleColumns Then 'Defined earlier
PageWidth = (PrintArea.Width + TitleColumns.Width * (NPagesWide - 1)) / NPagesWide
j = TitleColumns.Columns(TitleColumns.Columns.Count).Column + 1
Else
PageWidth = PrintArea.Width / NPagesWide
j = 1
End If
'Cycle vertical page breaks
For i = 1 To NPagesWide - 1
'Set width of TitleColumns
If HasTitleColumns Then
CumulWidth = TitleColumns.Width
Else
CumulWidth = 0
End If
'Cumulate columns width until the available page width
Do While CumulWidth + .Columns(j).Width <= PageWidth
CumulWidth = CumulWidth + .Columns(j).Width
j = j + 1
Loop
'Add the break
.VPageBreaks.Add .Columns(j + 1)
Next i
Any ideas why this happens, and how can I solve it?
Thanks,
I propose general advice to the issue when VBA code works fine while hitting F8 in the debug mode, but it doesn't not work after hitting F5 to run the whole macro.
Hint 1. Use ThisWorkbook.ActiveSheet instead of ActiveWorkbook.ActiveSheet whenever possible to reference the proper sheet. Use ThisWorkbook.Application instead of just Application. It could be that you have another Addin program working on in background, switching ActiveSheet to something else that you may not be aware of. Check other macros enabled and get rid of anything that you do not use. So before anything important in your code, try to get the focus for your sheet with ThisWorkbook.Activate. See the graph on that page: http://analystcave.com/vba-tip-day-activeworkbook-vs-thisworkbook/
Hint 2. Force Excel to wait in different way then DoEvents.
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) 'Place this line of code at the very top of module
Sleep 1 'This will pause execution of your program for 1 ms
https://stackoverflow.com/a/3891017/1903793
https://www.fmsinc.com/microsoftaccess/modules/examples/AvoidDoEvents.asp
Or alternatively:
Application.Calculate
If Not Application.CalculationState = xlDone Then
DoEvents
End If
https://stackoverflow.com/a/11277152/1903793
Hint 3. If the above still does not work for refreshing use:
ThisWorkbook.Connections("ConectionName").Refresh
ThisWorkbook.Application.CalculateUntilAsyncQueriesDone
https://stackoverflow.com/a/26780134/1903793
Another workaround for me was to set the PrintArea new (without .PrintCommunication = false)
wks.PageSetup.PrintArea = wks.PageSetup.PrintArea
This workaround does not affect any pagesetup settings (unlike the .Zoom property)
The problem only occurs if the PageView is set to xlPageLayoutView and there is data "outside" the printarea (UsedRange is larger than PrintArea) when running the macro.
So you can check if the sheet is in PageLayoutView before you do the workaround. If the sheet is in any other view the pagebreaks.count always works fine here.

How to move image in Excel using VBA?

I want to move image from one location in excel to another using VBA.
How can we do that?
If you need to change the position of the image within a given worksheet, you can use something like this:
ActiveSheet.Shapes.Range(Array("Picture 1")).Select
Selection.ShapeRange.IncrementLeft 100
You can adjust the direction and amount of motion by changing the parameters of the .Increment... command, to animate an image for example.
Another example : move a picture vertically to line up with a specific row
Sheets(1).Shapes("Picture 1").Top = Sheets(1).Rows(24).Top
If we are going quick and dirty and need to move between sheets the following works
Sub CutAndPasteAPicture(shapeName As String, fromSheet As String, toSheet As String, toRange As String)
'Cut and Paste
Sheets(fromSheet).Shapes(shapeName).Cut
Sheets(toSheet).Paste Sheets(toSheet).Range(toRange)
End Sub
Sub Example()
CutAndPasteAPicture "Picture 1", "Sheet1", "Sheet2", "D2"
End Sub
Here is the code to first insert picture to Excel and then adjust or resize the Picture. Later move the Picture towards down or right
'Insert the Picture from the path if its not present already
Set myPict = Thisworkbook.sheets(1).Range("A1:B5").Parent.Pictures.Insert(ThisWorkbook.Path & "\" & "mypic.jpg")
'Adjust the Picture location
myPict.Top = .Top
myPict.Width = .Width
myPict.Height = .Height
myPict.Left = .Left
myPict.Placement = xlMoveAndSize
'myPict.LockAspectRatio = msoTriStateMixed
'Change the width of the Picture
myPict.Width = 85
'change the Height of the Picutre
myPict.Height = 85
End With
'Select the Picutre
myPict.Select
'Move down the picture to 3 points. Negative value move up
Selection.ShapeRange.IncrementTop 3
'Move towards right upto 5 points. Negative value moves towards right
Selection.ShapeRange.IncrementLeft 5