Excel VBA changing my formulas in a table? - excel

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.

Related

Excel Table, using VBA to change data in a column

I have a Table with a column that I added, that checks the date of that row against a formula.
The result of that formulae is TRUE or FALSE and a subsequent Pivot Table Sums a value in the TRUE rows. I Introduced it to get what is called a Rolling Total Income.
There are two formula and I want to swap from one to another using VBA.
The formula are:
=IF([#Date] = "", "", AND([#Date]>=EOMONTH(TODAY(),-13)+1,[#Date]<EOMONTH(TODAY(),-1)))
for the Rolling data.
=IF(AND([#Date] >=G1,[#Date] <=H1),"TRUE","FALSE")
for the Sum of Income between G1 and H1, the financial year start and end dates.
The VBA I have tried is as follows:
Set lo = Sheet2.ListObjects(1) 'Sheet2 is the Income sheet see Project list
lo.Parent.Activate 'Activate sheet that Table is on
Set lColName = lo.ListColumns(9)
lColName.DataBodyRange.Cells(1).Formula = [=IF(AND([#Date] >=G1,[#Date] <=H1),"TRUE","FALSE")]
Running the above errors and gives #VALUE in the first cell in that column, and doesn't ripple through the rest of the column.
What have I done wrong?
Try
lColName.DataBodyRange.Formula = "=IF(AND([#Date] >=$G$1,[#Date] <=$H$1),""TRUE"",""FALSE"")"
Thanks, those changes have sorted it.
One more refinement I am struggling with.
The IF test statement is testing a date against cells, $G1 and $H1 which I set up for ease of debugging. However I have another worksheet in the workbook where I store all the references/static data I use in the various Sheets and I have placed those dates there. In this Module, before the IF statement, I have set those dates against two variables.
startdate = Range("I2").Value
enddate = Range("J2").Value
I now want to use those in the IF statement but cannot get the correct syntax!
This just errors:
"=IF(AND([#Date] >= startdate,[#Date] <= enddate),""TRUE"",""FALSE"")"
How should I write it?
Thanks

Work with specific column in Named Range (Excel VBA)

Please have a look at this table, which I have named "Tasks":
It is a very basic GANTT chart-like table. Using VBA I use data from this table in order to perform some tasks in other Worksheets.
I have a For loop which loops through each row like this:
For i = 1 To Range("Tasks").Rows.Count
Worksheets("Calendar").Cells(i,2).Value = Range("Tasks").Cells(i,2)
End For
There are many other operations within the For loop, but that goes beyond the scope of my question. Basically what this does is that it loops through the entire table and performs various operations and calculations (where applicable) which results in populating other cells in other worksheets.
My question is this:
Since all columns in the table are labeled, I would like to somehow reference the Column name instead of column number in the loop, if it is possible of course.
For example:
Worksheets("Calendar").Cells(i, 2).Value = Range("Tasks").Cells(i, "Title")
This helps for code readability since then I would know that this references the "Title" column, instead of going back and forth to see which column is e.g. the number "2".
I know that this kind of reference can be done in Excel by using
=Tasks[Title]
(e.g. This expression can be used for Data Validation)
Is it possible to reference columns like this? I am relatevely new to VBA, so I'm not quite sure.
Looking forward for your answer.
if it's an Excel Table, then you can use
Dim lo As ListObject
Dim col As ListColumn
Dim r As Range
Set lo = ActiveSheet.ListObjects("Tasks")
Set col = lo.ListColumns.Item("Title")
r = col.DataBodyRange
For i = 1 To Range("Tasks").Rows.Count
Worksheets("Calendar").Cells(i,2).Value = r.Cells(i)
End For

ListRows.Count Returns Inconsistent Results

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).

How To Reference an Excel Table Cell by Row Number or Horizontal Header Using Table Notation?

I'd like to reference a single cell in a table, from outside the table, using square-bracket sheet-formula notation.
Something like:
[MyTable[MyField] 3]
or
[MyTable[MyField] 3:3]
-to reference the 3rd row of the MyField column, or:
[MyTable[MyField] MyRow]
-to reference the MyRow row (leftmost row-header) of the MyField column.
Needs to work from outside the table, ie can't use # or #ThisRow.
Not looking for methods involving MATCH, INDEX, OFFSET, etc. Not looking for VBA methods. Just straightforward table-notation. Not looking for manually creating named ranges.
Why? Because, Tables :)
Pre-2013 Excel.
(PS, didn't there used to be a way (pre-Tables) to reference cells by row and column headers? I think it was maybe called "auto-naming", or something like that.)
heh, well this works:
=Table1[Column2] 3:3
So that's progress :)
Just awesome would be a way to reference a row by the contents of left-most column.
Thx!
You can also use index() function as in:
index(MyTable[MyField], 3)
So you get row 3 from the column MyField in table MyTable.
Reference:
https://www.ozgrid.com/forum/forum/help-forums/excel-general/116365-reference-a-single-cell-in-a-table-using-structured-referencing
We can reuse the idea of the intersection operator (i.e. a space between two references) and improve it to have the relative row number of the targeted item in the table, referred to as row_nb here:
=tbl[col] OFFSET(tbl[[#Headers],[col]],row_nb,)
or just without intersection actually (cf. comment below):
=OFFSET(tbl[[#Headers],[col]],row_nb,)
E.g. =Table1[Column2] OFFSET(Table1[[#Headers],[Column2]],2,)
This way you do not depend on the position of the table in the worksheet. Well, it yields a more complicated formula where table name tbl and column name col appear twice. Here are some comments about it:
You can of course keep the OFFSET(...) part only. The downside is that the formula will never return any error if row_nb exceeds the actual number of line items in the table. It'll return e.g. just 0 if the cells below the table are empty.
Keeping a formula that throws an error when we refer to an off-table row, we can further improve it: make it "dynamic" by letting tbl, col and row_nb be parameters:
=INDIRECT(tbl&"["&col&"]") OFFSET(INDIRECT(tbl&"[[#Headers],["&col&"]]"),row_nb,)
Assuming that we've defined tbl, col and row_nb as named ranges. Or else just use cell addresses:
=INDIRECT(A1&"["&A2&"]") OFFSET(INDIRECT(A1&"[[#Headers],["&A2&"]]"),A3,)
I'm not a big fan of INDIRECT but in this case it really comes in handy to let the formula adapt to various tables, columns and line items.
To handle tables that don't start at the first row of a worksheet, we can use the ROW() function. We can determine the first row of the data in the table with:
=ROW(myTable)
Using this and the Indirect() Function we can identify the first cell in a named column with
= myTable[myField] INDIRECT(ROW(myTable) & ":" & ROW(myTable))
The 3rd cell in that column would be:
= myTable[myField] INDIRECT(ROW(myTable)+3 & ":" & ROW(myTable)+3)

How can I do the equivalent of a COUNT/GROUP BY in Excel?

I have a table in an Excel spreadsheet. I'd like to get the equivalent of doing something like
SELECT col1, COUNT(col1), COUNT(col2)
FROM table
GROUP BY col1
in SQL. I was trying to use Pivot Tables, and I am maybe overthinking this, but I'm not used to working in Excel and couldn't figure it out.
All help appreciated. Thank you.
Well in a Cell Formula it would be (ie)
//Cell Formula
=Count(A1:A50) // Range
=Count(A1, A3, A5) // Specific Cells
// in VBA
=Sheet1.Range("A:A").Cells.SpecialCells(xlCellTypeConstants).Count
// Where "A" is the column and 'SpecialCells(xlCellTypeConstants)' returns non-blank cells
// if you need to check for a specific value in vba:
Dim counter As Integer
counter = 0
For Each c In Sheet1.Range("A1:A100").Cells
If c.Value = "Something" Then
counter = counter + 1
End If
Next
// Then get your value of the counter variable
You might have to do a nested loop to get what you are looking for.
Open Access instance in background. Put the data. Query by SQL.

Resources