I would like to display the SUM of visible cells from column D in a Userform Label, and have it update automatically as the spreadsheet gets filtered.
I have this code
Private Sub SUM_click()
Me.SUM.caption = range ("AA2")
End Sub
This is problematic.
I use a SUM formula in cell AA2.
I’d like to avoid using formulas in any cells, if possible.
The return value isn’t excluding rows which are hidden.
It displays the value after I click on the label.
I want it to display automatically.
Automate display of SubTotals in UserForm
This reveals being not as trivial as it may seem.
Of course you might profit from a sheet's Worksheet_Calculate event
to get subtotals (of visible cells) by means of VBA whenever the (enabled) calculation processes a new result.
This is effective when filtering and/or when changing values, whereas the Worksheet_Change event wouldn't cover filtering.
Private Sub Worksheet_Calculate()
Dim subtotal As Double
subtotal = Sheet1.Evaluate("=Subtotal(109,D:D)") ' argument 109 sums up only visible cells!
Debug.Print "Subtotal: " & Format(subtotal, "0.00") ' display in VB Editor's immediate window
End Sub
... but there's no synchronized event to refresh a label in the Userform's code.
So I suggest the following tricky workaround profiting from the ListBox property RowSource:
add the subtotal formula =SUBTOTAL(109,D:D) to cell AA2
instead of a label ... let a listbox do the display binding it to a .RowSource; whenever a subtotal gets changed this will be reflected in the only list value of the bound listbox.
Defining the appearance via .SpecialEffect = fmSpecialEffectFlat makes the Listbox appear nearly like a label. Small beauty mistake: the background color can't be modified
UserForm code example
Private Sub UserForm_Initialize()
With Me.ListBox1 ' << rename control to any other name
.ColumnCount = 1 ' define columncount
.ColumnWidths = .Width - 8 ' play around with small sizes to avoid scroll bars
.Height = 12
.RowSource = "Sheet1!AA2" ' reference to cell containing subtotal formula
.SpecialEffect = fmSpecialEffectFlat
.ListIndex = 0 ' activate first (and only) record
End With
Tip
To enable concurrent actions like editing or filtering in the table, you need to show the UserForm modeless, e.g. via
With New UserForm1
.Show vbModeless
End With
Alternative suggested in comment
Thx to #norie's hint, it would also be possible to profit from a textbox'es .ControlSource property (having more ressemblance to a label) without the need of further cosmetics. Nevertheless this promising approach needs further (or even more complicated?) steps: a textbox allows inputs which would overwrite the initial formula in the referred cell which isn't wanted.
Possible ways out I won't develop here: prevent key-event entries (have a look into other SO posts) or use the TextBox1_Change event to rewrite the formula each time the textbox got changed (doubtful), place a blocking control over the textbox etc.
Related
I have a fairly large Excel sheet, but I'm only interested in a certain amount of columns at a time. Now some columns contain quite a long text requiring a large cell height. After hiding these columns I wanted to set the cell heights of the visible rows rescaled to the optimum height for better browsing my sheet.
How can these be achieved in Excel either out-of-the-box or with a special rescale VBA Macro?
I'm not an Excel specialist, so any help is welcome her.
You probably have set the cells having long text to "Wrap Text". If you reset this, text is shown in one row now matter how long the content is.
If you do this for all columns that are hidden, Excel is able to calculate the needed height properly:
Sub setheight()
Dim col As Range
For Each col In ActiveSheet.UsedRange.Columns
' Set WrapText to false if column is hidden
col.WrapText = Not col.Hidden
Next
ActiveSheet.UsedRange.EntireRow.AutoFit
End Sub
Unfortunately there is no easy way to trigger this automatically as there is no event that is fired when a column is shown/hidden (yes, there is one, but this is related to the ribbon. If you want to have a look, see Trigger Event in Excel VBA when Row or Column is hidden)
There are numerous ways to trigger the sub, you could for example create a keyboard shortcut to the macro. An alternative that I use sometimes is to create a trigger on DoubleClick on a specific cell or range. I would suggest to run the code when the top row is Double-Clicked. Put the following code into the Worksheet-module
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Target.Row > 1 Then Exit Sub
setheight
Cancel = True
End Sub
I'm attempting to hide groups of rows with a toggle button. In this instance, Rows 15 through 20, 22 through 25, 27 and finally 30 through 32.
The code I have so far works as intended.
Private Sub ToggleButton5_Click()
Dim xAddress As String
xAddress = ("15:20")
If ToggleButton5.Value Then
Application.ActiveSheet.Rows(xAddress).Hidden = True
ToggleButton5.Caption = "Show Assets"
Else
Application.ActiveSheet.Rows(xAddress).Hidden = False
ToggleButton5.Caption = "Hide Assets"
End If
End Sub
How do I add multiple groups to this row?
I tried
xAddress = ("15:20,22:25")
xAddress = "15:20,22:25"
xAddress = ("15:20 And 22:25")
and I tried individually
xAddress = ("15,16,17,18,19,20,22,23,24,25")
This last line works somewhat but runs into errors if more than maybe six row numbers are cited (going from memory on past attempts).
If you need a "toggle", then consider the implementation of a "radio-button-logic". It is either on or off, thus if it is not Hidden it should be Hidden and vice versa. Usually it is 1 line only:
Sub ToggleRowsVisibility()
With ThisWorkbook.Worksheets(1).Range("15:20,22:25")
.EntireRow.Hidden = Not .EntireRow.Hidden
End With
End Sub
In the case of the code, it can be outside the If condition:
Application.ActiveSheet.Rows(xAddress).Hidden = ToggleButton5.Value
Use Range instead of Rows.
Application.ActiveSheet.Range(xAddress).Hidden = True
If you are using Range, make sure that the row reference is in the form row:row, e.g. 1:1, 2:2, 3:3 and not 1, 2, 3.
I generally steer clear of Rows. For example,
Debug.Print Rows("1,2,3").Address
returns
$123:$123
Not what you expect, right?
Requirements:
To hide/unhide a multiple selection (i.e. non-contiguous or multiple areas) of rows by the click of a button.
Target rows: [15:20], [22:25], [27] and [30:32] (row 27 included to show a mixed of rows combination).
Non VBA Solution:
This can be achieved without VBA. Use the Range.Group method (Excel) manually to group the rows. Unfortunately, this method cannot be applied at once to a multiple selection, so you’ll have to apply the method to each range of contiguous rows separately.
Select the first range of rows to be grouped (i.e. [15:20])
In the Excel menu, click the [Data] tab then in the [Outline] group, click the [Group] option in the [Group] dropdown menu.
The rows selected are now grouped with a button beside the rows heading. Use this button to toggle the visibility of the respective grouped rows.
Repeat the action for the remaining group of rows.
The advantages of this method compared with the VBA method are:
The grouped rows are fixed, even if new rows are added or deleted. With the VBA method the rows are “hard-coded” and will lose focus when rows are inserted\deleted.
The visibility of the Group of rows can be toggle all at once using the buttons located at the top\left angle, i.e. 1 to hide and 2 to unhide.
The visibility of the Group of rows can be toggle independently from each other using each group's button located in the rows heading. Vba would required either independent buttons or additional variables.
VBA Solution:
If you must use VBA then I suggest to use the correct syntax for multiple selection:
For a single row use: Rows(15).Select or Range("15:15").Select
For a range of contiguous rows use: .Rows("15:20").Select or Range("15:20").Select
For multiple selection of rows use the Range method as the Rows method doesn't work with multiple areas and when applied to a multiple selection returns only the rows of the first area.
For multiple selection of single rows use: .Range("30:30,32:32,34:34,36:36,39:39").Select
For multiple selection of non-contiguous rows use: .Range("15:20,22:25,27:27").Select
Proposed VBA Solution:
Following the above your code should be:
Private Sub ToggleButton5_Click()
Dim xAddress As String
xAddress = "15:20,22:25,27:27,30:32" 'Update as required
With ToggleButton1
Me.Range(xAddress).Rows.Hidden = .Value
.Caption = IIf(.Value, "Show Assets", "Hide Assets")
End With
End Sub
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.
I need to, via VBA, make a note of the values (in a cell) selected on a slicer.
So in a slicer with options Option A, Option B, Option C I want some code to trigger that puts Option A|Option C into a cell, for example.
How do I trigger that code to start with? I can find no way to begin that event, I've tried right-click, assign-macro / NEW, but that just triggers an event when you CLICK and doesn't let you update the slicer.
While there are no ways I can think of to directly call an event tied to a slicer; I have thought about it some and realized that you can handle your event in the Worksheet_Calculate event if you include a volatile function (function that gets recalculated after each action) such as =NOW() or =RAND() hidden somewhere on the worksheet (either in a hidden column, or change the font color to blend in so as to be invisible to the user).
Every time you change the slicer parameters, that function will be recalculated which will call the Worksheet_Calculate event. In that event you can retrieve selected items and display them in a cell.
Open the VB Editor and paste the following code into the worksheet code of whichever sheet you want this functionality on:
Private Sub Worksheet_Calculate()
Dim item As SlicerItem
Dim strOutput As String
'Get all selected items from the first slicer
For Each item In ActiveWorkbook.SlicerCaches(1).SlicerItems
If item.Selected = True Then
strOutput = item.Caption & "|" & strOutput
End If
Next item
'If we don't pause macros when we modify the
'cell value, this function will get called again and
'start an infinite loop, so we disable macros for
'the update of the cell value
Application.EnableEvents = False
'Update a cell value with the slicer output
Me.Range("G1").Value = "|" & strOutput
Application.EnableEvents = True
End Sub
In my test workbook I added a table containing information about authors and books (author name, book title, price, rating, etc.) and I added a slicer for Author (this is the slicer that ActiveWorkbook.SlicerCaches(1).SlicerItems refers to).
The above code will output the selected options in the Author slicer to cell G1 (which you may change to suit your needs). I placed the =NOW() function in cell L2 and made the text white so as to appear invisible to the user.
The result of selecting items within the slicer is cell G1 looking like:
|Clive Barker|Stephen King|
Assuming those two authors were selected.
A snap of output after the code executes when I selected H.P. Lovecraft and Clive Barker.
This will work for whatever is selected, and of course you can change the referenced slicer as needed. Be sure to keep the Application.EnableEvents = False and Application.EnableEvents = True trigger around the part of the code that modifies the cell value within the Worksheet_Calculate event, otherwise the code will enter an infinite loop.
I added the "|" to the beginning of the string to add to cell G1 to make it look better (looked awkward with a trailing |) but you could just as well trim the trailing | off by using Me.Range("G1").Value = Left(strOutput, Len(strOutput) - 1) instead of Me.Range("G1").Value = "|" & strOutput.
This would make the output look like this:
H.P. Lovecraft|Clive Barker
Again assuming those two authors were selected in the slicer. If you selected three, you would get:
H.P. Lovecraft|Stephen King|Clive Barker
Etc.
I designing a form in Excel 2010 to enter the date each task in a list is completed by checking a "YES" form control box. I have assigned the cell underneath each form control box as the cell link for that box and I want the macro to start in that cell and then offset to the right 6 cells and put today's date. The offset function isn't hard but I don't know how to get the macro to start in the linked cell because clicking the box doesn't select the cell so the active cell object doesn't seem to work for me. Because the row is different for each box the range object needs to change depending upon which box I check otherwise I am back to writing code for every row.
The second problem is if the box is already checked, I don't want it to change to today every time I open the workbook. Once I check it, it should put in the date and then stay the same from then on. This is probably simple but I can't seem to get it right. Any ideas?
Using the object and event navigation dropdowns above the code editor, add a CheckBox Changed event for each of your checkboxes in order to set the active cell to the appropriate one each time a checkbox is clicked. You can also perform your validation ensuring the checkbox is currently selected at this time.
Private Sub CheckBox1_Click()
If Me.CheckBox1.Value = True Then
ActiveSheet.Cells(x, y).Select
End If
End Sub
Where x and y are the appropriate values for your implementation. If you have a lot of checkboxes, I would recommend encapsulating this logic into a common function and calling it from the various checkbox changed events to clean up your code, but this method will get you the correct behavior.
Assign this macro (in a regular module) to all your checkboxes:
Sub CheckBoxUpdated()
Dim cb As CheckBox
On Error Resume Next
Set cb = ActiveSheet.DrawingObjects(Application.Caller)
On Error GoTo 0
If Not cb Is Nothing Then
If cb.Value = 1 Then
ActiveSheet.Range(cb.LinkedCell).Offset(0, 6).Value = Date
End If
End If
End Sub