Excel Conditional Formatting expanding with pivot tables? - excel

Once conditional formatting is applied to a pivot table, if you expand the table, how can the conditional formatting be updated automatically to apply itself to the new expanded pivot table?
Attached is the sample excel file.

The following is verbatim from my post at http://yoursumbuddy.com/re-apply-excel-pivot-table-conditional-formatting/. You might also be interested in http://yoursumbuddy.com/unified-method-of-pivot-table-formatting/:
The key to this code is the ModifyAppliesToRange method of each FormatCondtion. This code identifies the first cell of the row label range and loops through each format condition in that cell and re-applies it to the range formed by the intersection of the row label range and the values range, i.e., the banded area in the first image above.
This method relies on all the conditional formatting you want to re-apply being in that first row labels cell. In cases where the conditional formatting might not apply to the leftmost row label, I’ve still applied it to that column, but modified the condition to check which column it’s in.
This function can be modified and called from a SheetPivotTableUpdate event, so when users or code updates a pivot table it re-applies automatically.
Sub Extend_Pivot_CF_To_Data_Area()
Dim pvtTable As Excel.PivotTable
Dim rngTarget As Excel.Range
Dim rngSource As Excel.Range
Dim i As Long
'check for inapplicable situations
If ActiveSheet Is Nothing Then
MsgBox ("No active worksheet.")
Exit Sub
End If
On Error Resume Next
Set pvtTable = ActiveSheet.PivotTables(ActiveCell.PivotTable.Name)
If Err.Number = 1004 Then
MsgBox "The cursor needs to be in a pivot table"
Exit Sub
End If
On Error GoTo 0
With pvtTable
'format conditions will be applied to row headers and values areas
Set rngTarget = Intersect(.DataBodyRange.EntireRow, .TableRange1)
'set the format condition's source to the first cell in the row area
Set rngSource = rngTarget.Cells(1)
With rngSource.FormatConditions
For i = 1 To .Count
'reset each format condition's range to row header and values areas
.Item(i).ModifyAppliesToRange rngTarget
Next i
End With
'display isn't always refreshed otherwise
Application.ScreenUpdating = True
End With
End Sub

Pivot tables are notoriously unresponsive to formatting and have a history of not keeping manually applied formatting, not even mentioning extending conditional formatting. If you require that,you may want to consider using VBA to re-apply formats after a refresh, or apply conditional formats to whole rows or whole columns.

I'm using 2010 so i can't say this would work in other versions,
also as far as i have tested this only works if you want the conditional formatting to apply to all cells within the pivot chart (not just one specific row of data, you may be able to exclude grand totals, but i haven't tried).
However before doing any coding it might be worth a quick try if you want the formatting to apply to all rows in your table.
Create a New Worksheet > Enter your conditional formatting into a cell in this new worksheet > use the copy formatting paint brush on this cell > go back to your pivot chart worksheet and drag the formatting over first top left hand value down to the last bottom left value.
Refresh your chart, expand collapse your chart, your formatting should stay put, not only that, but even when you remove all the fields from your pivot and add them back in, stays in place. it seems that by doing this it applies the formatting to the chart it'self rather than the individual cells within in.
I found this out quite by accident but as I am often on here looking for answers it seemed only right to let people know what i discovered, even if it was a fluke.

Related

How to alternate row color in a table when cell value changes?

I have a spreadsheet that brings in a table of data via Power Query.
Every time there is a refresh, the table length may change.
I need columns C and D to alternate highlight colors when the value is not the same as the previous row.
How the table should look each time it is refreshed through Power Query and the VBA code runs.
(1) Attempt with conditional formatting:
(Note: This will work correctly only if a value cannot appear later down that list).
Create a rule (or two rules, to be precise) based on a formula. According to your screenshot, I assume that your data starts at row 3 and you want to look at column C.
There is a rather easy formula that you can use to count the number of unique values of a list. The base formula was "borrowed" from ExcelJet and is =SUMPRODUCT(1/COUNTIF(data,data)). The trick now is that you look to the range from the beginning of the list to the actual row by using the range C$3:C3. If you copy the formula down, the start row remains while the end row is changed. Now simple put a IsOdd resp IsEven around the formula.
So the formula is =ISODD(SUMPRODUCT(1/COUNTIF(C$3:C3,C$3:C3))) for the "green" rows and =ISEVEN(SUMPRODUCT(1/COUNTIF(C$3:C3,C$3:C3))) for the yellow. Apply the rule to the range =$C$3:$C$1048576
However, I don't know if this conditional formatting will "survive" an update of the query.
(2)
Formatting with VBA is simple. The following code formats one column of an table (a table in VBA is the type ListObject. Pass the listobject and the column number as parameter:
Sub ColorRows(table As ListObject, columnNumber As Long)
Dim cell As Range, isOdd As Boolean
For Each cell In table.DataBodyRange.Columns(columnNumber).Cells
With cell
If .Offset(-1, 0) <> .Value Then isOdd = Not isOdd
' On a standard Office color scheme, 10 is kind of green and 8 is a dirty yellow
.Interior.ThemeColor = IIf(isOdd, 10, 8)
.Interior.TintAndShade = 0.8
End With
Next
End Sub
This is how the call could look like (adapt the sheet and the listObject to your needs):
Sub test()
ColorRows ThisWorkbook.Sheets(1).ListObjects(1), 3
End Sub
Now calling this code automatically is a different story and unfortunately rather complicated - if you want/need, try https://stackoverflow.com/search?q=vba+QueryTable+After+Refresh for some insights. An easy alternative is to trigger the formatting manually, eg by placing a button (or a shape) on your sheet that calls the code.
The VBA to apply the format condition for the alternating coloring would be:
Public Sub alternatingColorSizeAndKind(rg As Range)
Dim fc As FormatCondition
With rg.FormatConditions
.Delete
Set fc = .Add(xlExpression, , "=($C1=$C2)+($C2=$C3)")
fc.Interior.Color = 14348258
Set fc = .Add(xlExpression, , "=($C1<>$C2)")
fc.Interior.Color = 13431551
End With
End Sub
You have to pass the range of your table to this sub.
If you have a listobject/table then you call it like this:
Public Sub update()
Dim lo As ListObject
'>>> adjust the names to your needs
Set lo = ThisWorkbook.Worksheets("Sheet1").ListObjects("Pull_Data")
alternatingColorSizeAndKind lo.DataBodyRange
End Sub

Receipt printing - how to not show zero value rows

I am setting up a receipt printout from Excel 2010. I have multiple items on the worksheet and can print everything ok.
The receipt is to be printed in a busy environment and as such we just want the operators to enter the numbers and press CTRL+P.
Not all the items on the receipt will be used:
Item 1 10:00
Item 2 0.00 <--- This is an example of the row I do not want to print
Item 3 10.00
Total 20.00
The number of items could increase over time so the solution must be able to include the entire print range. Use of the hide function is not an option as it takes to long.
The solution must require no action by the user as they are not 'computer people'.
All cells are locked except those which require data to be entered to minimise input errors. i.e. VAT calculations
I had tried a VB routine but with no luck, hence the question.
EDIT: The VB I had written was-
Private Sub Workbook_BeforePrint(Cancel As Boolean)
Dim RngCol As Range
Dim i As Range
Set RngCol = Range("B1", Range("B" & Rows.Count). _
End(xlUp).Address)
For Each i In RngCol
If i.Value = 0 Then
i.EntireRow.Hidden = True
End If
Next i End Sub
I have tried Jeeped's suggestion but some how the page size has now changed - won't change back either?
Although Jeeped's suggestion has done what I wanted it is now ignoring the header which is needed although I can move the info to the main sheet.
Use a conditional format rule. I always use rules based on formulas like =NOT($D1) to cover columns A:E depending on the value in column D but any of the others will do if you can determine criteria that equals zero. When you decide on how you want to handle the criteria, click Format and go to the Number tab. Choose Custom from the list down teh left and use ;;; (three semi-colons) for the Type:.
Click OK to accept the format and then OK to create the rule. Nothing in A:E will be visible if there is a blank or 0 in column D.
Alternate AutoFilter Method:
The individual worksheets do not have any standard events to do with printing but the workbook has a BeforePrint Event.
Go the workbook's VBE and locate ThisWorkbook in the Project Explorer then double-click it. Paste the following into the new pane on the right titled something like ThisWorkbook (Code).
Option Explicit
Private Sub Workbook_BeforePrint(Cancel As Boolean)
Cancel = False
With Worksheets("Sheet2")
If .AutoFilterMode Then .AutoFilterMode = False
With .Range(.Cells(1, 2), .Cells(Rows.Count, 2).End(xlUp))
.AutoFilter Field:=1, VisibleDropDown:=False, _
Criteria1:="<>0.00", Operator:=xlAnd, Criteria2:="<>"
End With
End With
End Sub
Edit to suit the actual column that holds the numbers. I've used column B. Note that the AutoFilter requires a header row.
Tap Alt+Q to return to your worksheet. Any method of printing the page will result in the AutoFilter Method being applied to column B and hiding any blanks or zeroes (in the form of 0.00) in that column.
Before:        
After:    
As you can see from the sample images, printing to a .PDF filters the code column. I've also chosen not to display the little drop-down arrow normally associated with an AutoFilter.
You can unhide the rows with conventional unhide commands, clear the filter or just turn the autofilter off to get the rows back.
This latter method is a bit more involved but it gets rid of (aka hides or filters out) the undesired rows rather than simply not displaying the text in them. If need be, the worksheet could be unprotected for the duration of the print cycle.

Combine 2 Excel tables into one appending the data?

I have 2 tables on 2 separate sheets of an MS Excel 2007 workbook, like below:
===========================
no. f_name l_name
===========================
13 Little Timmy
1 John Doe
17 Baby Jessica
---------------------------
===========================
no. f_name l_name
===========================
1 john Tim
16 kyle joe
14 Baby katy
22 qbcd wsde
---------------------------
Both have the same columns, but they can have different data.
I want to combine the data of both tables vertically i.e. a single table with all the data in a 3rd separate sheet.
If possible, I want to add another column with the sheet name from where the row came.
===================================
SheetName no. f_name l_name
===================================
Sheet1 13 Little Timmy
Sheet1 1 John Doe
Sheet1 17 Baby Jessica
Sheet2 1 john Tim
Sheet2 16 kyle joe
Sheet2 14 Baby katy
Sheet2 22 qbcd wsde
-----------------------------------
Can it be done without using macros?
This answer deals with Structured Tables as interpreted by Excel. While the methods could easily be transcribed to raw data matrixes without assigned table structure, the formulas and VBA coding for this solution will be targeted at true structured tables.
Preamble
A third table can maintain the combined data of two tables with some native worksheet formulas but keeping the third table sized correctly as rows are added or deleted to/from the dependent tables will require either manual resizing operations or some VBA that tracks these changes and conforms the third table to suit. I've included options to add both the source table's worksheet name as well as some table maintenance VBA code at the end of this answer.
If all you want is an operational example workbook without all the explanation, skip to the end of this answer for a link to the workbook used to create this procedure.
Sample data tables
    
I've used the OP's sample data to construct two tables named (by default) Table1 and Table2 on worksheets Sheet1 and Sheet2 respectively. I've intentionally offset them by varying degrees from each worksheet's A1 cell in order to demonstrate a structured table's ability to address either itself or another structured table in a formula as a separate entity regardless of its position on the parent worksheet. The third table will be constructed in a similar manner. These offsets are for demonstration purposes only; they are not required.
Step 1: Build the third table
Build the headers for the third table and select that future header row and at least one row below it to base the Insert ► Tables ► Table command upon.
        
Your new empty third table on the Sheet3 worksheet should resemble the following.
    
Step 2: Populate the third table
Start by populating the first cell in the third table's DataBodyRange. In this example, that would be Sheet3!C6. Type or paste the following formula in C6 keeping in mind that it is based on the default table names. If you have changed your tables names, adjust accordingly.
=IFERROR(INDEX(Table1, ROW([#[no.]])-ROW(Table3[#Headers]),COLUMN(A:A)), INDEX(Table2, ROW([#[no.]])-ROW(Table3[#Headers])-ROWS(Table1),COLUMN(A:A)))
The INDEX function first retrieves each available row from Table1. The actual row numbers are derived with the ROW function referencing defined pieces of the structured table together with a little maths. When Table1 runs out of rows, retrieval is passed to a second INDEX function referencing Table2 by the IFERROR function and its sequential rows are retrieved with the ROW and ROWS functions using a bit more maths. The COLUMN function is used as COLUMN(A:A) which is going to retrieve the first column of the referenced table regardless of where it is on the worksheet. This will progress to the second, third, etc. column as the formula is filled right.
Speaking of filling right, fill the formula right to E6. You should have something that approximates the following.
    
Step 2.5: [optional] Add the source table's parent worksheet name
Grab Table3's sizing handle (indicated by the orange arrow in the sample image below) in the lower right hand corner and drag it right one column to add a new column to the table. Rename the header label to something more appropriate than the default. I've used Sheet as a column label.
        
While you cannot retrieve the worksheet name of the source table directly, the CELL function can retrieve the fully qualified path, filename and worksheet of any cell in a saved workbook¹ as one of its optional info_types.
Put the following formula into Table3's empty cell in the first row of the new column you have just created.
=TRIM(RIGHT(SUBSTITUTE(CELL("filename", IF((ROW([#[no.]])-ROW(Table3[#Headers]))>ROWS(Table1), Table2, Table1)), CHAR(93), REPT(CHAR(32), 999)), 255))
Complate populating Table3
If you are not planning on finishing this small project with some VBA to maintain Table3's dimensions when rows are added or deleted from either of the two source tables then simply grab Table3's resizing handle and drag down until you have accumulated all of the data from both tables. See the bottom of this answer for a sample image of the expected results.
If you are planning to add some VBA, then skip the full population of Table3 and move on to the next step.
Step 3: Add some VBA to maintain the third table
Full automation of a process that is triggered by changes to a worksheet's data is best handled by the worksheet's Worksheet_Change event macro. Since there are three tables involved, each on their own worksheet, the Workbook_SheetChange event macro is a better method of handling the change events from multiple worksheets.
Open the VBE with Alt+F11. Once you have it open, look for the Project Explorer in the upper left. If it is not visible, then tap Ctrl+R to open it. Locate ThisWorkbook and right-click then choose View Code (or just double-click ThisWorkbook).
        
Paste the following into the new pane titled something like Book1 - ThisWorkbook (Code).
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Select Case Sh.Name
Case Sheet1.Name
If Not Intersect(Target, Sheet1.ListObjects("Table1").Range.Offset(1, 0)) Is Nothing Then
On Error GoTo bm_Safe_Exit
Application.EnableEvents = False
Call update_Table3
End If
Case Sheet2.Name
If Not Intersect(Target, Sheet2.ListObjects("Table2").Range.Offset(1, 0)) Is Nothing Then
On Error GoTo bm_Safe_Exit
Application.EnableEvents = False
Call update_Table3
End If
End Select
bm_Safe_Exit:
Application.EnableEvents = True
End Sub
Private Sub update_Table3()
Dim iTBL3rws As Long, rng As Range, rngOLDBDY As Range
iTBL3rws = Sheet1.ListObjects("Table1").DataBodyRange.Rows.Count
iTBL3rws = iTBL3rws + Sheet2.ListObjects("Table2").DataBodyRange.Rows.Count
iTBL3rws = iTBL3rws + Sheet3.ListObjects("Table3").DataBodyRange.Cells(1, 1).Row - _
Sheet3.ListObjects("Table3").Range.Cells(1, 1).Row
With Sheet3.ListObjects("Table3")
Set rngOLDBDY = .DataBodyRange
.Resize .Range.Cells(1, 1).Resize(iTBL3rws, .DataBodyRange.Columns.Count)
If rngOLDBDY.Rows.Count > .DataBodyRange.Rows.Count Then
For Each rng In rngOLDBDY
If Intersect(rng, .DataBodyRange) Is Nothing Then
rng.Clear
End If
Next rng
End If
End With
End Sub
These two routines make extensive use of the Worksheet .CodeName property. A worksheet's CodeName is Sheet1, Sheet2, Sheet3, etc and does not change when a worksheet is renamed. In fact, they are rarely changed by even the more advanced user. They have been used so that you can rename your worksheets without having to modify the code. However, they should be pointing to the correct worksheets now. Modify the code if your tables and worksheets are not the same as given. You can see the individual worksheet codenames in brackets beside their worksheet .Name property in the above image showing the VBE's Project Explorer.
Tap Alt+Q to return to your worksheets. All that is left would be to finish populating Table3 by selecting any cell in Table1 or Table2 and pretending to modify it by tapping F2 then Enter↵. Your results should resemble the following.
    
If you have followed along all the way to here then you should have a pretty comprehensive collection table that actively combines the data from two source 'child' tables. If you added the VBA as well then maintenance of the third collection table is virtually non-existent.
Renaming the tables
If you choose to rename any or all of the three tables, the worksheet formulas will instantly and automatically reflect the changes. If you have opted to include the Workbook_SheetChange and accompanying helper sub procedure, you will have to go back into the ThisWorkbook code sheet and use Find & Replace to make the appropriate changes.
Sample Workbook
I've made the fully operational example workbook available from my public DropBox.
Table_Collection_w_Sheetname.xlsb
¹ The CELL function can only retrieve the worksheet name of a saved workbook. If a workbook has not been saved then it has no filename and the CELL function will return an empty string when asked for the filename.
You can activate the Office Clipboard (arrow at bottom right of clipboard section on Ribbon Home Tab). Copy both ranges then use the Paste All command as shown below.
You would still need to fill down the sheet name in an extra column first though which can be done by double-clicking the fill handle.
Update
To get the same results with formulas try filling down this for the sheet name:
=IF(ROW()<=COUNTA(Sheet1!A:A),"Sheet1",IF(ROW()<COUNTA(Sheet1:Sheet2!A:A),"Sheet2",""))
and then fill down and across this formula for the values in the tables:
=IF(ROW()<=COUNTA(Sheet1!A:A),Sheet1!A2,IF(ROW()<COUNTA(Sheet1:Sheet2!A:A),INDEX(Sheet2!A:A,ROW()-COUNTA(Sheet1!A:A)+1),""))
lori_m made a really good contribution that I built upon by using Microsoft Excel Tables and structured references.
First make a column in your output table called RowID which contains the row number within the table and then use this to fill the data values.
=IF( INDIRECT("Table3[RowId]")<=ROWS(Table1)
,INDEX(Table1[column1],INDIRECT("Table3[RowId]"))
,INDEX(Table2[Column1],INDIRECT("Table3[RowId]")-ROWS(Table1)))
There is a detailed explanation of how this works on my blog as it was too long to include here.
A slight modification to Jeeped's code.
If you happen to use a similar approach, but with several tables (e.g. more than 10), then it will be rather cumbersome to attempt to manually add every name of every table. This is also a problem if you change names of the tables, since the names are hard-wired in VBA. To avoid additional work, consider this:
So, assume the following:
On each worksheet there is one or several tables, but they have similar structure.
There are only tables on worksheets - no other members of ListObjects collection are being present.
Every time we edit a table on a sheet, this will trigger an update in master table (table 3).
Then the Workbook_SheetChange Sub in the example above could look like the following:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim tbl As ListObject
For Each tbl In ActiveSheet.ListObjects
If Not Intersect(Target, tbl.Range.Offset(1, 0)) Is Nothing Then
On Error GoTo bm_Safe_Exit
Application.EnableEvents = False
Call update_Table
End If
Next tbl
bm_Safe_Exit:
Application.EnableEvents = True
End Sub
Edit. The second routine will then look like:
Private Sub update_Table()
Dim iTBL3rws As Long, rng As Range, rngOLDBDY As Range
Dim tbl As ListObject
Dim sht As Worksheet
iTBL3rws = 0
' consider all tables, excluding master table
For Each sht In ThisWorkbook.Worksheets
For Each tbl In sht.ListObjects
If tbl.Name <> "Table3" Then
iTBL3rws = iTBL3rws + tbl.DataBodyRange.Rows.Count
End If
Next tbl
Next sht
iTBL3rws = iTBL3rws + Sheet3.ListObjects("Table3").DataBodyRange.Cells(1, 1).Row - Sheet3.ListObjects("Table3").Range.Cells(1, 1).Row
With Sheet3.ListObjects("Table3")
Set rngOLDBDY = .DataBodyRange
.Resize .Range.Cells(1, 1).Resize(iTBL3rws, .DataBodyRange.Columns.Count)
If rngOLDBDY.Rows.Count > .DataBodyRange.Rows.Count Then
For Each rng In rngOLDBDY
If Intersect(rng, .DataBodyRange) Is Nothing Then
rng.Clear
End If
Next rng
End If
End With
End Sub
This routine differs from previous one by eliminating preprogrammed cases. When there is a change registered on active worksheet, then any table in this worksheet which is about to be changed will trigger update_Table procedure.
Im using this code/formula. works well for my needs only thing i would like to know is how do i make a better cell formula so i can use 3+ tables as a reference. currently im just nesting a bunch of iferror statements within the iferror
=IFERROR(INDEX(Table1, ROW([#Date])-ROW(Table3[#Headers]),COLUMN(A:A)),IFERROR( INDEX(Table2, ROW([#Date])-ROW(Table3[#Headers])-ROWS(Table1),COLUMN(A:A)), IFERROR(INDEX(Table4, ROW([#Date])-ROW(Table3[#Headers])-ROWS(Table2)-ROWS(Table1),COLUMN(A:A)),INDEX(Table5, ROW([#Date])-ROW(Table3[#Headers])-ROWS(Table2)-ROWS(Table1)-ROWS(Table4),COLUMN(A:A)))))
Im also using the

Moving Rows to another sheet in a workbook

I need Help!
I am not well versed in VBA or Macros but i cannot find any other way to accomplish what i need to do without using it.
I have a sheet which i will be using to track Purchase orders, and what i need to do is; when i have a row in sheet 1 (Purchase Orders) which has been recieved i.e. the date of receipt has been recorded in column H i need for the entire row to be cut and pasted into sheet 2 (Received orders).
The header takes up the first 7 rows the rows, so i need the macro to look at rows 8-54. Once the received items are removed from sheet 1, i need the row to also be deleted or preferably for the list to be sorted by column A moving the now empty row which has been cut from open for a future entry.
Any help would be greatly appreciated.
The "Record Macro" feature should be enough to do the task you describe.. In Excel 2007, go to the Developer tab in the Ribbon, and select "Record Macro", and perform exactly the steps you are describing. It will record the equivalent VBA code, which you can then execute - or tweak/modify.
I tested this out, here's one way to do it:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Dim receivedDate As Range, nextOpen As Range, isect As Range
Set receivedDate = Sheet1.Range("H8:H54")
Set isect = Application.Intersect(Target, receivedDate)
If Not (isect Is Nothing) And IsDate(Target) = True Then
Set nextOpen = Sheet2.Range("A1").End(xlDown).Offset(1, 0)
Target.EntireRow.Copy Destination:=nextOpen.EntireRow
Target.EntireRow.Delete
End If
Application.EnableEvents = True
End Sub
This would be pasted into the Sheet1 code. Any time a cell is changed on sheet1, the code checks to see if it's in the critical range that you specified. (H8:H54) If it is, it then checks to see if it's a date. If it is, it then copies the entire row, puts it in the next open row on Sheet2, and deletes the original row. The cells below it will get shifted up so there are no gaps.
Since the code functions on a cell changing event, it disables "Application.EnableEvents" in order to avoid a loop of changing a cell to call an event which changes a cell to call an event... etc.

Excel VBA Set Cell Formula Error

This one has me stumped. When I set the formula for a selected cell in a ListObject, if the ListColumn is empty, Excel fills the formula for the whole column, rather than just the Selection. I have duplicated this in a separate workbook.
Create a Table
Insert a five or so rows
Click on (select) one of the cells in the column
Execute the following code:
Sub setCellFormula()
Selection.Formula = "=myFormula()"
End Sub
Function myFormula() As Integer
myFormula = 1
End Function
Note that the whole column is filled with the numeral one
Delete the data in the cells
Enter any value in any of the cells
Select a cell other than the cell with a value in it
Re-execute step 4
Note that only the selected cell is filled with the numeral one
My experience is that, regardless of VBA, Tables behave as you have described in XL 2007, and Lists behave as you'd prefer in XL 2003. However in 2007 you can tell it to "stop creating calculated columns." So I'd guess it's not a code issue, but an issue due to different behavior between versions, or different settings for two instances of XL 2007.

Resources