How do I copy the functionality of Range.Cells in my own class? - vba

I am trying to emulate the Range.Cells(row, col) property in my own class. My .Cells property does update the specified cell correctly.
The problem, however, is that as I'm typing d.Cells(1, 3) =, after the equals sign intellisense will suggest "Cells(row As Long, col) as Range". I'm not sure if this will cause problems for me down the track.
The Cells property is defined like this:
Property Get Cells(row As Long, col As Variant) As Range
' Get the column number for the requested cell
Dim c As Long
If IsNumeric(col) Then
' ensure it is an int
c = CInt(col)
ElseIf VarType(col) = vbString Then
' Get column number from the header name
c = Me.Column(CStr(col))
Else
' Otherwise, variant type is not supported
Exit Property
End If
' Return the requested Cell if column number is valid
If c > 0 And c <= pHeaderRange.Columns.Count Then
Set Cells = pHeaderRange.CurrentRegion.Cells(1 + row, c)
' the row is +1 because pHeaderRange.CurrentRegion also returns
' the header row
End If
End Property
I have also tried this:
Public Property Get Cells(row As Long, col As Variant) As Range
' same code as above
End Property
Public Property Set Cells(v As Variant)
' some code here
End Property
But I get the message: "Compile Error: Definitions of property procedures for same property are inconsistent, or property procedure has an optional parameter a ParamArray, or an invalid Set final parameter."
I think I get the compile error because of parameters I have included in this line Property Get Cells(row As Long, col As Variant) As Range. But I need these parameters to select the cell.
What is the correct way to define the .Cells property in a user defined class so that it works the same way as Range.Cells?
Full code is:
Option Explicit
Private pHeaderRange As Range
'
' Sets the Range of the header row.
'
' -r Range The header row is expected to the in the CurrentRegion of r.
'
Property Let Header(location As Range)
' if range is empty, only the top, left cell will be selected
Dim r As Range
Set r = location.CurrentRegion
' if top row is blank, then remove top row from the range
If WorksheetFunction.CountA(r.Rows(1)) = 0 Then
' dont (and cant) resize unless there are multiple rows in the range
If r.Rows.Count > 1 Then
Set r = r.Resize(r.Rows.Count - 1, r.Columns.Count).Offset(1, 0) ' resizes and repositions range
Else
' the is no header, only a blank cell
Set pHeaderRange = r
Exit Property
End If
End If
' find the starting column of the header row
Dim startCell As Range
Dim endCell As Range
Set startCell = r.Cells(1, 1)
If IsEmpty(startCell) Then
' if startCell is empty, look for header to the right
Set startCell = r.End(xlToRight)
ElseIf IsEmpty(startCell.Offset(0, -1)) Then
' if cell to the left is empty, we have already found the start of the header
Else
' otherwise move to left to find the start
Set startCell = startCell.End(xlToLeft)
End If
' find the last column of the header row
If IsEmpty(startCell.Cells(1, 2)) Then
' if cell to the right is empty, header row only contains one cell
Set endCell = startCell
Else
' otherwise move to right to find the end
Set endCell = startCell.End(xlToRight)
End If
' store the header range
Set pHeaderRange = Range(startCell, endCell)
' debug
pHeaderRange.Select
End Property
'
'
Public Property Get Cells(row As Long, col As Variant) As Range
' Get the column number for the requested cell
Dim c As Long
If IsNumeric(col) Then
' change to int
c = CInt(col)
ElseIf VarType(col) = vbString Then
' Get column by header name
c = Me.Column(CStr(col))
Else
' Otherwise, variant type is not supported
Exit Property
End If
' Return the requested Cell if column number is valid
If c > 0 And c <= pHeaderRange.Columns.Count Then
Set Cells = pHeaderRange.CurrentRegion.Cells(1 + row, c) ' the row is +1 because CurrentRegion also returns the header row
End If
End Property
Public Property Set Cells(v As Range)
' some code here
End Property
'
' Returns the entire column range of the header that matches the index.
'
' -name String The header name to find
'
Public Property Get Column(name As String) As Long
' Find header
On Error Resume Next ' continue even if name is not found (ie Find returns an error)
Dim r As Range
Set r = pHeaderRange.Find(name)
' return column number
Column = r.Column - pHeaderRange.Column + 1
End Property

http://msdn.microsoft.com/en-us/library/gg251357.aspx
The parameters for Property Get, Property Let, and Property Set procedures for the same property must match exactly, except that the Property Let has one extra parameter, whose type must match the return type of the corresponding Property Get...
The problem is that you have arguments in your Get that aren't in your Set. So this would work
Public Property Get Cells(lrow As Long, vcol As Variant) As Range
End Property
Public Property Set Cells(lrow As Long, vcol As Variant, v As Range)
End Property
Except that it makes no sense (which you already know). The reason Cells works in the Excel object model is that it's a read-only property (there's a Get, but no Let or Set). The Cells property returns a Range object, but you can't set Cells. I'm not sure what you're trying to accomplish with your Set statement, but perhaps you don't need it. You don't seem to have any module level variables to store it.

If I understand well your question, maybe you could try:
Public Property Set Cells(row As Long, col As Variant) As Range
'code to set the class
End Property
See some tips on this thread: Set property of vba class with object reference

Related

Excel 2016 UDF not showing up

I have been taking some VBA course from the Excel for Business web site and created a user defined function called ULookup. The function works fine in the spreadsheet and if I use the = sign, the Ulookup will show up. However, Control-Shift-A does not show the complete fill in hints like it does on a PC and if I go to the insert function menu, the function will not show up at all. Is this a bug in the Mac version of Excel or is am I missing something? Here is the code that I put in Module 1 of myWorkbook:
Option Base 1
Function ULookup(lookup_value As Range, lookup_range As Range, source_range As Range, Optional match_type As Integer = 0) As Variant()
' Performs an Index / Match Lookup.
Dim results_Array() As Variant
Dim lookup_index As Long
ReDim results_Array(1) As Variant
' Consider defining this as an array function
' Dim lookup_value ' Contains the value I want to find
' Dim lookup_range ' Range to search in
' Dim source_range ' Pull corresponding value from
' Dim match_type ' Consider the type of match to perform, exact, lesser, greater
' Find lookup_value in lookup range
lookup_index = Application.WorksheetFunction.Match(lookup_value, lookup_range, match_type)
' Determine if lookup value was in the range.
' Get corresponding value from source range
results_Array(1) = WorksheetFunction.Index(source_range, lookup_index)
' Do not edit code beyond this comment
ULookup = results_Array
End Function

Looping through columns to get column numbers based on headers

I have a template with a set number of columns (170) and title headers (row 1 cell name's). This is always the same, until users add columns in between (they're instructed not to change headers). The idea is to make it tamperproof as far as the adding of columns is involved.
I'd like to make variables to hold some of the headers (with the capacity to hold all) and check these with the template to find out the column number (in a loop I reckon). It's probably wisest to make a function to call upon it?
Dim ColHeader1Str as string 'literal row 1, column 1 value (which is always
'the same string and position in the template)
Dim iColHeader1 as integer 'holds the (to be set) value of the column number
Set ColHeader1Str = "ColHeader1"
Now I'd like a loop where it loops trough all the columns (last column = 200) and checks to see what the column number is that matches the ColHeader1Str and store this in the iColHeader1
So something like:
Function find_columnNmbr
Dim i As Integer
For i = 1 To 200 Step 1
If 'ColHeader1Str matches actual column header name
'set found integer as iColHeader1 and so forth
Exit For
End If
Next
End Function`
I know I'm missing a few steps and I'm hoping you guys can help me out.
Update: The template has set column headers. When users interact with it a result could be that columns shift position, or they add more. I have a workbook that needs to load data out of the user's altered template.
I.E. The template has columns 1, 2, 3, 4 and the names are column1, column 2 etc. A user ads a random column so now there are 5. The loop needs to loop through the names of the column headers and identify the column number of the original template columns 1, 2 etc based on a string variable with the original names, which I've hard coded beforehand. These are public constants.
What function LookForHeaders do: input a string, then search for the string in usersheet.range(1:1). If it is found, return the column number of that cell, otherwise it returns 0.
Private Function LookForHeaders(ByVal headerName As String) As Long
Dim rng As Range
Dim userSheet As WorkSheet
Set userSheet = 'userSheet here'
On Error GoTo NotFound
LookForHeaders = userSheet.Range("1:1").Find(headerName).Column
Exit Function
NotFound:
LookForHeaders = 0
End Function
Private Sub Test()
Dim rng As Range
Dim template As WorkSheet
Set template = 'template here'
For Each rng In template.Range(Cells(1,1), Cells(1,200))
iColHeader1 = LookForHeaders(rng.Value)
'Do something with iColHeader1
Next rng
End Sub
Not sure what your looking for but here is example
Option Explicit
Public Sub Example()
Dim LastCol As Long
Dim i As Long
LastCol = ActiveSheet.UsedRange.Columns(ActiveSheet.UsedRange.Columns.Count).Column
For i = 1 To LastCol
If Cells(i) = "Name" Then
Debug.Print Cells(i).Address
End If
Next
End Sub

VBA makro to format XML in Excel to CSV

I need to reformat a XML file to .CSV.
I already opened the XML in Excel and did a little formating but now I really need to write a macro to get the data into shape. I already started bu I really have issues with the loop logic.
the List has a couple thousand Articles with a variable amount of subarticles.
each subarticle as a the same amount of properties but not every article has the same properties.
https://picload.org/image/ipialic/now.jpg
https://picload.org/image/ipialip/then.jpg
My Code up till now looks like this:
Option Explicit
Dim rowCount As Long, articleCount As Long, propertyCount As Integer, name As String
Sub Sortfunction()
rowCount = 1
articleCount = 0
propertyCount = 0
Do While Sheets("Test").Cells(rowCount, 1).Value <> "end"
If Cells(rowCount, 1).Value = "Reference" Then
rowCount = rowCount + 1
Do While Cells(rowCount, 3).Value = ""
If Cells(rowCount, 3).Value = "4" Then
End If
articleCount = articleCount + 1
Loop
articleCount = articleCount + 1
End If
rowCount = rowCount + 1
Loop
Sheets("result").Cells(1, 1).Value = rowCount
Sheets("result").Cells(2, 1).Value = articleCount
End Sub
At the end of the document i wrote the "end" to have a hook to stop the loop.
Can anyone provide some help? I'm really not the best programmer :-/
I'd really appreciate any help I can get :-)
here he's a translation into algorithm and some tips on functions
update: it was more tricky than I thought... I had to rewrite the code.
The main problem is "how to decide when change column".
I choose this solution "Each product in reference must have the same amount of properties".
If it's not the case, please indicate "how you decide when you have to create a new Column" (you can explain it in plain words)
Here the code rewrited. I tried it on your exemple, it work
Public Sub test()
' Set the range to navigate in your first sheet
Dim cell As Range: Set cell = Sheets("Feuil1").Range("A1")
' set the range to navigate in your result sheet
Dim res As Range: Set res = Nothing
' pos will be used to know the position of a product
Dim lastProperties As Range, posProperties As Range
' While the cell value is not "end"
Do While cell <> "end"
' if the cell is a reference
If cell = "Reference" Then
' Set the range of res
If res Is Nothing Then
Set res = Sheets("Feuil2").Range("A1")
Else
Set res = Sheets("Feuil2").Range("A" & lastProperties.offset(2).Row)
End If
' I set Offset(2) so you will have an empty line between 2 references
' Set the text of the new reference in the result
res = cell.offset(, 1) ' The reference is the cell 1 offset the right of the cell "Reference"
' WARNING : here no writing of titles anymore. It'll be done in the "Else".
' Here you just write "new reference" and reinit var
Else
' Here we have a property
' If the property alreay exist, consider it a new product in the reference
' When we are on a new property, the column of the product if the next to the right
If GetProperties(cell.offset(, 3), res, posProperties) Then
Set lastProperties = posProperties
End If
posProperties = cell.offset(, 4)
End If
' BIG FORGET: you have to get the next cell
Set cell = cell.offset(1)
Loop
End Sub
And the function to search / create your properties
Private Function GetProperties(ByVal propValues As String, ByVal start As Range, ByRef position As Range) As Boolean
Set position = start.offset(1)
' Is the cell below the properties ? Return the row below
' Search for the first "empty row" on the line
If position = propValues Then
Do
Set position = position.offset(, 1)
Loop While Trim(position) <> ""
' Indicate it's an existing value
GetProperties = True
Exit Function
End If
' Is the range empty ?
If Trim(position) = "" Then
' Create the new properties
position = propValues
Set position = position.offset(, 1)
GetProperties = False
Exit Function
End If
' Search the properties in the row below
GetProperties = GetProperties(propValues, position, position)
End Function
It should do the work. If you have any question on understanding some part, don't hesitate
if you don't know about Offset, some reading : https://msdn.microsoft.com/en-us/library/office/ff840060.aspx

Knowing the assigned name of a cell instead of the "A1" name

Context:
I have several lists in my sheet (1 column wide, 1-10 rows long). When I right click a cell in these lists, I can do several options, that all work well. I have given a name to the cell at the top of each of these lists (ex. Cell A1 has been given the name cell_1, B10 is names cell_2, etc).
I would like to know if the cell I am right clicking on is the one at the top of the list; is it named "cell_(number)"? If it is not, it checks the cell on top of that one. Does it have a name that starts with "cell_"? If not, check the one on top, etc. Until I can figure out the user clicked on an element of WHICH list.
TL;DR The actual question
I can use ActiveCell.Address, which gives me something like "A1" whether or not I have assigned a name to that cell. ActiveCell.Name gives "Sheet1!A1", so it's not much better. Any idea how to get it to return the name I have assigned instead?
Create a UDF to test the application names, it's less efficient but contains error handling within the function itself:
Sub SO()
'// Example how to call function
Debug.Print GetCellName(Range("A1"))
End Sub
Function GetCellName(myCell As Excel.Range) As Variant
Dim nameCheck As Variant
For Each nameCheck In Application.Names
If Replace(Replace(Replace(nameCheck, "=", ""), "'", ""), "!", "") = _
CStr(myCell.Parent.Name & myCell.Address) Then
GetCellName = CStr(nameCheck.Name)
Exit Function
End If
Next
GetCellName = CVErr(Excel.xlErrName)
End Function
Note you can also use this function in a worksheet cell like so:
=GetCellName(A1)
Perhaps this would work. This function returns the names assigned to a cell (or bigger range for that matter). If there's more than one name, it returns it as an array for array formula...or the user can supply an index to return only the desired name position
Public Function CellIsInRangeNames(sheetname As String, checkRange As Range, Optional itemNumber As Variant) As Variant
Dim oNM As Name
Dim oSht As Worksheet
Dim isect As Range
Dim namesCollection() As Variant
Set oSht = Worksheets(sheetname)
Dim i As Integer
i = -1
For Each oNM In oSht.Names
Set isect = Application.Intersect(Range(oNM.Name), checkRange)
If Not isect Is Nothing Then
i = i + 1
ReDim Preserve namesCollection(0 To i)
namesCollection(i) = CStr(oNM.Name)
End If
Next oNM
If i = -1 Then
'didn't find any
CellIsInRangeNames = xlErrName
ElseIf Not IsMissing(itemNumber) Then
'user wanted this instance only
If (itemNumber - 1 > UBound(namesCollection)) Or (itemNumber - 1 < LBound(namesCollection)) Then
CellIsInRangeNames = xlErrValue
Else
CellIsInRangeNames = namesCollection(itemNumber - 1)
End If
Else 'here's the list as an array
CellIsInRangeNames = namesCollection
End If
End Function

VBA Conditional format cell based on whether value is in list of text

I have this code:
Sub Japan()
Set MyPlage = Range("A1:R1000")
For Each Cell In MyPlage
If Cell.Value = "A" Then
Rows(Cell.Row).Interior.ColorIndex = 3
End If
If Cell.Value = "B" Then
Rows(Cell.Row).Interior.ColorIndex = 3
End If
If Cell.Value = "C" Then
Rows(Cell.Row).Interior.ColorIndex = 3
End If
If Cell.Value = "D" Then
Rows(Cell.Row).Interior.ColorIndex = 3
End If
If Cell.Value = "E" Then
Rows(Cell.Row).Interior.ColorIndex = 3
End If
Next
End Sub
THis find any cells that have either A, B, C, D, E as the value and then colours the entire row red if so.
Basically, I have hundreds of more values that I want to lookup. I have them stored in another excel file (could just as easily be in a text file). How could I reference them? i.e, if cell value is in this list of text, do this.
Sounds like you want a Set datastructure that contains unique values and you can use an Exist method on it.
For example your desired usage is this.
Set MySet = LoadRedValueSet(???) ' explain later
Set MyPlage = Range("A1:R1000")
For Each Cell In MyPlage
If MySet.Exists(Cell.Value) Then
Rows(Cell.Row).Interior.ColorIndex = 3
End If
Next
Well too bad Set is a reserved keyword and VBA does not provide a Set object. However, it does provide a Dictionary object which can be abused like a Set would be. You will need to reference the Scripting Runtime Library to use it first through. The usage would be exactly as stated as above. But first we need to define LoadRedValueSet()
Lets assume that you are able to load whatever file you save these values as in as an Excel worksheet. I will not be explaining how to open various file types in Excel as there are many answers detailing that in more detail than I can. But once you have your range of values to add to the set we can add them to the dictionary.
Private Function LoadRedValueSet(valueRange As Range) As Dictionary
Dim result As New Dictionary
Dim cell As Range
For Each cell In valueRange.Cells
result(cell.value) = Nothing
Next cell
Set LoadRedValueSet = result
End Function
Dictionary are mapping objects that have key->value pairs. The key's are effectively a set, which is what we want. We don't care about the values and you can pass whatever you want to it. I used Nothing. If you use the .Add method the dictionary will throw an error if your list contains duplicate entries.
Assuming you have implemented some function that loads your file as a worksheet and returns that worksheet.
Dim valueSheet As Worksheet
Set valueSheet = LoadSomeFileTypeAsWorksheet("some file path")
Dim valueRange As Range
Set valueRange = valueSheet.??? 'column A or whatever
Dim MyDictAsSet As Dictionary
Set MyDictAsSet = LoadRedValueSet(valueRange)
Set MyPlage = Range("A1:R1000")
For Each Cell In MyPlage
If MyDictAsSet.Exists(Cell.Value) Then
Rows(Cell.Row).Interior.ColorIndex = 3
End If
Next
There are quite a few ways you could possibly do this but here's my approach. Application.WorksheetFunction.<function name> can be used to evaluate worksheet functions within VBA. This means we can use it to run a Match function. For the sake of a simple example let's assume your values to match are in Column A of a worksheet called Sheet2 (in the same workbook).
Dim MyPlage As Range, Cell As Range
Dim result as Variant
Set MyPlage = Range("A1:R1000") '<~~ NOTE: Sheets("<SheetName>").Range("A1:R1000") would be better
For Each Cell in MyPlage
result = Application.WorksheetFunction.Match(Cell.Value, Sheets("Sheet2").Range("A:A"), 0)
If Not IsError(result) Then
Rows(Cell.Row).Interior.ColorIndex = 3
End If
Next Cell
We only need to know whether or not the WorksheetFunction.Match function returned an error: If it didn't then Cell.Value was present in Column A of Sheet2 and we color the row red.
Paste your color value + index data to a new sheet called "Colors" in the following order;
Value ColorIndex
A 1
B 2
C 3
D 4
E 5
And update your method with the following code and update the range based your data;
Sub SetColors()
' DataCells: The cells that's going to be checked against the color values
Set DataCells = Range("A1:A15") ' Update this value according to your data cell range
' ColorValueCells: The cells that contain the values to be colored
Set ColorValueCells = Sheets("Colors").Range("A2:A6") ' Update this value according to your color value + index range
' Loop through data cells
For Each DataCell In DataCells
' Loop through color value cells
For Each ColorValueCell In ColorValueCells
' Search for a match
If DataCell.Value = ColorValueCell.Value Then
' If there is a match, find the color index
Set ColorIndexCell = Sheets("Colors").Range("B" & ColorValueCell.Row)
' Set data cell's background color with the color index
DataCell.Interior.ColorIndex = ColorIndexCell.Value
End If
Next
Next
End Sub