Adding items from a listbox into the clipboard in a excel paste friendly format - vba

The Aim: using VBA (excel) to populate the clipboard with a table (from a listbox) in an excel friendly format so that I can then paste (ctrl+V) into excel and the values are split into the correct columns and rows as they were in the original listbox.
The Problem: I can't seem to get the formatting right; when I paste the contents of my clipboard into excel each row is contained in one cell (the first column) separated with "," (which is what I have tried to use as a sort of "delimiter" - not sure if this is correct or not?)
I am using the API workaround method to populate the clipboard and I don't want to wall you to death with code but here is the most important bit (I feel anyway - ask me if you need to see more code).
Private Sub btnCopyTable_Click()
Dim I As Long
Dim J As Long
Dim tmp As String
Dim arrItems() As String
Dim clipboard As DataObject
ReDim arrItems(0 To lbIPActions.ColumnCount - 1)
For J = 0 To lbIPActions.ListCount - 1
For I = 0 To lbIPActions.ColumnCount - 1
On Error Resume Next ' Handles null values
arrItems(I) = lbIPActions.Column(I, J)
On Error GoTo 0
Next I
tmp = tmp & Join(arrItems, ",") & vbCrLf
Next J
MsgBox tmp
ClipBoard_SetData tmp
End sub
The Current Output:
1,1,Low,Controls,,LS,Do this,02-Oct-2015,Note 1,Brev
2,1,Low,Controls,,LS,Do that,02-Oct-2015,Note 2,Brev
The Current State: I have had a little look around and found a way to do it in code to a specific sheet, but my end user requires the freedom to copy it into numerous sheets in a very ad hoc fashion. I know there is always the option to right click -> import text wizard, but again ctrl+V is the preferred method if possible.
As always, any help/advice is appreciated, thanks in advance.

Separate your string with Tab in each line and it'll work. Like so:
Dim I As Long
Dim J As Long
Dim textItem As String
Dim copyText As String
Dim clipboard As DataObject
For J = 0 To lbIPActions.ListCount - 1
For I = 0 To lbIPActions.ColumnCount - 1
textItem = vbNullString
On Error Resume Next ' Handles null values
textItem = lbIPActions.Column(I, J)
On Error GoTo 0
If I > 0 Then copyText = copyText & vbTab
copyText = copyText & textItem
Next I
If J < lbIPActions.ListCount - 1 Then copyText = copyText & vbNewLine
Next J
ClipBoard_SetData clipboardText

Related

Object or With Variable Not Set

Option Explicit
Public Sub consolidateList()
DeleteTableRows (ThisWorkbook.Worksheets("Master").ListObjects("MasterSheet"))
FillTableRows
End Sub
Private Sub FillTableRows()
'set up worksheet objects
Dim wkSheet As Worksheet
Dim wkBook As Workbook
Dim wkBookPath As String
Set wkBook = ThisWorkbook
wkBookPath = wkBook.Path
Set wkSheet = wkBook.Worksheets("Master")
'set up file system objects
Dim oFile As Object
Dim oFSO As Object
Dim oFolder As Object
Dim oFiles As Object
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oFolder = oFSO.GetFolder(wkBookPath)
Set oFiles = oFolder.Files
'set up loop
Dim checkBook As Excel.Workbook
Dim reportDict As Dictionary
Application.ScreenUpdating = False
'initial coordinates
Dim startRow As Long
Dim startColumn As Long
startColumn = 3
Dim i As Long 'tracks within the row of the sheet where information is being pulled from
Dim k As Long 'tracks the row where data is output on
Dim j As Long 'tracks within the row of the sheet where the data is output on
Dim Key As Variant
j = 1
k = wkSheet.Range("a65536").End(xlUp).Row + 1
Dim l As Long
'look t Set checkBook = Workbooks.Open(oFile.Path)hrough folder and then save it to temp memory
On Error GoTo debuger
For Each oFile In oFiles
startRow = 8
'is it not the master sheet? check for duplicate entries
'oFile.name is the name of the file being scanned
'is it an excel file?
If Mid(oFile.Name, Len(oFile.Name) - 3, 4) = ".xls" Or Mid(oFile.Name, Len(oFile.Name) - 3, 4) = ".xlsx" Then
Set checkBook = Workbooks.Open(oFile.Path)
For l = startRow To 600
If Not (IsEmpty(Cells(startRow, startColumn))) Then
'if it is, time do some calculations
Set reportDict = New Dictionary
'add items of the payment
For i = 0 To 33
If Not IsEmpty(Cells(startRow, startColumn + i)) Then
reportDict.Add Cells(4, startColumn + i), Cells(startRow, startColumn + i)
End If
Next i
For i = startRow To 0 Step -1
If Not IsEmpty(Cells(i, startColumn - 1)) Then
reportDict.Add "Consumer Name", Cells(i, startColumn - 1)
Exit For
End If
Next i
'key is added
For Each Key In reportDict
'wkSheet.Cells(k, j) = reportDict.Item(Key)
Dim myInsert As Variant
Set myInsert = reportDict.Item(Key)
MsgBox (myInsert)
wkSheet.ListObjects(1).DataBodyRange(2, 1) = reportDict.Item(Key)
j = j + 1
Next Key
wkSheet.Cells(k, j) = wkSheet.Cells(k, 9) / 4
wkSheet.Cells(k, j + 1) = oFile.Name
'
k = k + 1
' Set reportDict = Nothing
j = 1
Else
l = l + 1
End If
startRow = startRow + 1
Next l
checkBook.Close
End If
' Exit For
Next oFile
Exit Sub
debuger:
MsgBox ("Error on: " & Err.Source & " in file " & oFile.Name & ", error is " & Err.Description)
End Sub
Sub DeleteTableRows(ByRef Table As ListObject)
On Error Resume Next
'~~> Clear Header Row `IF` it exists
Table.DataBodyRange.ClearContents
'~~> Delete all the other rows `IF `they exist
Table.DataBodyRange.Offset(1, 0).Resize(Table.DataBodyRange.Rows.count - 1, _
Table.DataBodyRange.Columns.count).Rows.Delete
On Error GoTo 0
End Sub
Greetings. The above code consolidates a folder of data that's held on excel spreadsheets into one master excel spreadsheet. The goal is to run a macro on Excel Spreadsheet named master on the worksheet named master which opens up other excel workbooks in the folder, takes the information, and puts it into a table in the worksheet "master". After which point, it becomes easy to see the information; so instead of it being held on hundreds of worksheets, the records are held on one worksheet.
The code uses a dictionary (reportDict) to temporarily store the information that is needed from the individual workbooks. The goal then is to take that information and place it in the master table at the bottom row, and then obviously add a new row either after a successful placement or before an attempted placement of data.
The code fails at the following line:
wkSheet.ListObjects(1).DataBodyRange(2, 1) = reportDict.Item(Key)
The failure description is "object or with variable not set" and so the issue is with the reportDict.Item(Key). My guess is that somehow VBA is not recognizing the dictionary item as stable, but I don't know how to correct this. Eventually the goal is to have code which does:
for each key in reportDict
- place the item which is mapped to the key at a unique row,column in the master table
- expand the table to accomodate necessary data
next key
Implicit default member calls are plaguing your code all over.
reportDict.Add Cells(4, startColumn + i), Cells(startRow, startColumn + i)
That's implicitly accessing Range.[_Default] off whatever worksheet is currently the ActiveSheet (did you mean that to be wkSheet.Cells?), to get the Key - since the Key parameter is a String, Range.[_Default] is implicitly coerced into one, and you have a string key. The actual dictionary item at that key though, isn't as lucky.
Here's a MCVE:
Public Sub Test()
Dim d As Dictionary
Set d = New Dictionary
d.Add "A1", Cells(1, 1)
Debug.Print IsObject(d("A1"))
End Sub
This procedure prints True to the debug pane (Ctrl+G): what you're storing in your dictionary isn't a bunch of string values, but a bunch of Range object references.
So when you do this:
Dim myInsert As Variant
Set myInsert = reportDict.Item(Key)
You might as well have declared myInsert As Range, for it is one.
This is where things get interesting:
MsgBox (myInsert)
Nevermind the superfluous parentheses that force-evaluate the object's default member and pass it ByVal to the MsgBox function - here you're implicitly converting Range.[_Default] into a String. That probably works.
So why is this failing then?
wkSheet.ListObjects(1).DataBodyRange(2, 1) = reportDict.Item(Key)
Normally, it wouldn't. VBA would happily do this:
wkSheet.ListObjects(1).DataBodyRange.Cells(2, 1).[_Default] = reportDict.Item(Key).[_Default]
And write the value in the DataBodyRange of the ListObject at the specified location.
I think that's all just red herring. Write explicit code: if you mean to store the Value of a cell, store the Value of a cell. If you mean to assign the Value of a cell, assign the Value of a cell.
I can't replicate error 91 with this setup.
This, however:
DeleteTableRows (ThisWorkbook.Worksheets("Master").ListObjects("MasterSheet"))
...is also force-evaluating a ListObject's default member - so DeleteTableRows isn't receiving a ListObject, it's getting a String that contains the name of the object you've just dereferenced... but DeleteTableRows takes a ListObject parameter, so there's no way that code can even get to run FillTableRows - it has to blow up with a type mismatch before DeleteTableRows even gets to enter. In fact, it's a compile-time error.
So this is a rather long answer that doesn't get to the reason for error 91 on that specific line (I can't reproduce it), but highlights a metric ton of serious problems with your code that very likely are related to this error you're getting. Hope it helps.
You need to iterate through the dictionary's Keys collection.
dim k as variant, myInsert As Variant
for each k in reportDict.keys
debug.print reportDict.Item(k)
next k

Excel VBA not splitting named ranges/rows when using page break in macro

Currently I'm trying to print a sheet in pdf in excel and then let a code set the pagebreaks automatically. The code should not break up named ranges in the sheet. The named ranges have multiple rows attached to them, so those rows should stay together on one page under that named range. On this sheet there are also hidden named ranges with rows. The code should not take those in account.
I have tried the code below, but I keep getting an error. See image below the code.
Sub CommandButton1_Click()
'Change pages breaks so it is only split on a named range top or bottom
Dim n As Name
Dim RngRows() As Integer
Dim PageBreakRows() As Integer
Dim NumPageBreaks As Integer
Dim i As Integer
Dim TtlRows As Integer
Dim MaxRowsPerPage As Integer
Dim strBreakRow As String
Dim intBreakRow As Integer
i = 1
TtlRows = 0
NumPageBreaks = 0
MaxRowsPerPage = 17
'clear any settings that exist
ActiveSheet.ResetAllPageBreaks
For Each n In ActiveWorkbook.Names
'operate only on ranges that start with RANGE in their name
If Mid(n.Name, 1, 1) = "P" Then
'if named range is visible
If Range(n).EntireRow.Hidden = False Then
Debug.Print "Processing Range " & n.Name & " with #"
Rows = " & Range(n).Rows.Count"
'add # rows in range to row count.
TtlRows = TtlRows + Range(n).Rows.Count
'if rount count larger than max allowable add a page break
If TtlRows > MaxRowsPerPage Then
'take first row of current named range and add page break
ActiveSheet.HPageBreaks.Add before:=Range(Range(n).Rows(1).Address)
'update Totl row count
TtlRows = Range(n).Rows.Count
End If
Else
Debug.Print "Range hidden: " & n.Name & " with # rows=" & Range(n).Rows.Count
End If 'end visible check
End If 'end name check
Next
End Sub
All the references are correct.
So I have no idea what is wrong here. Does anybody know the solutions? Or know a better code from experience?
New image after deleting line: "Rows = " & Range(n).Rows.Count" "

Looping through Excel Cells and writing them to Word

I am using a Macro in Excel to loop through cells and write the data into a Template in Word. Everything worked completely fine until I wanted to add more cells to grab data from. Everything still works fine except once the variable I have name "j" gets to the value of 25, I get an error saying "Run-time error '5941': The requested member of the collection does not exist."
I've played around with using different rows and columns and every combination works. It is only when the "j" reaches 25 does the error occur. It is failing when it reaches the wrd.ActiveDocument.Tables(1).Rows(j)... line.
Sub Label_ExcelToWord_Single()
'Variable that stores file path for
'word template
Dim strpath As String
strpath = Cells(28, 8)
'opens Microsoft Word to edit template
Call OpenWord
Set wrd = GetObject(, "Word.Application")
wrd.Visible = True
wrd.Activate
wrd.ActiveDocument.Close savechanges:=False
wrd.Documents.Open strpath
'Variables used for loop data manipulation
Dim k As Integer
Dim j As Integer
k = 1
j = 1
'Primary loop responsible for exporting Excel
'data to word template
For Col = 1 To 3
For Row = 3 To 32
wrd.ActiveDocument.Tables(1).Rows(j).Cells(((Row - 3) Mod 7) + k).Range.Text = Cells(Row, Col) & vbCrLf & Cells(Row, Col)
If k = 7 Then
k = 0
j = j + 2
End If
If Col = 3 Then
If Row = 32 Then
'When we reach the last cell containing data exit routine
Exit Sub
End If
End If
k = k + 1
Next
Next
End Sub
I think it's a lot easier to use DocVariables in Word and push your data from Excel cells into the DocVariables. The end game is about the same, but I think the code is a lot easier to setup and maintain.
Sub PushToWord()
Dim objWord As New Word.Application
Dim doc As Word.Document
Dim bkmk As Word.Bookmark
sWdFileName = Application.GetOpenFilename(, , , , False)
Set doc = objWord.Documents.Open(sWdFileName)
'On Error Resume Next
objWord.ActiveDocument.variables("FirstName").Value = Range("FirstName").Value
objWord.ActiveDocument.variables("LastName").Value = Range("LastName").Value
objWord.ActiveDocument.variables("AnotherVariable").Value = Range("AnotherVariable").Value
objWord.ActiveDocument.Fields.Update
'On Error Resume Next
objWord.Visible = True
End Sub
Set a reference ot the MS Word Object Library.
Looks like the table might not have enough rows. If so, a brute-force way is to add rows as you need them:
...
For Col = 1 To 3
For Row = 3 To 32
' vvv new lines vvv
Do While wrd.ActiveDocument.Tables(1).Rows.Count < j
wrd.ActiveDocument.Tables(1).Rows.Add
Loop
' ^^^ new lines ^^^
...
I also recommend renaming j, k, Row, and Col to more descriptive names. You have Excel row/column indices and Word-table row/column indices, and it's easy to get them confused unless the names are clear.
(Note: Yes, the above code is slow, clunky, unoptimized, yadda yadda. Hopefully it will help the OP. :) )

Improve / optimize Excel macro to search for text phrases within a folder of text reports

Using Microsoft Excel 2010, this macro searches for a list of phrases within a folder of text reports. For each phrase, it searches all of the reports and lists each report that contains the phrase.
I found some better macros to do each part of the macro - such as enumerating a directory, or finding a phrase within a text file - although I had a really hard time putting them together successfully. Despite it not being perfect, it may be helpful for others with the same problem, and I hope for some feedback on how to improve and optimize the macro.
Basic overview:
Column A: list of full path to text reports (for instance, "C:\path\to\report.txt")
Column B: name of report (such as "report.txt")
Column C: list of phrases to search for
Columns D+: output showing each report that contains the phrase (column C)
Areas for improvement:
Make the macro run faster! (This took over an hour for 360 reports and 1100 phrases)
Select the reports and report folder from a pop-up or other function (currently entered into the spreadsheet using another macro)
Filter reports by file name (for instance, only check reports with a word or phrase in the file name)
Filter reports by file extension (for instance, only check .txt files and not .xlsx files)
Detect the number of reports and phrases (currently this is hard coded)
Other suggestions / areas for improvement
Code:
Sub findStringMacro()
Dim fn As String
Dim lineString As String
Dim fileName As String
Dim searchTerm As String
Dim findCount As Integer
Dim i As Integer
Dim j As Integer
For i = 2 To 1109
searchTerm = Range("C" & i).Value
findCount = 0
For j = 2 To 367
fn = Range("A" & j).Value
fileName = Range("B" & j).Value
With CreateObject("Scripting.FileSystemObject").OpenTextFile(fn)
Do While Not .AtEndOfStream
lineString = .ReadLine
If InStr(1, lineString, searchTerm, vbTextCompare) Then
findCount = findCount + 1
Cells(i, 3 + findCount) = fileName
GoTo EarlyExit
End If
Loop
EarlyExit:
.Close
End With
Next j
Next i
End Sub
As #Makah pointed out, you're opening a lot of files, which is slow. To fix this, change the order of the loops (see the code below). This will switch from 407,003 file opens to 367. Along the same lines, lets create the FileSystemObject once, instead of once per file open.
Also, VBA is surprisingly slow at reading/writing data from/to Excel. We can deal with this by loading largw blocks of data into VBA all at once with code like
dim data as Variant
data = Range("A1:Z16000").value
And then writing it back to Excel in a large block like
Range("A1:Z16000").value = data
I have also added in code to dynamically check the dimension of your data. We assume that the data starts in cell A2, and if A3 is empty, we use the single cell A2. Otherwise, we use .End(xlDown) to move down to just above the first empty cell in column A. This is the equivalent of pressing ctrl+shift+down.
Note: the following code has not been tested. Also, it requires a reference to "Microsoft Scripting Runtime" for the FileSystemObjects.
Sub findStringMacro()
Dim fn As String
Dim lineString As String
Dim fileName As String
Dim searchTerm As String
Dim i As Integer, j As Integer
Dim FSO As Scripting.FileSystemObject
Dim txtStr As Scripting.TextStream
Dim file_rng As Range, file_cell As Range
Dim output As Variant
Dim output_index() As Integer
Set FSO = New Scripting.FileSystemObject
Set file_rng = Range("A2")
If IsEmpty(file_rng) Then Exit Sub
If Not IsEmpty(file_rng.Offset(1, 0)) Then
Set file_rng = Range(file_rng, file_rng.End(xlDown))
End If
If IsEmpty(Range("C2")) Then Exit Sub
If IsEmpty(Range("C3")) Then
output = Range("C2")
Else
output = Range(Range("C2"), Range("C2").End(xlDown))
End If
ReDim Preserve output(1 To UBound(output, 1), 1 To file_rng.Rows.Count + 1)
ReDim output_index(1 To UBound(output, 1))
For i = 1 To UBound(output, 1)
output_index(i) = 2
Next i
For Each file_cell In file_rng
fn = file_cell.Value 'Range("A" & j)
fileName = file_cell.Offset(0, 1).Value 'Range("B" & j)
Set txtStr = FSO.OpenTextFile(fn)
Do While Not txtStr.AtEndOfStream
lineString = txtStr.ReadLine
For i = 1 To UBound(output, 1)
searchTerm = output(i, 1) 'Range("C" & i)
If InStr(1, lineString, searchTerm, vbTextCompare) Then
If output(i, output_index(i)) <> fileName Then
output_index(i) = output_index(i) + 1
output(i, output_index(i)) = fileName
End If
End If
Next i
Loop
txtStr.Close
Next file_cell
Range("C2").Resize(UBound(output, 1), UBound(output, 2)).Value = output
Set txtStr = Nothing
Set FSO = Nothing
Set file_cell = Nothing
Set file_rng = Nothing
End Sub

pasting from text document into excel comments

Just like you can copy an arbitrary number of lines from a text document and paste into Excel in successive rows, I want to be able to copy the lines of text and paste them into the comments of successive rows in Excel. To make it a bit easier, I paste the rows of comment text from the .txt file into a column in Excel first. This is what I'm looking at right now:
Dim myClip As New DataObject
Dim myString As String
myClip.GetFromClipboard
myString = myClip.GetText
Sheet1.Range("A1").AddComment myString
but pasting from the clipboard like this doesn't seem to have to the desired effect. Any ideas?
Sub AddCommentsToSelection()
Dim myClip As New DataObject
Dim myString As String
Dim c As Range, arr, x As Integer
myClip.GetFromClipboard
myString = myClip.GetText
If Len(myString) = 0 Then Exit Sub
Set c = Selection.Cells(1)
arr = Split(myString, vbCrLf)
For x = LBound(arr) To UBound(arr)
c.AddComment arr(x)
Set c = c.Offset(1, 0)
Next x
End Sub