I am working with listobjects in Excel and I have the following problem:
I add data to a table everytime a code is run.
Previously I have to delete all the old data.
ThisWorkbook.Sheets("comm").ListObjects(1).DataBodyRange.Delete
What happend afterwards is that I get an error with:
myNrofRowsinCOMM = COMMtbl.DataBodyRange.Rows.Count
I had a look to this post at no avail. I still dont understand what is going on.
I tried the follwoing as well:
MsgBox "COMMtbl.Range.Rows.Count:" & COMMtbl.Range.Rows.Count
MsgBox "COMMtbl.ListRows.Count:" & COMMtbl.ListRows.Count
MsgBox "COMMtbl.databodyRange.Rows.Count:" & COMMtbl.DataBodyRange.Rows.Count
If COMMtbl.Range.Rows.Count = 1 Then
COMMtbl.ListRows.Add (1)
End If
If the table is empty (row headers and an empty first row) the first line gives 2. So the range has 2 rows which seems according to reality.
COMMtbl.Range.Rows.Count=2
the sencond one gives 0. Which I dont understand at all.
COMMtbl.ListRows.Count=0
And the third one gives an error
"Object variable or withblcok variable not set"
I am trying to add rows to the table and fill them with data, for that I add a row and populate it. I want to add a row at the end, therefore I need everytime to count the number of rows. ALL fine except for the firts one when I previously deleted the whole content of the table which looks like:
Any help is welcome
Thanks a lot.
I needed a refresher, so this might help you too:
.
.Range - Includes the Header, Insert Row and Totals Row (if visible)
.DataBodyRange
- Contains the data area, between the Header Row and the Insert Row
- If the ListObject doesn't have a DataBodyRange, this property returns Null
.ListRows
- Represents all the rows of data (doesn't include Header, Total, or Insert rows)
- To delete any items from this collection, do not use the Delete method of the item
Use the Delete method of the range of the item to delete the item
For example ListRows.Item(1).Range.Delete()
.
When you do DataBodyRange.Delete the table doesn’t have the DataBodyRange object anymore, so to confirm that there are no rows with data in the table, replace
myNrofRowsinCOMM = COMMtbl.DataBodyRange.Rows.Count
with
myNrofRowsinCOMM = COMMtbl.ListRows.Count
More details from MSDN - ListObject
If you don't have data in the ListObject.DataBodyRange Is Nothing, so you can't count rows. You can get the last row of the ListObject by using n = ListObject.Range.Rows.Count and then ListObject.ListRows(n).Range
I don't know how the data you have at hand looks like, but for the sake of the example, if all you had was one column and one row, you could add the data to the last row without worrying if the table is empty or not and then using the .Add method in your example.
Dim current_n_rows As Integer
With ThisWorkbook.Sheets("comm").ListObjects(1)
.DataBodyRange.Delete
current_n_rows = .ListRows.Count
.ListRows(current_n_rows).Range.Value = NewData
.ListRows.Add
End With
You can wrap this statement around the loops you would need to fill the table.
Hope it helps! Cheers!
Related
I would like to find the text "Currency" in columns A or B, store all the currencies listed under Currency. Same process for Amount (Can be in an column)
Store values in an array. Then paste in Output Sheet. The currencies will already be listed in Output sheet in 1st row of the sheet. However if it is a new currency then the code should find last used cell in row 1 and add it. The value of Amount should be added to Output sheet against the currency and ID number also copied from the Source sheet.
I have some code.
Public Sub loopRow()
Dim curArray As Variant
Dim listarray As Variant
Dim cnt As Long
'Find Currency
Dim rgFound As Range
Set rgFound = Range("A:B").Find("Currency")
'Find last used row
curArray = Cells(rgFound.Address).End(xlUp).Row
'Transpose list of currecny from the row down from the word Currency that it has found
listarray = Application.Transpose(Cells(Rows, curArray).End(xlUp)).Row
For cnt = LBound(curArray) To UBound(curArray)
curArray(cnt) = curArray(cnt)
Next cnt
For cnt = LBound(curArray) To UBound(curArray)
'Debug.Print curArray(cnt)
'Copy and paste into Sheet under the correct curreny, if new currency then add this in row A
Next cnt
End Sub
Whilst you need to understand your question is unanswerable as is, I'll do my best to help.
The problem we have is not seeing the source sheet the way you do, as we can't see it at all. You say you have the word Currency in columns A or B or both, and an ID column somewhere, and Amount values everywhere. That's tricky source data. If as is more likely, the ID is in a specific column and the amounts are in a set of columns, then we'd have a chance.
Your question outlines the basic steps you'd want to take pretty well, so you're off to a good start.
However you can do all of the work without VBA, certainly if I'm right about the Source data. Create yourself a working sheet, or multiple working sheets. Definitely one to sort out the full list of currencies. Grab a copy of columns A and B (by formulae) and then have the working sheet go through line by line and use logic to build the list. Spreadsheets are great at this.
Once you have the list, use it as row headers on your Output sheet and use sumifs to get the values. I am not sure how the IDs would fit in, but if they were to be your row headings, then do the same as the above to get the list of unique ids and link them into your Output page in column A. Your sumifs can handle that.
That will hardly tell you all you need to know, but if you work it out you'll have learned a lot about Excel and when you need to go into VBA.
If you'd rather do it with VBA, break down each step until it works, and then go onto the next one.
And if you want more help, paste your data in here. Anonymise it first if you need to.
I'm trying to develop an Excel application that asks our 4D database for information. To do that, I built a query builder and it works. Now I want to make it more generic so that when I call the query builder, I can pass it a range in which the tables and fields the query is based on are stored. Here is a line where I call the sub and pass it the parameters:
QueryDatabase Worksheets("TablesAndFields").Range("A2:R20"), Worksheets("TablesAndFields"), Worksheets("Import")
Here is the first line in the sub:
Sub QueryDatabase(QuerySpecs As Range, QuerySheet As Worksheet, TargetSheet As Worksheet)
One of the things I need to do is have the VBA figure out the row of the last fields in the various columns. Here is my current code:
LastRowReportTables = QuerySpecs.Offset(0, 3).End(xlDown).Row
LastRowQuery = QuerySpecs.Offset(0, 6).End(xlDown).Row
LastRowSort = QuerySpecs.Offset(0, 14).End(xlDown).Row
This returns the same value for all 3 of them (the second line of the range). It seems to do this regardless of which cells have values in them. For instance, in the case of the range specified above it will return 3. If the range is "A22:R40" it returns 23. What I really need is for it to return the row relative to it's position in the range, but I could fake that if necessary by subtracting the largest multiple of 20 less than the result. (I'm formatting my query builder to fit in 19 rows + a buffer row.) So far, I haven't even been able to get it to return different results for the LastRow variables.
In addition to the Offset method you see above, I've also tried putting it in a With QuerySpecs... End With block. I don't remember the exact result, but I couldn't get that to work either.
The next thing I will need to do is pull values out of the various cells kind of like this:
strStartCell = QuerySpecs.Offset(0, 18).Value
This throws a Run time error 13: Type mismatch. Does anybody have any advice on how to accomplish my goals? Thank-you!
I suggest reading the documentation of the resources you are trying to use.
This can be done by selecting the word which you want to search in the documentation, then press the [F1] key, that will take you to the Microsoft documentation page (i.e. if you select Row and press [F1] will take you to the page Range.Row Property (Excel).
Reading the documentation for Row and Offset will help you understand these Range.Properties and the returns you are getting from your code.
To get the last non-empty row in a range of one column, assuming all data in the range is continuous (i.e. there are no empty cells between rows with content), use these lines:
With WorksheetFunction
LastRowReportTables = .CountA(QuerySpecs.Columns(4))
LastRowQuery = .CountA(QuerySpecs.Columns(7))
LastRowSort = .CountA(QuerySpecs.Columns(15))
End With
Note: If the "QuerySpecs" range is a kind of database, all columns in the fields should have the same number of records, if so, it should be sufficient to obtain the last row for one field only.
To obtain the value of a cell within a range, use this line:
This returns the value of the field (column) 18,row 13 of the QuerySpecs range
strStartCell = QuerySpecs.Cells(13, 18).Value2
The Run-time error 13: Type mismatch is generated when trying to assign an array to a string variable.
Starting with screenshot:
http://i.imgur.com/Isj9MER.png
(I'm a new user, can't post images)
Working for a call center. We have a program that tracks our time spent in various phone states (so when we're on calls, out to lunch, etc) that can export data for a given team and date range as a CSV.
I'm working on automating this report. The way it works is that the team lead will pull the CSV, copy-paste into another tab, and then I've got a bunch of array formula If functions and Indirect references to pull all the data as shown. The data analysis and everything is working great.
My problem is the graph. Right now, I've got column B with an If function that either outputs the agent's email (which is how the system tracks it) or "" if all emails have been used. The rest of the columns have If(B2="","", [relevant formula]). That way, we can have all the team leads with various (and fluctuating) team sizes use the same report with a simple copy-paste.
My problem is the stupid bar chart. It pulls data from rows 2-32 (A2:A32). Our current largest team is 28, and I left room for new hires showing up soon. My problem happens when I use data from one of our smaller teams. As you can see, even though the blank rows are filled with "" in every cell, it's still displaying those rows. In the chart. This means that with the smallest team (shown), the chart is half wasted whitespace.
Is there a way to make the column chart only show rows that have actual data in them?
One thing I tried was putting an Indirect reference for Series Values. So I had a cell (AA1) with {=MAX(IF(B2:B31="","",ROW(B2:B31)))}. That outputs the row number of the last non-blank row. Then for the Series Values I put =Indirect("Report!A2:A"&AA1), but Excel gave me an error saying the function was not valid. I guess you can only have an actual range (and not a formula) in the data input for a chart.
Excel 2016, by the way.
I come up with three possible solutions for this problem.
Convert your data table to a pivot table and use a pivot chart (currently only available in Windows version, sorry if you are using a Mac).
Use a bit of VBA to hide the empty rows.
Use a bit of VBA to modify the data displayed on the chart.
Sample Data Setup
Although you provided a screen shot of your data, it is not simple to convert that into a test case to demonstrate the three solutions. Therefore, I threw together this very simple set up.
Column A contains a list of possible "users".
Cell D1 is a user entry to change the contents of column B and C.
Column B contains the actual "users". It is calculated with =IF(A2<=$D$1,A2,"")
Column C contains the data that goes with the "users". It is calculated with =IF(B2<>"",10,"")
A chart is added to the sheet.
Below is a screenshot of the sample setup, where all potential users are included. (Note: It is Sheet1)
Below is a screenshot of the sample setup, where only potential users A through E are included.
The white space in the second image is the problem we are trying to address.
SOLUTION 1: Make a pivot table of the data
Select all of the pertinent data, in this case B1:C12.
Select Insert -> Pivot Table
In the Create Pivot Table dialog, make sure "New Worksheet" is selected, and click OK.
On the Pivot Table, place "User" field in Rows, and "Total" field in Values. Select the Value Field Settings... for the "Total" field and make sure it uses Sum.
In the Pivot Table, select the Row Labels drop down, Label Filters, Greater Than... . Type "" into the dialog and select OK.
From Pivot Table Tools -> Analyze, select PivotChart. Choose Bar Chart from the dialog.
Below is a screen shot of the Pivot Table.
On the tab with the data, change the last potential user from E to G. On the Pivot Table, refresh the data.
Below is a screen shot of the refreshed pivot table.
SOLUTION 2: Use VBA to hide empty rows
The below code is attached to a button made visible on the worksheet. (n.b. There are other ways to activate the code such as change events, but this will depend on a number of factors outside the scope of this answer).
Sub HideRows()
Dim AllCatRange As Range
Set AllCatRange = Worksheets("Sheet1").Range("B2:B12")
Dim iLoop As Long
For iLoop = 1 To AllCatRange.Rows.Count
If AllCatRange.Cells(iLoop, 1) = "" Then
AllCatRange.Cells(iLoop, 1).EntireRow.Hidden = True
Else
AllCatRange.Cells(iLoop, 1).EntireRow.Hidden = False
End If
Next iLoop
Set AllCatRange = Nothing
End Sub
Below is a screen shot of the data tab with the button added.
After clicking the button, it now looks like this ...
This code will expand rows when they contain data, and collapse rows when they do not.
A potential problem with this approach is that expanding/collapsing rows will change the size of the chart, if the chart lays over those rows.
SOLUTION 3: Use VBA to modify the chart
The below code is attached to a button, and used to modify the chart after the source data is changed.
Sub ModifyChart()
Dim AllCatRange As Range
Set AllCatRange = Worksheets("Sheet1").Range("B2:B12")
Dim lastRow As Long
lastRow = 1
Dim iLoop As Long
For iLoop = 1 To 11
If AllCatRange.Cells(iLoop, 1) <> "" Then
lastRow = lastRow + 1
End If
Next iLoop
Dim PlotCatRange As Range
Set PlotCatRange = Worksheets("Sheet1").Range("B2:B" & lastRow)
Dim PlotSerRange As Range
Set PlotSerRange = Worksheets("Sheet1").Range("C2:C" & lastRow)
Worksheets("Sheet1").ChartObjects(1).Chart.FullSeriesCollection(1).XValues = "=Sheet1!" & PlotCatRange.Address
Worksheets("Sheet1").ChartObjects(1).Chart.FullSeriesCollection(1).Values = "=Sheet1!" & PlotSerRange.Address
Set AllCatRange = Nothing
Set PlotCatRange = Nothing
Set PlotSerRange = Nothing
End Sub
Below is a screenshot of the data tab with the new button in place.
Below is a screenshot of the tab after clicking the button.
Wrap up
My personal preference is to use VBA to modify the chart after the data is modified, as this reflects what you would do manually.
I found a solution! Thanks largely to this page. Here's what I did:
I created made a named range using this formula: =OFFSET(Report!$B$1,1,0,COUNTIF(Report!$B$2:$B$30,"<>-"),1)
For that to work, I had to change all the empty cells to output "-" when empty in stead of "". I couldn't get COUNTIF to accept "<>""" or "<>" or any other weird tricks I tried. Using "-" was easier.
That makes a dynamic named range that changes size as I put data into the sheet. I named a similar range for everything I'm trying to chart (Approved Status, Call Ready, Not Ready). The chart was able to accept those names and now it's dynamically sized. If I only have three agents on the sheet, it shows three huge bars. With twenty, it shows twenty bars (exactly what I was looking for).
One other tip: I changed my first row to output "" when empty, so that COUNTIF(Report!$B$2:$B$30,"<>-") always returns at least 1 (otherwise you get annoying errors because you have a named range referencing a 0-length array).
I have a strange problem with two of my Excel Tables residing on two different worksheets in my project. I am using VSTO but VBA shows the same result: an empty table's row count has 0 rows in one case and 1 row (I presume the insert row) in another case.
The Setup
Two worksheets: Sheet1, Sheet2
Two corresponding named Excel Tables: Sheet1Table, Sheet2Table
Both tables are empty, i.e. they have one empty row, which is insert row that cannot be deleted.
I run the following code to determine the number of data rows (i.e. excluding the header row):
Microsoft.Office.Tools.Excel.ListObject sheet1Table = Globals.Sheet1.Sheet1Table;
int numberOfListRows1 = sheet1Table.ListRows.Count;
and
Microsoft.Office.Tools.Excel.ListObject sheet2Table = Globals.Sheet2.Sheet2Table;
int numberOfListRows2 = sheet2Table.ListRows.Count;
The result is that numberOfListRows1 is 1 and numberOfListRows2 is 0 although the result (whichever is correct) should be the same. I compared the table and worksheet properties, as well as the source files in Visual Studio, and I could not spot any differences. Any idea what I should be looking for (and which result is the correct one)?
In VBA I used these subs to test your case:
Sub Sheet1TableRowsCount()
Dim numberOfListRows1 As Integer
Dim sheet1Table As ListObject
Set sheet1Table = Sheet1.ListObjects("Sheet1Table")
numberOfListRows1 = sheet1Table.ListRows.Count
Set sheet1Table = Nothing
End Sub
Sub Sheet2TableRowsCount()
Dim numberOfListRows2 As Integer
Dim sheet2Table As ListObject
Set sheet2Table = Sheet2.ListObjects("Sheet2Table")
numberOfListRows2 = sheet2Table.ListRows.Count
Set sheet2Table = Nothing
End Sub
These are the tables:
When these tables are created (incorrectly) by selecting the headers and one empty row and then formatting the selection as a table, they have one empty row. This row has no content, COUNTA across the row is 0, and it appears like the insert row that cannot be deleted. It is however a valid data row, so ListRows.Count will get the value of 1.
Similarly, if you fill in some data and manually delete those entered values (using Delete) so that your tables would look like at the beginning, the results will still be 1.
If you delete rows manually or programmatically (using something like Sheet1.Range("Sheet1Table").Rows("1").Delete), the ListRows.Count will then yield the value of 0.
The solution to determining the actual count of data rows is to check ListRows.Count, and if the result is 1 - delete the row after checking if it does not contain actual values. This whole situation can be avoided if an empty table is created by selecting the header row only before clicking Format as Table. The insert row is then created automatically and not counted as a data row (the result of ListRows.Count in this case is 0).
Has anyone come across a situation where Excel seems to manipulate your formulas.
I have a sheet where I have an Index value in Column A. The First row starts with any non zero Value. Subsequent rows in the column increment the value. Eg
A1 = 1000
A2= A1+ 1
A3= A2 + 1
and so on/
I have another column B whose values will either be blank or a formula pointing to column A(usually the subsequent rows)
Eg:
B1.Formula = "=A2"
B2.Formula = "=A3"
B3.Value = ""
B4.value = "=A6"
Now I have a backup-restore functionality that lets me write out the data/formulas to a text file and then read it back in another workbook.
In the case of columns A and B, I am checking if the text value starts with "=" and then set either the value or formula of that cell depending on whether there is a formula or not.
So far the functionality has worked fine. It lets me restore accurately.
Now, if I convert this data range to a table and modify the code accordingly the behaviour is strange. I am using the ListObject structure to refer to the table. So for Column B my restore code is:
If Left(soureString) = "=" Then
'This is a formula
Sheets("MySheet").ListObjects(1).ListColumns("Next").DataBodyRange(row).Formula = sourcestring
Else
'This is a value
Sheets("MySheet").ListObjects(1).ListColumns("Next").DataBodyRange(row).Value = soureString
End If
once I am done writing a row, I loop to the start and
Dim newRow AS listrow
Set newRow = Sheets("MySheet").Listrows.Add(AlwaysInsert:=False)
row = newRow.Index
But this time when I run the process. this is what I get:
B1.Formula = "=A5"
B2.Formula = "=A5"
B3.Value = ""
B4.value = "=A5"
Why are my formula values all changing to the same value when I use a table instead of a range?
I had the same issue when populating a ListObject (Table) from an Excel Add-in, setting AutoFillFormulasInLists was the solution.
My workaround is to save the current setting, set AutoFillFormulasInLists to false, populate the table with data, formulas etc, then set AutoFillFormulasInLists back to the original setting.
bool OriginalAutoFillFormulaInListsFlag = app.AutoCorrect.AutoFillFormulasInLists;
app.AutoCorrect.AutoFillFormulasInLists = false;
//[ListObject population code....]
if (OriginalAutoFillFormulaInListsFlag == true)
{
app.AutoCorrect.AutoFillFormulasInLists = true;
}
Hope this helps someone.
I faced a similar issue. Ideally you could tell excel to stop doing this but I haven't been able to figure out how. Supposedly doing the following is supposed to keep excel from copying the formulas:
xlApp.AutoCorrect.AutoFillFormulasInLists = false
but it didn't work for me.
Using the answer from this question How to create running total using Excel table structured references? helped me. It doesn't feel like the ideal solution but it does do the job.
I used this formula where Weight is a column name from my table. #This Row is a "Special item specifier" and has a special meaning. The syntax looks a little funky because it's what's called a Structured Reference:
=AVERAGE(INDEX([Weight],1):[[#This Row],[Weight]])
The INDEX([Weight],1) part gives the reference for the 1st row in the Weight column
While the [[#This Row],[Weight]] part gives the reference for the current row in the Weight column.
So for example, if Weight is column J, and the current row is, say, 7 then this is equivalent to
=AVERAGE(J1:J7)
and on the 8th row it will be equivalent to
=AVERAGE(J1:J8) and so on
I have found that the only way to solve the problem of formulas changing in Excel Tables when you insert in VBA is to insert at the first row of the table, NOT the bottom or the middle. You can sort after.
And I always select or reference the EntireRow to do my insert in the Worksheet object not in the table itself. I always put a table in its own Worksheet anyway using xx.Entirerow.Insert.