From a list of columns incl Header names with defined widths in a separate sheet, I want to be able to reset column widths in a named sheet to follow the widths defined in the table, e.g.
Column A - Firstname width 32
Column B - Lastname width 40
Column C - Telephone width 6
Column D - Email width 36 etc
The same could be done to define row heights defined in a similar manner.
Thanks for your help
Your question is too imprecise to permit a targeted answer. A variant of the code below may present a generic solution.
Worksheets("Target").Columns(12).ColumnWidth = Worksheets("Source").Columns(3).ColumnWidth
This code has two parts. On the right, the column width is read. On the left it is set. You can do either with any column that you can specify, including the column of a named range such as you have in a table. You can assign the read value to a variable and apply the width stored in a variable to any column you can specify, including columns of named ranges like in a table or ranges you specify in your code on the fly. Note that the column widths is a numeric value of Single data type.
Note that the PasteSpecial method offers an option to copy the column width, too. And, of course, you have the command AutoFit at your disposal to adjust a column's width to whatever is needed by its contents.
This is the code for what I understood you wanted.
Sub test()
Worksheets("test").Columns("D").ColumnWidth = Worksheets("Values").Range("D4").Value
Worksheets("test").Columns("E").ColumnWidth = Worksheets("Values").Range("D5").Value
Worksheets("test").Columns("F").ColumnWidth = Worksheets("Values").Range("D6").Value
Worksheets("test").Range("D7").Value = Worksheets("Values").Range("C4").Value
Worksheets("test").Range("E7").Value = Worksheets("Values").Range("C5").Value
Worksheets("test").Range("F7").Value = Worksheets("Values").Range("C6").Value
End Sub
If you have troubles putting this together with your excel file, I will gladly help you.
Related
In Excel 365, currently I manually update multiple named ranges based on a dynamic header row, with dynamic values under each header. I hoping someone can help me with using VBA code to update the names and ranges all together based on the current data/table.
The goal is to have a drop down list with the relevant Units values, without blanks or duplicates, for each Item.
The source data comes from an external workbook that is updated weekly. The data is in columns of Item and Units, but there is often multiple instances of each Item and Units and each week there may be new Items or some removed, same with the associated Units values. The source is as per below example:
ITEM
UNITS
AA 120
100
AA 120
100
AA 120
150
AA 60
350
BB 200
36
BB 200
30
BB 200
30
SH 1001
55
SH 1001
55
The unique headers are pulled using the formula
=IFERROR(INDEX($B$4:$B$600, MATCH(0, COUNTIF($K$2:K2, $B$4:$B$600), 0)),"")
The following formula is in the row below the headers, to pull the unique values for each header
=UNIQUE(FILTER($C$4:$C$600,$B$4:$B$600=L2))
The resulting table is as per below example:
AA 120
AA 60
BB 200
SH 1001
100
350
36
55
150
30
Currently I have highlight all the headers and the row below and select Create from Selection under Defined Names and select Top row. This creates named ranges that refer to the cell below each header. I then edit each range that the name refers to, by adding # at the end, so it refers to the spilled data, as it is a dynamic range.
e.g. Update the named range reference for AA_120 from =SHEET1!$L$3 to =SHEET1!$L$3#
I do this one by one for 100+ named ranges. Any tips or help to make this more efficient?
oh and the formula I'm using for the source of the Data Validation is =INDIRECT(C7) where C7 is the Item/named range. This all works well...
TIA
EDIT:
I worked out some VBA code to add the # at the end of the referred range. The range for all Named Ranges starts on row 3, so this worked to update all relevant ranges at once...
Sub RangeRename()
Dim n As Name
For Each n In Names
If Right(n.RefersTo, 1) = "3" Then n.RefersTo = n.RefersTo & "#"
Next n
End Sub
Would still really appreciate it if someone could improve on my VBA, to update all the ranges from when the source data is updated. Currently I delete all named ranges in the Name Manager, then select the 2 rows with all the updated Item and Units data. Then Create from Selection under Defined Names and select Top row. Then I run my macro. But if the Macro could do all above, that would be great. The difficulty I see if that the amount of Items and corresponding Units vary as it is dynamic. Plus there are 3 data sources on 3 different sheets, all rows 2&3 but columns start at L, N & T for the 3 sheets.
What I have worked out will work for me, but if anyone can improve on my code, I'd be extremely grateful!
Maybe I don't understand exactly what you're trying to do...but can't you get the info you want with a simple pivot table? I imagine there are advanced tricks you can apply to that to get to the formatting you want...
But using current Excel formulas, I think I got what you want by doing this:
I pasted your source data into a worksheet (A1:B10) and made it into
a table (Control+T), selecting 'My table has headers' option. (I think it's better to use a table but it's certainly possible to tweak the formulas below if you don't want to.)
I picked another cell on the same or another sheet--let's say E2--and entered =TRANSPOSE(UNIQUE(Table1[ITEM])). This gives a horizontal spill of unique items.
In the cell immediately below that (ie, E3) I entered =IFERROR(SORT(UNIQUE(FILTER(Table1[[UNITS]:[UNITS]],Table1[[ITEM]:[ITEM]]=D15))),""). (You need to repeat the bracketed column names like this if you want to drag things horizontally, which we do.) If you don't need/want to sort the Units in the columns we're going to create, you can leave that part of the formula out.
I dragged that cell horizontally to the left, going way beyond you need it right now (in case any new Items appear in your list). The IFERROR in the formula will keep the table looking clean on the right-most columns where no data currently appear. If/when you add more rows to your source data table, the spilled formulas will adjust accordingly, so I think your worry about dynamic sources is taken care of.
Results shown below. Maybe there's a way to devise a single formula that when put into E3 will spill into a 2-D array, so you don't even need to drag things...but I'll leave that to someone else.
Just in case anyone is wanting to achieve the same thing.
I used a filter to create the column of items, then TRANSPOSE(UNIQUE(FILTER(G:G,B:B=T7 for the rows of unit options next to each item.
For the VBA I added the following to a module
Function xDAV(c As Range) As Range
Dim r As Range
With Sheets("LookUpList").Columns("T:T") 'Items
Set r = .Find(What:=c.Value, LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
'Units are in col U
'col U is 1 column to the right from col T
n = 1 'change to suit
If Not r Is Nothing Then
Set f = r.Offset(, n)
If f.Offset(, 1) = "" Then
Set xDAV = f 'single cell or blank cell
Else
rc = f.End(xlToRight).Column 'get last column with data
Set xDAV = f.Resize(, rc + 1 - f.Column)
End If
End If
End With
End Function
Then in the data validation I entered =toXDAV in the source field.
Currently I have a couple of SUM functions setup in my worksheet.
Lets use H2:H34 for my example: If I add a row within that range, the sum function will automatically adjust. The problem is when I add new rows, it is below the range (below H:33). In this case, I would just have to highlight the column and expand/drag the range to where it needs to be. There are a total of 8 columns that are doing a sum function. Is this only possible by using a macro?
=SUBTOTAL(3,H2:H34)
With your formula being the extent of the range minus one row we can use this:
=SUBTOTAL(3,H2:INDEX(H:H,ROW()-1))
Now as rows are added or deleted the reference will remain intact.
If there is a chance that row 2 would be deleted you will get #Ref errors. To avoid that you can use this formula:
=SUBTOTAL(3,INDEX(H:H,2):INDEX(H:H,ROW()-1))
Now there are no specific cell references and adding or deleting will not effect the formula and the range will be dynamic.
you could use a named range like =sum(range1) and something like;
Select first empty cell in column F starting from row 1. (without using offset )
to find the first empty cell to change the reference for the named range.
Triggering it would be difficult... potentially could use on_save?
or option 2.... bit of a dodge, but can work....
you say your sum is in cell H35... maybe move it to h36, and create a dummy hidden row in 35 (0.1 row height). that way when you add a row, it is always above 35. Use sum H1:H35 and it will always auto update? its not a neat solution, but should work
Use the following code, you need to adjust column "H" to whatever column you need.
Dim sumtest As Variant
sumstest = Application.WorksheetFunction.Subtotal(9, Range("H2:H" & ActiveSheet.Cells(ActiveSheet.Rows.Count, "H").End(xlUp).Row))
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)
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.
I've got a spreadsheet with plenty of graphs in it and one sheet with loads of data feeding those graphs.
I've plotted the data on each graph using
=Sheet1!$C5:$C$3000
This basically just plots the values in C5 to C3000 on a graph.
Regularly though I just want to look at a subset of the data i.e. I might just want to look at the first 1000 rows for example. Currently to do this I have to modify the formula in each of my graphs which takes time.
Would you know a way to simplify this? Ideally if I could just have a cell on single sheet that it reads in the row number from and plots all the graphs from C5 to C 'row number' would be best.
Any help would be much appreciated.
OK, I had to do a little more research, here's how to make it work,
completely within the spreadsheet (without VBA):
Using A1 as the end of your desired range,
and the chart being on the same sheet as the data:
Name the first cell of the data (C5) as a named range, say TESTRANGE.
Created a named range MYDATA as the following formula:
=OFFSET(TESTRANGE, 0, 0, Sheet1!$A$1, 1)
Now, go to the SERIES tab of the chart SOURCE DATA dialog,
and change your VALUES statement to:
=Sheet1!MYDATA
Now everytime you change the A1 cell value, it'll change the chart.
Thanks to Robert Mearns for catching the flaws in my previous answer.
This can be achieved in two steps:
Create a dynamic named range
Add some VBA code to update the charts data source to the named range
Create a dynamic named Range
Enter the number of rows in your data range into a cell on your data sheet.
Create a named range on your data sheet (Insert - Name - Define) called MyRange that has a formula similar this:
=OFFSET(Sheet1!$A$1,0,0,Sheet1!$D$1,3)
Update the formula to match your layout
Sheet1!$A$1 set this to the top left hand side of your data range
Sheet1!$D$1 set this to the cell containing the number of rows
3 set this value to the number of columns
Test that the named range is working:
Select the dropdown menus Edit - Go To, type MyRange into the reference field.
Your data area for the chart should be selected.
Add some VBA code
Open the VBA IDE (Alt-F11)
Select Sheet1 in the VBAProject window and insert this code
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address <> "$D$1" Then Exit Sub
'Change $D$1 to the cell where you have entered the number of rows
'When the sheet changes, code checks to see if the cell $D$1 has changed
ThisWorkbook.Sheets("Sheet1").ChartObjects(1).Chart.SetSourceData _
Source:=ThisWorkbook.Sheets("Sheet1").Range("MyRange")
' ThisWorkbook.Sheets("Chart1").SetSourceData _
Source:=ThisWorkbook.Sheets("Sheet1").Range("MyRange")
'The first line of code assumes that chart is embedded into Sheet1
'The second line assumes that the chart is in its own chart sheet
'Uncomment and change as required
'Add more code here to update all the other charts
End Sub
Things to watch for
Do not directly use the named range as the data source for the chart. If you enter the named range "MyRange" as the Source Data - Data Range for the chart, Excel will automatically convert the named range into an actual range. Any future changes to your named range will therefore not update your chart.
Performance might be impacted by the approaches listed above.
The OFFSET function in the named range is "volatile" which means that it recalculates whenever any cell in the workbook calculates. If performance is an issue, replace it with the INDEX formula.
=Sheet1!$A$1:INDEX(Sheet1!$1:$65536,Sheet1!$D$1,2)
The code fires everytime data is changed on Sheet1. If performance is an issue, change the code to run only when requested (i.e. via a button or menu).
You could look at dynamic ranges. If you use the OFFSET function, you can specify a starting cell and the number of rows and columns to select. This site has some useful information about assigning a name to an OFFSET range.
You can set the range for a chart dynamically in Excel. You can use something like the following VBA code to do it:
Private Sub Worksheet_Change(ByVal Target as Range)
Select Case Target
Case Cells(14, 2)
Sheet1.ChartObjects(1).Chart.SetSourceData Range("$C5:$C$" & Cells(14,2))
...
End Select
End Sub
In this case, the cell containing the number of the last row to include is B14 (remember row first when referring to the Cells object). You could also use a variable instead of the Cells reference if you wanted to do this entirely in code. (This works in both 2007 and 2003.) You can assign this procedure to a button and click it to refresh your chart once you update the cell containing the last row.
However, this may not be precisely what you want to do ... I am not aware of a way to use a formula directly within a chart to specify source data.
Edit: And as PConroy points out in a comment, you could put this code in the Change event for that worksheet, so that neither a button nor a key combination is necessary to run the code. You can also add code so that it updates each chart only when the matching cell is edited.
I've updated the example above to reflect this.
+1s for the name solution.
Note that names don't really really reference ranges, they reference formulae. That's why you can set a name to something like "=OFFSET(...)" or "=COUNT(...)". You can create named constants, just make the name reference something like "=42".
Named formulae and array formulae are the two worksheet techniques that I find myself applying to not-quite-power-user worksheets over and over again.
An easy way to do this is to just hide the rows/columns you don't want included - when you go to the graph it automatically excludes the hidden rows/columns
Enhancing the answer of #Robert Mearns, here's how to use dynamic cells ranges for graphs using only the Excel's formulas (no VBA required):
Create a dynamic named Range
Say you have 3 columns like:
A5 | Time | Data1 | Data2 |
A6 | 00:00 | 123123 | 234234 |
...
A3000 | 16:54 | 678678 | 987987 |
Now, the range of your data may change according to the data you may have, like you have 20 rows of data, 3000 rows of data or even 25000 rows of data. You want to have a graph that will be updated automatically without the need to re-set the range of your data every time you update the data itself.
Here's how to do it simply:
Define another cell that it's value will have the number of the occupied cells with data, and put the formula =COUNTIF(A:A,"<>"&"") in it. For example, this will be in cell D1.
Go to "Formulas" tab -> "Define Name" to define a name range.
In the "New Name" window:
i. Give your data range a name, like DataRange for example.
ii. In the "Refers to" set the formula to: =OFFSET(Sheet1!$A$1, 0, 0,Sheet1!$D$1,3),
where:
Sheet1!$A$1 => Reference: is the Reference from which you want to base the offset.
0 => Rows: is the number of rows, up or down, that you want the upper-left cell of the results to refer to.
0 => Columns: is the number of columns, to the left or right, that you want the upper-left cell of the results to refer to.
Sheet1!$D$1 => Height: is the height, in number of rows, that you want the result to be.
3 => Width: is the width, in number of columns, that you want the result to be.
Add a Graph, and in the "Select Data Source" window, in the Chart data range, insert the formula as you created. For the example: =Sheet1!DataRange
The Cons: If you directly use the named range as the data source for the chart, Excel will automatically convert the named range into an actual range. Any future changes to your named range will therefore not update your chart.
For that you need to edit the chart and re-set the range to =Sheet1!DataRange every time. This may not be so usable, but it's better than editing the range manually...