Embedding Contextual Object Icon using Excel VBA - vba

I'm somewhat inexperienced with VBA coding and need help inserting an object into an Excel spreadsheet based on a cell reference.
What I need to be able to do is pull a PDF in as an object icon based on another cell's contents, overlay it over a specific cell and then move down a row to repeat indefinitely until a blank cell occurs.
One problem with this is that the item number in column A may not have a corresponding file that matches it in my file directory, so I need for the code to run regardless of an error in the search for the PDF. Here's an example of the layout of my sheet. Spreadsheet Example
Here's the small bit of code that I have cobbled together already, however I'm not confident that it's even remotely usable.
Dim varItem
Sub Insert_PDF_Object()
Range("A2").Select
Do Until IsEmpty(ActiveCell)
varItem= ActiveCell.Offset(0, 0)
ActiveCell.Offset(0, 0).RowHeight = 80
Get_Object
ActiveCell.Offset(1, 0).Select
Loop
Range("A3").Select
End Sub
Sub Get_Object()
Worksheets("Sheet1").OLEObjects.Add Filename:="c:\Test\"&
Range("A2").Value & ".pdf", Link:=False, DisplayAsIcon:=True,
Left:=40, Top:=40, Width:=100, Height:=100
End Sub
Any help anyone can provide would really be great!

Try this.
Note if you want to use DisplayAsIcon:=True then you need to supply the path to the icon file and the index.
https://msdn.microsoft.com/en-us/vba/excel-vba/articles/oleobjects-add-method-excel
Sub Insert_PDF_Object()
Dim c As Range, fName
Set c = ActiveSheet.Range("A2")
Do While Len(c.Value) > 0
fName = "c:\_Stuff\Test\" & c.Value & ".pdf"
If Dir(fName, vbNormal) <> "" Then
c.RowHeight = 80
c.Worksheet.OLEObjects.Add Filename:=fName, Link:=False, _
DisplayAsIcon:=True, _
Left:=c.Offset(0, 1).Left, _
Top:=c.Offset(0, 1).Top, _
Width:=80, Height:=80
End If
Set c = c.Offset(1, 0)
Loop
End Sub

Related

Excel VBA - Searching in a Loop

First, my code (below) works, but I am trying to see if it can be simplified. The macro in which this code is located will have a lot of specific search items and I want to make it as efficient as possible.
It is searching for records with a specific category (in this case "Chemistry") then copying those records into another workbook. I feel like using Activate in the search, and using Select when moving to the next cell are taking too much time and resources, but I don't know how to code it to where it doesn't have to do that.
Here are the specifics:
Search column T for "Chemistry"
Once it finds "Chemistry", set that row as the "top" record. e.g. A65
Move to the next row down, and if that cell contains "Chemistry", move to the next row (the cells that contain "Chemistry" will all be together"
Keep going until it doesn't find "Chemistry", then move up one row
Set that row for the "bottom" record. e.g. AX128
Combine the top and bottom rows to get the range to select. e.g. A65:AX128
Copy that range and paste it into another workbook
Here is the code:
'find "Chemistry"
Range("T1").Select
Cells.Find(What:="Chemistry", After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Activate
'set top row for selection
toprow = ActiveCell.Row
topcellselect = "A" & toprow
'find all rows for Chemistry
Do While ActiveCell = "Chemistry"
ActiveCell.Offset(1, 0).Select
Loop
ActiveCell.Offset(-1, 0).Select
'set bottom row for selection
bottomrow = ActiveCell.Row
bottomcellselect = "AX" & bottomrow
'define selection range from top and bottom rows
selectionrange = topcellselect & ":" & bottomcellselect
'copy selection range
Range(selectionrange).Copy
'paste into appropriate sheet
wb1.Activate
Sheets("Chemistry").Select
Range("A2").PasteSpecial
Thanks in advance for any help!
You never need to select or activate unless that's really what you want to do (at the end of the code, if you want the user to see a certain range selected). To remove them, just take out the activations and selections, and put the things on the same line. Example:
wb1.Activate
Sheets("Chemistry").Select
Range("A2").PasteSpecial
Becomes
wb1.Sheets("Chemistry").Range("A2").PasteSpecial
For the whole code; I just loop thorugh the column and see where it starts and stops being "chemistry". I put it in a Sub so you only have to call the sub, saying which word you're looking for and where to Paste it.
Sub tester
Call Paster("Chemistry", "A2")
End sub
Sub Paster(searchWord as string, rngPaste as string)
Dim i as integer
Dim startRange as integer , endRange as integer
Dim rng as Range
With wb1.Sheets("Chemistry")
For i = 1 to .Cells(Rows.Count,20).End(XlUp).Row
If .Range("T" & i ) = searchWord then 'Here it notes the row where we first find the search word
startRange = i
Do until .Range("T" & i ) <> searchWord
i = i + 1 'Here it notes the first time it stops being that search word
Loop
endRange = i - 1 'Backtracking by 1 because it does it once too many times
Exit for
End if
Next
'Your range goes from startRange to endRange now
set rng = .Range("T" & startRange & ":T" & endRange)
rng.Copy
.Range(rngPaste).PasteSpecial 'Paste it to the address you gave as a String
End with
End sub
As you can see I put the long worksheet reference in a With to shorten it. If you have any questions or if it doesn't work, write it in comments (I haven't tested)
The most efficient way is to create a Temporary Custom Sort Order and apply it to your table.
Sub MoveSearchWordToTop(KeyWord As String)
Dim DestinationWorkSheet As Workbook
Dim SortKey As Range, rList As Range
Set SortKey = Range("T1")
Set rList = SortKey.CurrentRegion
Application.AddCustomList Array(KeyWord)
rList.Sort Key1:=SortKey, Order1:=xlAscending, Header:=xlGuess, _
OrderCustom:=Application.CustomListCount + 1, MatchCase:=False, _
Orientation:=xlTopToBottom, DataOption1:=xlSortNormal
Application.DeleteCustomList Application.CustomListCount
Set DestinationWorkSheet = Workbooks("Some Other Workbook.xlsx").Worksheets("Sheet1")
rList.Copy DestinationWorkSheet.Range("A1")
End Sub

How to loop through rows, save these as variables and use them as variables VBA

I'm trying to store values in sheets as a variable, and then go on to reference a sheet using that variable as well as use it to filter by.
This will be looped through until the program reaches the first empty cell.
The relevant code I have so far is:
Sub Program()
Dim i As Integer
i = 2
Do Until IsEmpty(Cells(i, 1))
Debug.Print i
Sheets("Button").Activate
Dim First As String
First = Cells(i, 1).Value
Debug.Print First
Dim Second As String
Second = Cells(i, 2).Value
Debug.Print Second
'Filters my Data sheet and copies the data
Sheets("DATA").Activate
Sheets("DATA").Range("A1").AutoFilter _
Field:=2, _
Criteria1:=First 'Filters for relevant organisation
Sheets("DATA").Range("A1").AutoFilter _
Field:=6, _
Criteria1:="=" 'Filters for No Response
Sheets("DATA").Range("A1:H6040").Copy
'This should loop through for each separate group
Sheets(CStr(Second)).Select
Range("A1").Select
ActiveSheet.Paste
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
i = i + 1
Loop
Worksheets("DATA").AutoFilterMode = False
End Sub
I have changed the program significantly trying to add notation such as 'CStr' as there was an error at this line:
Sheets(CStr(Second)).Select when it used to say Sheets(Second)).Select
and the debug.print's to see if it is actually working but it isn't logging to the Immediate Window.
Additionally, when I actually run it, no error comes up but nothing seems to happen.
Not sure what else to add, or what else to try. Cheers!
As a first remark, using (at least the first) sheet activation within the loop seems unnecessary, because the start of the loop is what determines which sheet is being used to control the flow of the loop.
Furthermore, I would argue that it is better to remove the sheet activation altogether, re: the discussion about .Select (the cases aren't the same, but the solution discussed herein works better for both .Select and .Activate in almost all instances): How to avoid using Select in Excel VBA macros.
Let's also see if we can refer to the table in the "DATA" sheet in a more direct manner, as well as do some errorchecking.
My suggestion:
Sub Program()
Dim i As Integer
Dim First, Second As String
Dim secondWs As Worksheet
Dim dataTbl As ListObject
i = 2
Set dataTbl = Worksheets("DATA").Range("A1").ListObject.Name
' The above can be done more elegantly if you supply the name of the table
Sheets("DATA").Activate
Do Until IsEmpty(Cells(i, 1))
Debug.Print i
First = Sheets("Button").Cells(i, 1).Value
Debug.Print First
Second = Sheets("Button").Cells(i, 2).Value
Debug.Print Second
'Filters my Data sheet and copies the data
dataTbl.AutoFilter _
Field:=2, _
Criteria1:=First 'Filters for relevant organisation
dataTbl.AutoFilter _
Field:=6, _
Criteria1:="=" 'Filters for No Response
Sheets("DATA").Range("A1:H6040").Copy
'This should loop through for each separate group
On Error Resume Next
Set secondWs = Worksheets(Second)
On Error GoTo 0
If Not secondWs Is Nothing Then
secondWs.Range("A1").PasteSpecial Paste:=xlPasteValues
Else
Debug.Print "Sheet name SECOND was not found"
End If
i = i + 1
Loop
Worksheets("DATA").AutoFilterMode = False
End Sub
If you get any errors, please state which line it appears on and what the error message actually is.
Ref:
http://www.mrexcel.com/forum/excel-questions/3228-visual-basic-applications-check-if-worksheet-exists.html#post13739

How do I prevent Excel from merging a row's data into a single cell when pasting?

I have two spreadsheets that I'm copying rows between. I have a macro that is in a separate workbook that stays open while I open and close the source and target workbooks which contain the spreadsheets.
I'll select the target row with
rows(activecell.row).copy
I'll then close the source workbook because its name is identical to the target workbook, open the target workbook in a different folder and execute
activesheet.pastespecial ' this line copied from record macro generated code
Sometimes it'll work fine, other times the source row will get scrunched into the first cell of the row and sometimes I'll get an error message saying the object doesn't support pastespecial.
I can't tell if I'm messing up the copy by not specifying I mean copy the whole row or if I need to somehow to specify that the source data is from another workbook and the paste function should treat the data as a row's worth of data as opposed to a single cell's worth.
As noted, I copied the pastespecial line from a macro Excel generated when I used the "Record Macro" feature and performed the row copy manually.
In case it matters, here's the entire routine..
Sub copyStudentsToEmail(emailApacket)
' copies student records to email
Dim cel, nameCel, sourcePath As String
Rows(ActiveCell.Row).Copy
sourcePath = ActiveWorkbook.FullName
ActiveWorkbook.Close (True)
Workbooks.Open ThisWorkbook.path & "\\email\\records.xls"
' search for bottom row
Range("a1").Select
While ActiveCell.Value <> ""
ActiveCell.Offset(1, 0).Select
Wend
ActiveSheet.PasteSpecial
' search for email cell
Cells(ActiveCell.Row, 1).Select
For Each cel In Range(Cells(ActiveCell.Row, 1), Cells(ActiveCell.Row, 10))
If cel Like "*#*" Then Exit For
Next
cel.Select
' if found email cell make adjustments
If (ActiveCell.Column < 10) Then
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
ActiveCell.Offset(0, 3).Select
Set nameCel = ActiveCell.Offset(0, 2)
ActiveCell.Value = Left(nameCel, InStr(nameCel, " ") - 1)
ActiveCell.Offset(0, 1).Select
cel = Right(nameCel, Len(nameCel) - Len(ActiveCell.Offset(0, -1)) - 1)
ActiveCell.Value = Left(cel, InStr(cel, " ") - 1)
Else
MsgBox ("Didn't find email address. Not sure how to adjust record.")
End If
If (emailApacket) Then sendApacket
ActiveWorkbook.Close True
Workbooks.Open sourcePath
End Sub
Many things in your macro can be improved, but briefly, if the problem is that sometimes the row gets pasted into one cell, try changing the part of copying/pasting into this:
ActiveCell.EntireRow.Copy
ActiveWorkbook.Close
Workbooks.Open ThisWorkbook.Path & "\\email\\records.xls"
Range("A32767").End(xlUp).Offset(1, 0).EntireRow.PasteSpecial
ps: I use A32767 because your target file seems to be from an Excel version prior to 2007, so I guess that the number of records should not reach that number.

.Paste works in 'If' portion of statement, but not 'Else'

I'm automating a process to import data from a worksheet, though for data integrity (and to eventually append to a database), I do not want the identifier entered twice. My code works to import the data if the identifier (SHC_No) is not in column A, and to prompt if you would like to replace the entries because something has changed. It will delete the entries, and find the next blank row, but the .paste function will not operate. (Even though it is on the cell I want to paste into and I can see the data in the clipboard.)
I have gotten Run Time Error 1004 "Paste method of worksheet class failed," and "PasteSpecial Method of Range class failed," as well as 438 "Object doesn't support this property or method."
I'm relatively new to Excel VBA. I have tried different variations of .paste and .pastespecial and nothing seems to work. I have tried With statements, and defining the range. I'm at a loss.
Any ideas or suggestions, would be greatly appreciated.
Sub ImportAPDR()
Dim wbImport As Workbook
Dim wbCurrent As Workbook
Dim strSHC As String
Set wbImport = Workbooks("ImportPhase2.xlsm") 'Ensure name of the workbook... and don't change it.
Set wbCurrent = ActiveWorkbook
'On Error GoTo Handler:
Application.ScreenUpdating = False 'Prevents flickering screen.
'Activate Page 3 of the APDR
Worksheets("Page 3").Activate
strSHC = Range("A11").Value
Range("A11").Activate
Do
If ActiveCell.Value = "" Then Exit Do
ActiveCell.Offset(1, 0).Activate
Loop
'Selects All data from A11 until the EOR
ActiveCell.Offset(-1, 12).Activate
Range("A11", ActiveCell).Select
Selection.Copy
'Find the first blank cell in the Import workbook.
wbImport.Activate
Worksheets("Import").Activate
FindSHC (strSHC) 'Must send a variable or the other subroutine will not work.
Application.ScreenUpdating = True
Exit Sub
'Handler:
'MsgBox ("Ensure to run the macro on the APDR workbook, not the import workbook.")
End Sub
Sub FindSHC(strSHC As String)
Dim foundSHC As String
Dim Rng As Range
Dim StartRange As Range
Dim PasteRng As Range
FindString = strSHC
If Trim(strSHC) <> "" Then
With Sheets("Import").Range("A:A")
Set Rng = .Find(What:=FindString, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Rng Is Nothing Then
Range("A1").Activate
Do
If ActiveCell.Value = "" Then Exit Do
ActiveCell.Offset(1, 0).Activate
Loop
ActiveSheet.Paste
Application.CutCopyMode = False
MsgBox ("The Tenant/Unit Details have been copied for import.")
Else
Application.Goto Rng, True
Set StartRange = ActiveCell
Answer = MsgBox("This APDR looks like it has already been imported." & vbNewLine & "Do you want to reimport and replace?", vbYesNo)
If Answer = vbYes Then
'Finds the values that have previously been entered and loops to select them all for deletion.
Do
If ActiveCell.Value <> strSHC Then Exit Do
ActiveCell.Offset(1, 0).Activate
Loop
ActiveCell.Offset(-1, 0).Activate
Range(StartRange, ActiveCell).Select
Selection.EntireRow.Delete Shift:=xlUp
'Find the next open row
Range("A1").Activate
Do
If ActiveCell.Value = "" Then Exit Do
ActiveCell.Offset(1, 0).Activate
Loop
ActiveSheet.Paste '<-----This is where I get my error.
Application.CutCopyMode = False
MsgBox ("The Tenant/Unit Details have been replaced.")
Else
Application.CutCopyMode = False
MsgBox ("Import has been cancelled.")
End If
End If
End With
End If
End Sub
Here are some tips to avoid getting stuck in such moments.
Always try to emulate what macro does by manually performing all the actions, wherever it is possible to. In your case you can choose 2 identifiers for QA - one which is not present and another which is present. You won't have any trouble with the first one, but with the second - as soon as you delete any cell content after you have copied some range, your selection and copied range would be lost from clipboard. And that's actually what makes a trouble.
Do proper debugging. As soon as you go into strange exception - debug what brought your code to this point. And again - in your case you can see that as soon as you delete contents the "dashed border" around copied range will disappear, meaning you have no copied range anymore which you could paste.
This one goes directly to copy-paste combination. Whenever you are in need to use Copy & Paste build up your code so that these two go one after another. Avoid any range interaction between these two functions. If you need to do some calculations just save the range parameters(like range rectangle dimensions, starting row+column, ending row+column) to other variables and Copy only when you've done with everything using those saved parameters.
Good luck with this one.

How to transfer highlighted cells in Excel 2007 from one table to another in the same sheet?

I would like to create a code that transfer the content of the highlighted cells from one table to another in the same sheet with the content, I use button to copy the content, but I would like to create a macro the transfer the content dynamically by clicking on a button, when the user change the content of the highlighted cells of the first table the content changes automatically in the second table or by clicking on the button again.
I use this code to highlight the cells
' Set of highlighted cells indexed by row number
Dim highlightedCells As New Collection
' Scan existing sheet for any cells coloured 'red' and initialise the
' run-time collection of 'highlighted' cells.
Private Sub Worksheet_Activate()
ActiveSheet.Unprotect Password:="P#ssw0rd"
Dim existingHighlights As Range
' Reset the collection of highlighted cells ready to rebuild it
Set highlightedCells = New Collection
' Find the first cell that has its background coloured red
Application.FindFormat.Interior.ColorIndex = 3
Set existingHighlights = ActiveSheet.Cells.Find("", _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=True)
' Process for as long as we have more matches
Do While Not existingHighlights Is Nothing
cRow = existingHighlights.Row
' Add a reference only to the first coloured cell if multiple
' exist in a single row (will only occur if background manually set)
Err.Clear
On Error Resume Next
Call highlightedCells.Add(existingHighlights.Address, CStr(cRow))
On Error GoTo 0
' Search from the cell after the last match. Note an error in Excel
' appears to prevent the FindNext method from finding formats correctly
Application.FindFormat.Interior.ColorIndex = 3
Set existingHighlights = ActiveSheet.Cells.Find("", _
After:=existingHighlights, _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=True)
' Abort the search if we've looped back to the top of the sheet
If (existingHighlights.Row < cRow) Then
Exit Do
End If
Loop
ActiveSheet.Protect Password:="P#ssw0rd"
End Sub
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
ActiveSheet.Unprotect Password:="P#ssw0rd"
Dim hCell As String
Dim cellAlreadyHighlighted As Boolean
hCell = ""
Err.Clear
On Error Resume Next
hCell = highlightedCells.Item(CStr(Target.Row))
On Error GoTo 0
If (hCell <> "") Then
ActiveSheet.Range(hCell).Interior.ColorIndex = 2
If (hCell = Target.Address) Then
Call highlightedCells.Remove(CStr(Target.Row))
Target.Interior.ColorIndex = 2
Else
Call highlightedCells.Remove(CStr(Target.Row))
Call highlightedCells.Add(Target.Address, CStr(Target.Row))
Target.Interior.ColorIndex = 3
End If
Else
Err.Clear
On Error Resume Next
highlightedCells.Remove (CStr(Target.Row))
On Error GoTo 0
Call highlightedCells.Add(Target.Address, CStr(Target.Row))
Target.Interior.ColorIndex = 3
End If
Cancel = True
ActiveSheet.Protect Password:="P#ssw0rd"
End Sub
And I use this code to copy the highlighted cells:
Sub CopyCat()
ActiveSheet.Unprotect Password:="P#ssw0rd"
Dim LR As Long, i As Long, j As Long
Dim c As Range
j = 1
LR = Range("A" & Rows.Count).End(xlUp).Row
For Each c In Worksheets("MB").Range("A1:O" & LR)
If c.Interior.ColorIndex = 3 Then
c.Copy Destination:=Worksheets("MB").Range("J" & j)
j = j + 1
End If
Next c
ActiveSheet.Protect Password:="P#ssw0rd"
End Sub
Please Help !!!!
Instead of copying the whole table and using the values to populate the table on the second page, why not (for those items you want to update, as the sheet1 gets updates) just leave a "link" back to the original table. You could either set it literally to the cell it refers to, or more robustly, use something like Index/Match. See below:
This is an example of Sheet1 (the data you want copied onto a second sheet). I have highlighted the "Salary" column, to reflect that the user is asked to change these.
And in your sheet 2, you can use various ways of "linking" back to the first sheet:
That way, when you go in an edit the salary for Chris or John, it'll update their salary in the second sheet, without needing to run any macros. Is this what you're looking to do, or am I overlooking/misunderstanding something?