I have a table named table in a worksheet titled Sheet. I want to get a range consisting of the a specified column of the body of the table. The following code works:
Public Function GetColumn(colNum as Integer) As Range
With Sheet.ListObjects("table").DataBodyRange
Set GetColumn = Range(.Cells(1, colNum), .Cells(.Rows.Count, colNum))
End With
End Function
I feel there must be a more elegant way to solve this problem.
It seemed like the Columns property would be the way to go but I can't seem to get the syntax right.
You can use the Columns-property of a range to get the range for one column of that range simply by specifying the column number. Note that the index is always relative, if your range starts at C3, column(2) would refer to column D of the sheet.
So for you case:
Including the header cell:
Set GetColumn = ws.ListObjects("Table").Range.Columns(colNum)
Without header cell:
Set GetColumn = ws.ListObjects("Table").DataBodyRange.Columns(colNum)
Update: When you access a Range like that and iterate over it with foreach, it will only execute one iteration, containing all cells. Fix is easy:
Set GetColumn = ws.ListObjects("Table").DataBodyRange.Columns(colNum).Cells
Related
I would like to copy entire rows from mutliple sheets to a target sheet.
But I would like to start the row copy from column B of the target sheet.
I tried
Sheet1.Range("13:32").EntireRow.Copy Sheet5.Range("2:21")
that works fine but starts from column A of target sheet, sheet5 ;
I then tried
Sheet1.Range("13:32").EntireRow.Copy Sheet5.Range("B2:21")
but gives an error.
Can you please help?
Thanks a lot,
Electra
EntireRow will always return ... well ... the entire row!
Therefore you receive an error on the second row - as there are too many cells to be inserted.
You have to resize the range using this function - it will reduce the range by one column and then offsets it to start at the second column.
Public Function resizeRowToStartFromColumnB(rg As Range) As Range
Dim rgResized As Range
With rg
Set rgResized = .Resize(, .Columns.Count - 1).Offset(, 1)
End With
Set resizeRowToStartFromColumnB = rgResized
End Function
Within your code you could use it like:
resizeRowToStartFromColumnB(Sheet1.Range("13:32")).Copy Sheet5.Range("B2")
I'm using VBA code to write to cells in excel. For eg.
Range("C3") = code
Or
Cells(3,3) = code
If a row is inserted in the sheet, the code does then not update accordingly and would still write to Range("C3") etc. So the code then writes to the incorrect cell.
Is there a better way I can structure my code so it will update accordingly? Perhaps using a table instead of cells?
One solution is to used Named Ranges. You can define a Named Range in Formula Tab by clicking on Name Manager.
Then you will write.
Range("Name of the Range") = code
My believe is that named ranges update automatically when a row or column is inserted, so your code will print the value in the correct cell.
Thanks, good idea. I ended up defining the column filled with values as a range, then use a for loop to search for the defined string. That way it doesnt matter what row it is in aslong as the name and string doesnt change (Using a Named Range will probably be better for that exact reason).
Worksheets("Sheet1").Select
Set WS = ActiveSheet
With WS
LastRow = .Cells(.Rows.Count, 2).End(xlUp).Row 'Determine the last row number with data in it for column B
For Each acell In .Range("B1:B" & LastRow) 'Defines the search range from B1 to last row
If acell.Value = "String Searched For" Then
'Do stuff based on found cell location
End If
If acell.Value = "String Searched For#2" Then
'Do stuff based on found cell location#2
End If
Next
End With
I have a column number , say columnNumber = 4 . I need the used range of this column. I know how to find the last used row, and I could convert the column number to a column number like so
ColumnLetter = Split(Cells(1, ColumnNumber).Address, "$")(1)
LastRow = sht.Cells(sht.Rows.Count, ColumnLetter).End(xlUp).Row
and then build an address like so
rngaddy = ColumnLetter & "1:" & ColumnLetter & LastRow
and finally do
Range(rngaddy)
But is there an easier way to find the complete used range of a column given it's number ?
Dim rngaddy As Range
With Sheet1
Set rngaddy = .Range(.Cells(1, 4), .Cells(.Rows.Count, 4).End(xlUp))
End With
and if, for some reason, you want to see the address in A1 notation, merely:
debug.print rngaddy.address
Note that in doing it this way, rngaddy is, itself, the range object and not a string. So no need to do Range(rngaddy)
You could return the last populated cell is in columns # col with this:
MsgBox Cells(sht.Rows.Count,col).End(xlUp).Address
If you want to return the first populated cell as well, you could use:
MsgBox IIf(IsEmpty(Cells(1,col)),Cells(1,col).End(xlDown),Cells(1,col)).Address
Therefore this would return only the "used" range of Column #4 (D):
Sub Example_GetUsedRangeOfColumn()
Const col = 4
Dim sht As Worksheet
Set sht = Sheets("Sheet1")
MsgBox Range(IIf(IsEmpty(Cells(1, col)), Cells(1, col).End(xlDown), _
Cells(1, col)), Cells(sht.Rows.Count, col).End(xlUp)).Address
End Sub
So with this example:
...the above procedure would return: .
My preferred method is to use ListObjects aka Excel Tables to hold any input data whenever I possibly can. ListObjects are named ranges that Excel automatically maintains on your behalf, and because they grow automatically when new data is added, they give you a very robust way of referencing ranges in Excel from VBA, that is more immune to users doing things that might otherwise break code reliant on the .End(xlUp) approach.
? Range("MyTable").ListObject.ListColumns("Column 1").DataBodyRange.Address
$A$3:$A$7
Often I'll give the column concerned a named range of its own, in case the user (or a developer) later wants to change the Table column name, and use that name in my code instead.
? Range("FirstColumn").Address
$A$3:$A$7
If somebody (perhaps me) adds rows/columns above/left of the range of interest or shuffles the order of Table columns around, or changes the name of a column, the code still references the intended range and doesn't need to be changed.
? Range("FirstColumn").Address
$C$4:$C$8
? Range(Range("FirstColumn").Address & ":" & Range("FirstColumn").EntireColumn.cells(1).address).Address
$C$1:$C$8
Granted, that method of getting the range from the top cell (which may be above the ListObject) to the bottom of the column concerned is kinda long, but once you start using ListObjects more in your code you normally don't care what is above or below them...you just want the goods held inside.
I haven't used .End(xlUp) in years, other than to find where my data ends should I be in the process of turning it into a ListObject. But I'm a ListObject evangelist...your mileage may vary :-)
to get the real UsedRange of a columns you could use:
With Columns(columnNumber).SpecialCells(xlCellTypeConstants)
Set rngaddy = .Parent.Range(.Areas(1), .Areas(.Areas.Count))
End With
where rngaddy is a Range object
of course what above would fail if the column has no "constant" cells, then you may want to add some error trapping or entry check (e.g. If WorksheetFunction.CountA(Columns(columnNumber)) = 0 Then Exit Sub
Or
Option Explicit
Public Sub test()
Const columnNumber As Long = 4
Dim rngaddy As Range
Set rngaddy = Intersect(Columns(2), ActiveSheet.UsedRange): Debug.Print rngaddy.Address
End Sub
What I am doing is very simple - selecting a union of columns which contain numbers stored as text and converting them. Every time this runs, all even union numbered columns of data are getting cleared.
Union(Columns(19), Columns(22), Columns(25), Columns(28), Columns(31), Columns(34), Columns(37), Columns(40), Columns(43), Columns(46)).Select
With Selection
.Value = .Value
End With
I've looked though my entire code multiple times are cant figure why this is behaving so weird. any help is greatly appreciated.
The Value property of a discontiguous range only returns the first area of that range. When you then try and assign that value (array, in this case) back to a discontiguous range, you get strange results. For this particular case, every second column will get the value of the first cell in the first area.
You should loop through the areas in your range.
For each rArea in Selection.Areas
rarea.value2 = rarea.value2
Next rarea
Try to avoid using Select, and fully qualify your ranges. This makes things easier to diagnose and more robust...
Dim myRange As Range
With ThisWorkbook.Sheets("Sheet1")
Set myRange = Union(.Columns(19), .Columns(22), .Columns(25)) ' etc.
End With
Now if you're trying to convert text to numbers, you might be better off using the NumberFormat property as discussed here: What are .NumberFormat Options In Excel VBA?
Looping through range areas and number-formatting:
Dim area As Range
For Each area In myRange.Areas
area.NumberFormat = 0 ' for numbers, could still use area.Value = area.Value
Next area
I want to write a formula like =SUM(tab2!A:A) but instead use the column title of A which is say "count". How can I modify this to look more like: =SUM(tab2!"count")?
The reason I want to do this is because I copy and paste a spreadsheet from another source in tab2 and the column referring to "count" may be in a different column. I want the formula to give me the correct calculation as soon as I paste the new spreadsheet by automatically finding the column to sum up by title name.
I must be missing something because this seems like a very basic question and I can't find the answer anywhere...
Thanks for your help!
I like the idea of naming ranges proposed by #Doug, but if the issue is that you are dumping your data in [and you don't know in advance which column is going to be where] and would need to rename your range every time, there are other options - I suggest using OFFSET. OFFSET allows you to dynamically create a range, starting at a particular point and moving down/up / right/left for as many rows and columns as you determine.
In your case, you will need to combine that with a method for searching the columns to see which one says "Count". I am assuming that your column headings are always in row 1. If they aren't [or if they aren't always in row 2, or any row you know in advance]... you can get around that but then I'd recommend you try to make your data more uniform instead of creating unnecessary Excel workarounds.
In total your formula would look like this:
=SUM(OFFSET(A1,,MATCH("Count",1:1,0)-1,ROWS(A:A),1))
What this does is first determine which column the word "Count" is found in, in row 1. Then it subtracts 1 from that number - this now equals the number of columns to the right that it is, from column A. It uses offset to create a reference to that column, for all rows, and sums those rows together.
Check out the name manager for naming ranges :)
You didn't say whether you would consider a macro solution. If so, this may work.
If the sheet you are pasting into is Sheet2 and the sheet you want the result in is Sheet1, then this macro, if placed in the Worksheet_Activate event of Sheet1 will give you the result as soon as you click on the Sheet1 tab afetr pasting your data into Sheet2:
Private Sub Worksheet_Activate()
Dim r As Range
Dim rCol As Range
Dim rFound As Range
Dim ws As Worksheet
Dim lTotal As Long
Set ws = Sheet2
Set r = ws.Cells
Set rFound = r.Find("count")
If Not rFound Is Nothing Then
Set rCol = rFound.EntireColumn
lTotal = Application.WorksheetFunction.Sum(rCol)
End If
Cells(1, 1) = lTotal
End Sub
It does assume there is only one cell with the word "count" in it on Sheet2.