VBA Autorange function for Array to Range in excel - vba

As I am working with large csv files, I decided to load them into VBA memory instead of loading in my spreadsheet to make it quicker and lighter.
So I have a function CSVtoArray that read through my CSV and gives me an array.
Then if I still want to see my data in excel I just write {=(CSVtoArray(my_csv_path)} in my s/s.
But since the size of my csv changes over time, I wanted to write a function called AutoRange that would automatically fit the display area in my spreadsheet according to the size of my range.
So this is what I wrote but it's not working, it does nothing, only the cell in which I am writing the formula is filled.
Function AutoRange(my_array As Variant)
Dim nb_rows, nb_cols As Integer
Dim current_cell, target_range As Range
nb_rows = UBound(my_array, 1)
nb_cols = UBound(my_array, 2)
Set current_cell = Selection
current_cell.Resize(nb_rows, nb_cols).FormulaArray = current_cell.Formula
AutoRange = Selection
End Function
Thanks in advance guys.

Functions are for returning things. And if used in the cell are for returning things to that cell, not to manipulate other cells. Do you want actually want a sub like?
Code:
Option Explicit
Public Sub TEST()
Dim my_Array()
my_Array = [A1].CurrentRegion.Value
AutoRange my_Array
End Sub
Public Sub AutoRange(ByVal my_Array As Variant)
Dim nb_rows As Long, nb_cols As Long
Dim current_cell As Range
nb_rows = UBound(my_Array, 1)
nb_cols = UBound(my_Array, 2)
Set current_cell = Selection
current_cell.Resize(nb_rows, nb_cols).FormulaArray = current_cell.Formula
End Sub
Result:
From your comments: If you want to use as a function (Not a UDF, which cannot alter other cells) then you can use the following way, though I advise against it as it bad practice:
Option Explicit
Public Sub TEST()
Dim my_Array()
my_Array = [A1].CurrentRegion.Value
Dim target_Range As Range
Set target_Range = AutoRange(my_Array)
End Sub
Public Function AutoRange(ByVal my_Array As Variant) As Range
Dim nb_rows, nb_cols As Long
Dim current_cell, target_Range As Range
nb_rows = UBound(my_Array, 1)
nb_cols = UBound(my_Array, 2)
Set current_cell = Selection
Set target_Range = current_cell.Resize(nb_rows, nb_cols)
Set AutoRange = target_Range
target_Range.FormulaArray = current_cell.Formula
End Function

Ok so I did it another way,
I have my AutoRange sub :
Sub AutoRange(my_Array As Variant, top_left_corner As Range)
' Here we take an array in input, the one we want to display, and the top left corner of the range where we want to put it
Dim nb_rows, nb_cols As Integer
nb_rows = UBound(my_Array, 1)
nb_cols = UBound(my_Array, 2)
Set current_cell = top_left_corner
top_left_corner.Resize(nb_rows, nb_cols).FormulaArray = top_left_corner.Formula
End Sub
and then I added a Worksheet_change sub to my s/s:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$1" Then
if Target.value="load data" then
Call Autorange(my_array, my_range)
else
Range(my_range, my_range.End(xlDown).End(xlToRight)).clearcontents
End If
End Sub
so I just need to say if I want to load my data or not and it will adjust.
I assume that at my former company they were using an addin rather than VBA itself.
thanks anyways guys.
cheers

Related

Is there a faster method of deleting shapes in Excel

I've successfully added shapes into cells (msoShapeOval) in a pivot table. I need to clear and recreate these shapes if the pivot / slicer selection changes. My current method works, but it is slow. Is there any better method to clear shapes in bulk? Note: I do know the exact cell range where all these shapes exist. I've also appied :
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Current code:
Dim Shp as Shape
For Each Shp In rng.Parent.Shapes
If InStrB(Shp.Name, "$") > 0 Then Shp.Delete
Next
It is possible to delete the shapes at once without selecting, with some fine tuning. Let's imagine you want to delete the rectangulars from this:
What you need to do is the following:
loop through all the objects
make an array with all the rectangular's names
delete the objects in the array
Tricky part is the looping through the objects, because you need to increment your array every time, which is not a built-in functionality (like in collection). incrementArray is the function for this.
Furthermore, the first time you increment to the unassigned array, you need to check whether it is allocated (achieved with the IsArrayAllocated function below).
Option Explicit
Sub TestMe()
Dim shp As Shape
Dim arrOfShapes() As Variant 'the () are important!
With ActiveSheet
For Each shp In .Shapes
If InStrB(shp.Name, "Rec") > 0 Then
arrOfShapes = incrementArray(arrOfShapes, shp.Name)
End If
Next
If IsArrayAllocated(arrOfShapes) Then
Debug.Print .Shapes.Range(arrOfShapes(0)).Name
.Shapes.Range(arrOfShapes).Delete
End If
End With
End Sub
The additional functions:
Public Function incrementArray(arrOfShapes As Variant, nameOfShape As String) As Variant
Dim cnt As Long
Dim arrNew As Variant
If IsArrayAllocated(arrOfShapes) Then
ReDim arrNew(UBound(arrOfShapes) + 1)
For cnt = LBound(arrOfShapes) To UBound(arrOfShapes)
arrNew(cnt) = CStr(arrOfShapes(cnt))
Next cnt
arrNew(UBound(arrOfShapes) + 1) = CStr(nameOfShape)
Else
arrNew = Array(nameOfShape)
End If
incrementArray = arrNew
End Function
Function IsArrayAllocated(Arr As Variant) As Boolean
On Error Resume Next
IsArrayAllocated = IsArray(Arr) And _
Not IsError(LBound(Arr, 1)) And _
LBound(Arr, 1) <= UBound(Arr, 1)
End Function
Credits to this guy for the finding that the arrOfShapes should be declared with parenthesis (I have spent about 30 minutes researching why I could not pass it correctly) and to CPearson for the IsArrayAllocated().
To delete all shapes except slicers:
Sub RemoveAllExceptSlicers()
Dim sh As Shape
For Each sh In ActiveSheet.Shapes
If Not sh.Type = MsoShapeType.msoSlicer Then
sh.Delete
End If
Next
End Sub

Vba Dictionary Result Don`t Return Correctly

I was trying to use dictionary to lookup value in column F
with key in column C.
But after the result dont return like I want. It show "0"
Scenario:
1. key in column C will have mutliple same value
2. I want to sum up all the value in column F based on key and return to "RAW" Range("C2")
"Sheet2"
"RAW"
Please help me.
Thanks in advance.
Here my code.
Option Explicit
Private Lrow As Long
Private oDict As Object
Private Sub CreateDict()
Dim arrValues As Variant, oKey As Variant, oValue As Variant, i As Long
'Find Master Item List Japan
Dim Master As Workbook
Dim t As Workbook
For Each t In Workbooks
If Left(t.Name, 16) = "Master Item List" Then
Set Master = Workbooks(t.Name)
End If
Next t
Set oDict = Nothing
If oDict Is Nothing Then
Set oDict = New Scripting.Dictionary
End If
' Add items to the dictionary
' Load values of used range to memory
arrValues = Master.Sheets("Sheet2").UsedRange.Value
' Assuming the Key is on first column and Value is on next
For i = 2 To UBound(arrValues)
oKey = arrValues(i, 3)
oValue = arrValues(i, 6)
If Len(oKey) > 0 Then
If oDict.Exists(oKey) Then
' Append Value to existing key
oDict(oKey) = oDict(oKey) + oValue
Else
' Add Key and value
oDict(oKey) = oValue
End If
End If
Next i
End Sub
Function GetList(ByVal oRange As Range) As Variant
If oDict Is Nothing Then CreateDict
' Static oDict As Scripting.Dictionary 'precerved between calls
If oDict.Exists(oRange.Value) Then
GetList = oDict.Item(oRange.Value)
' Else
' GetList = 0
End If
End Function
Just For Reference.
This is code I use in other workbook and working nicely
Option Explicit
Private lRow As Long
Private oDict As Object
Private Sub CreateDict()
Dim arrValues As Variant, oKey As Variant, oValue As Variant, i As Long
'Find Master Item List Japan
Dim Master As Workbook
Dim t As Workbook
For Each t In Workbooks
If Left(t.Name, 16) = "Master Item List" Then
Set Master = Workbooks(t.Name)
End If
Next t
Set oDict = Nothing
If oDict Is Nothing Then
Set oDict = New Scripting.Dictionary
End If
' Add items to the dictionary
' Load values of used range to memory
arrValues = Master.Sheets("Sheet2").UsedRange.Value
' Assuming the Key is on first column and Value is on next
For i = 1 To UBound(arrValues)
oKey = arrValues(i, 3)
oValue = arrValues(i, 6)
If Len(oKey) > 0 Then
If oDict.Exists(oKey) Then
' Append Value to existing key
oDict.Item(oKey) = oDict.Item(oKey)
Else
' Add Key and value
oDict.Add oKey, oValue
End If
End If
Next
End Sub
Function GetList(ByVal oRange As Range) As Long
If oDict Is Nothing Then CreateDict
' Static oDict As Scripting.Dictionary 'precerved between calls
If oDict.Exists(oRange.Value) Then
GetList = oDict.Item(oRange.Value)
Else
GetList = 0
End If
End Function
EDIT #1:
Based on #YowE3k comment I try execute the GetFile Function and got the result as picture below.
Not very sure why last one return with 0
Can this is because it have same key already in my dictionary history because in other workbook I use same key.

Vector of matches VBA

I am currently looking for the following issue. I would like to find every position of a particular matches and store them into a vector called pos. In my example I would like to know every row in the range(E1:E500) where "SG" appears. Then I would like to loop through this vector.
I have tried the following code but it seems not to work. Can anyone help me?
Sub test()
Set rangenew = Range("E1:E500")
pos = Application.Match("SG", rangenew, False)
End Sub
the results should be something like
pos = (1,6,8,10)
Then I would like to loop through this vector to test conditions.
Thanks for your great help.
As #GSerg explained in the comments, there is no built-in function for returning matched row numbers. Something like the below should do what you're after.
Public Sub getRows()
Dim wb As Workbook, ws As Worksheet
Dim checkData() As Variant, pos() As Long
Dim i As Long
Dim matchCount As Long
Set wb = ThisWorkbook
Set ws = wb.Sheets(1)
checkData = ws.Range("E1:E500")
For i = LBound(checkData, 1) To UBound(checkData, 1)
If checkData(i, 1) = "SG" Then
matchCount = matchCount + 1
ReDim Preserve pos(1 To matchCount)
pos(matchCount) = i
End If
Next i
End Sub

Set starting point for UsedRange

I have a combobox drop down that populates items from a list, with a function to filter to dropdown options by characters type in the combobox gathered by the following code
Option Explicit
Private cLstPrior As Variant
Private Sub Worksheet_SelectionChangePrior(ByVal Target As Range)
cLstPrior = Application.Transpose(Database.Columns("1:1").SpecialCells(xlCellTypeConstants, 23)) 'set module-level variable
Tool.priorCmb.List = cLstPrior 'initialize ComboBox to range Col A (UsedRange only)
Tool.priorCmb.ListIndex = -1 'set ComboBox value to empty
End Sub
Private Sub priorCmb_Change()
filterComboListPrior Tool.priorCmb, cLstPrior
End Sub
Private Sub priorCmb_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Tool.priorCmb.DropDown
End Sub
Private Sub priorCmb_GotFocus() 'or _MouseDown()
Tool.priorCmb.DropDown
End Sub
Public Sub filterComboListPrior(ByRef cmbPrior As ComboBox, ByRef dLstPrior As Variant)
Dim itmPrior As Variant, lstPrior As String, selPrior As String
Application.EnableEvents = False
With cmbPrior
selPrior = .Value
If IsEmpty(cLstPrior) Then cLstPrior = Worksheets("Database").Columns("1:1").SpecialCells(xlCellTypeConstants, 23)
For Each itmPrior In cLstPrior
If Len(itmPrior) > 1 Then If InStr(1, itmPrior, selPrior, 1) Then lstPrior = lstPrior & itmPrior & "||"
Next
If Len(lstPrior) > 1 Then .List = Split(Left(lstPrior, Len(lstPrior) - 2), "||") Else .List = dLstPrior
End With
Application.EnableEvents = True
End Sub
The data the combobox needs to populate with is all from Column 1 in this case, any cell with characters in it.
The issue is that there are blank cells at A1 and A2, so blank entries populate the combobox dropdown later on. I am trying to force the range to only include cells with values in them, but am getting an application-defined or object-defined error at If IsEmpty(cLstPrior) Then cLstPrior = Worksheets("Database").Columns("1:1").SpecialCells(xlCellTypeConstants, 23)
I can't seem to figure this out. Also, is my Application.Transpose behavior correct or not needed?
Instead of:
Database.UsedRange.Rows(2)
Try:
Database.Range(Database.Cells(2,2),Database.Cells(Database.UsedRange.Rows.Count, 2))
May be best to use specialcells, and loop through the cells that have values.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim rRng As Range, c As Range, ws As Worksheet
Set ws = Sheets("Database")
Me.ComboBox1.Clear
Set rRng = ws.Rows("2:2").SpecialCells(xlCellTypeConstants, 23)
For Each c In rRng.Cells
Me.ComboBox1.AddItem c
Next c
End Sub
Use Intersect to exclude columns
With Worksheets("Database")
Set rng = Application.Intersect(.UsedRange.Rows(2), .Cells.Resize(.Columns.Count - 1).Offset(1))
End With
Change the line in question to the newly defined range
If IsEmpty(cLst) Then cLst = rng

Cannot use named range when it is empty

I have a named range lstVendors that refers to: =OFFSET(Data!$W$2,0,0,COUNTA(Data!$W$2:$W$400),1). I want this range to be populated when the workbook opens. I have the following code for this:
Private Sub Workbook_Open()
Application.WindowState = xlMaximized
Dim rslt()
Dim i As Integer
Dim n As Integer
Dim startRng As Range
Dim DropDown1 As DropDown
ThisWorkbook.Sheets("Dashboard").Shapes("TextBox 6").Visible = False
' Range("lstVendors").Offset(0, 0).Value = "Please Select..."
' Set DropDown1 = ThisWorkbook.Sheets("Dashboard").DropDowns("Drop Down 1")
' DropDown1.Value = 1
On Error Resume Next
If Not IsError(Range("lstVendors")) Then
Range("lstVendors").ClearContents
End If
On Error GoTo 0
rslt = Application.Run("SQLite_Query", "path/to/my/sqlite", "SELECT PROGRAM_ID FROM VENDOR;")
Set startRng = Range("lstVendors")
i = 0
For n = 2 To UBound(rslt)
Range("lstVendors").Offset(i, 0).Value = rslt(n)(0)
i = i + 1
Next n
End Sub
It errors on the Set startRng = Range("lstVendors"). I know this is because there's nothing in the range when I'm trying to set it, because if I put one entry into the named range, the set works, however, I need it populated by the sqlite query on each open as the data changes.
Any suggestions much appreciated.
Try this. You have a dynamic range that doesn't evaluate after you clear the contents. To avoid this, there are probably several ways, but easy to simply hardcode the startRange variable so that it always points to Data!$W$2 address, which is (or rather, will become) the first cell in your lstVendors range.
Private Sub Workbook_Open()
Dim rslt()
Dim i As Integer
Dim n As Integer
Dim startRng As Range
Dim DropDown1 As DropDown
Dim rngList As Range
'// Define your startRange -- always will be the first cell in your named range "lstVendors"
' hardcode the address because the dynamic range may not evalaute.
Set startRange = Sheets("Data").Range("W2")
'// Empty th lstVendors range if it exists/filled
On Error Resume Next
Range("lstVendors").Clear
On Error GoTo 0
'// Run your SQL query
rslt = Application.Run("SQLite_Query", "path/to/my/sqlite", "SELECT PROGRAM_ID FROM VENDOR;")
i = 0
'// Print results to the Worksheet, beginning in the startRange cell
For n = 2 To UBound(rslt)
'Increment from the startRange cell
startRange.Offset(i, 0).Value = rslt(n)(0)
i = i + 1
'Verify that "lstVendors" is being populated
Debug.Print Range("lstVendors").Address
Next n
End Sub
Thanks for the suggestions. Here is what I ended up doing in order to get around my problem. It involves adding something I didn't specify would be ok in my original question, so David's answer is great if what I did isn't an option. I first populated the first two cells in my named range with "Please Select..." and "All". In Sub Workbook_Open() we do this:
Private Sub Workbook_Open()
Application.WindowState = xlMaximized
Dim rslt()
Dim i As Integer
Dim n As Integer
Dim startRng As Range
Dim DropDown1 As DropDown
' Disable our not found message
ThisWorkbook.Sheets("Dashboard").Shapes("TextBox 6").Visible = False
' Set our start range to our named range
Set startRng = Range("lstVendors")
' Grab all vendor names
rslt = Application.Run("SQLite_Query", "path/to/my/sqlite", "SELECT PROGRAM_ID FROM VENDOR;")
' Print result. Skip first two rows as constants "Please Select..." and "All" are populated there
i = 2
For n = 2 To UBound(rslt)
startRng.Offset(i, 0).Value = rslt(n)(0)
i = i + 1
Next n
End Sub
Then we will create Sub Workbook_BeforeClose:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
' Disable the save changes dialog. This workbook will be locked up for display only. No need to confuse the user.
Application.DisplayAlerts = False
' Clear everything below the "Please Select..." and "All" cells in the named range
On Error Resume Next
Range("lstVendors").Offset(2, 0).ClearContents
On Error GoTo 0
' Save the changes to the named range
ThisWorkbook.Save
Application.DisplayAlerts = True
End Sub
This information is going to populate a drop down, so having Please Select and All hardcoded into the named range is acceptable for me. If that stipulation doesn't work for someone else looking at this in the future, please use David's suggestion! Thanks again!