Determine if a cell has a button over/in it - excel

How can I use VBA in excel to determine if a particular cell has one or more buttons in it?
I have a spreadsheet with 50 rows. Each row has a series of data entry fields, and then in column S, there are two buttons to do some calculations based on that data. The buttons all run the same macro, and check which row they are in to determine which row to act on.
Periodically, a user will delete one or more rows, once that data is no longer relevant. When this happens, I want to insert the same number of new rows onto the bottom, to ensure I always have 50 data rows. I can copy and paste e.g. S49 to S50, and the buttons will appear in S50, and work as intended, but I can't tell if one row or n rows have been deleted, so I don't know whether to start copying from S49, S48, or Sn.
If I write a for n = 1 to 50 loop to cycle through each row of column S, how can I check to see if there is a button present in cell Sn?
Edit: with #Michael's answer below I realised I needed to loop through the buttons, not the cells. While Michael's solution correctly answers my question as asked, in my particular application it is a little simpler:
Dim lastBtnRow as Integer
Dim b As Button
lastBtnRow = 0
For Each b In ActiveSheet.Buttons
If b.TopLeftCell.Row > lastBtnRow Then lastBtnRow = b.TopLeftCell.Row
Next
Then it's just a matter of copying and pasting S:lastBtnRow to S:lastBtnRow+1 through S:50

Cells don't have a property that identifies objects sitting in them, but objects have properties about where they're located.
You need to instead build a loop through all buttons on the sheet and test where they're located. If you're dealing with Form Buttons (and not ActiveX Buttons), then this is a simple loop that will count the number of buttons located in the cell you currently have selected:
Sub ButtonLoop()
Dim b As Button, i As Long
For Each b In ActiveSheet.Buttons
If b.TopLeftCell.Address = Selection.Address Then i = i + 1
Next
MsgBox i
End Sub
If you want to check the number of buttons in each cell, you can either write an outer loop that loops through each cell, and then use the loop above as the inner loop to check for buttons in the current cell in the outer loop; or more efficiently use a data structure such as a dictionary to increment a counter for each cell address encountered.

Related

Excel Bar/Column graph showing blank cells

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

many buttons (userform) control a single macro opens a different userforms depending on selection

I have created small excel form for updating a database. works great, though staff are doing odd things and have to replace the excel weekly with a clean version. So I am thinking of creating userforms that update the excel sheet(DutySelection).
I have many buttons (userform) A4:A31 that will control a single macro which opens 3 different userforms depending on B4:B31 dropdown list selection
Currently My code only works from B4 no matter which button i click.
EG: B4 selection Start, the Start form opens. B6 selection Finish, the Start form opens
Sub Duty()
If Sheets("DutySelection").Range("B4,B31") = "Start" Then
frmStart.Show
ElseIf Sheets("DutySelection").Range("B4,B31") = "Duty Type" Then
ReportUpdate.Show
Else: Sheets("DutySelection").Range("B4,B31") = "Finish" 'Then
frmFinish.Show
End If
End Sub
I am thinking that i am missing a line or two but just can not find what i am needing online
Sheet.Range("B4,B31") doesn't return what you think it does: it returns a composite range consisting of 2 areas, area 1 being cell B4 and area 2 being cell B31. I.e., the same as you would get when you select cell B4, then Ctrl-Clicked cell B31.
I think you meant "B4:B31", but this also returns something else: an array filled with (the values of) all cells in the range B4 to B31. You cannot compare it with a text string just like that.
What you do want here is to loop through all cells between B4 and B31, then compare their values to the texts you're interested in.
Another issue is that your code only ever acts upon the first text it matches. So, if cell B4 contains "Start", then there's no way the ElseIf will ever be evaluated, not even if cell B5 contains "Duty Type". The best way to deal with this depends on how you get those texts in column B on your sheet.
If I understood you correctly, you have a button in each row next to column B and clicking it invokes the action selected in column B in the corresponsing row, right?
In that case I would suggest that you place 3 buttons next to each other that invoke 3 different macros.
Greetings,
vat

Copy data from rows in Worksheet to another worksheet based on quantity selected

I'm a Graphic Designer by trade and while I know basic Excel this is getting into foreign territory for me.
So I have been tasked with putting together our company's product price list (with +800+ products that are all broken into categories/tabs) into a version that users can select quantities per item and see a running list on the page of their total. No problem, I have this figured out.
Each item has a column for a part number, description, price, and quantity...with the quantity defaulted to zero. I need to get this so that if user wants an item they can change the quantity to whatever they want, and press the add/update purchase order button and have this values of that item's row add to a separate sheet. Additionally, if they were to change value to zero and press the button again, it would remove the item.
So right now each worksheet in the book is like this:
Ideally it would be great to just have one macro/button that once a user enters whatever quantity of item(s) they want, it would copy the row (but as column-order G B C F H) to a sheet in that book named "Purchase Order". If the user changes a quantity of an item to 0, and reclicks the button, it removes said row from the other worksheet. This would need to work through the 30+ other tabs, and append the rows below already entered values as the user continues to add products to their purchase order (starting on B22 on the Purchase Order worksheet)
My concerns are adding/deleting products in the future/other staff having to update this and not needing to do crazy changes to the macro if, say I need to add 5 more products to a tab.
I will take ANY help, insight, direction, or guidance anyone can shed on this issue. I greatly appreciate it!
Ok I don't know if I understood well your question, but here's what I'm thinking: You could create 2 buttons on the Worksheet, one with a big "+" on it, the other with a "-" and link them to 2 different subroutines. Those items could be positioned at the end or beginning of a product row. Right click on the buttons, go to properties and be sure that those are dependant on cell position and size. The "+" one could check if your "Purchase Order" is currently open (in case it isn't, could create a new one), and add all the content on the same row in the new worksheet (you can use Application.Caller to address the buttons and .Top / .Left to address its row/column). Naturally you'll want to search if the name of the product is already in the "Purchase Order", in that case increase quantity, otherwise copy all the row. Same thing with the "-" button eliminating the product row if quantity is 0. The fun part: you can create 2 buttons and the macros, and then copy the buttons for each row, since they each address rows on their right/left!
EDIT:
Sub Add()
Dim Button_cell_row As Integer
Dim ws As Worksheet, Target As Worksheet
Dim ProductName As String
Dim i As Integer
Set ws = ThisWorkbook.ActiveSheet
Set Target = Workbooks("Purchase_Order.xls").worksheets(1) ' Here you need to choose the file (Workbook) and the Worksheet (Tab) you want
Button_cell_row = ws.Shapes(Application.Caller).TopLeftCell.Row
Do Until IsEmpty(Target.Cells(i,4)) ' for example your product name is in the 4th column
If Target.Cells(i,4) = ProductName Then
Target.Cells(i,7) = cInt(Target.Cells(i,7)) + 1 ' say that the quantity is stored in the 7th cell
Exit Do
End If
i = i+1
Loop
If IsEmpty(Target.Cells(i,4)) Then ' meaning that the whole sheet has been searched but there's no product with a similar name on the whole "i" loop-range
' in this case "i" is now the index of your first empty row
ws.Range(Cells(Button_cell_row,3), Cells(Button_cell_row,8)).Copy _ ' For example you need to copy from 3rd column to 8th column...
Destination:=Target.Cells(i,1) ' ... on the first empty row, from column 1
End If
Set ws = Nothing
Set Target = Nothing
End Sub
This wasn't tested and may not work. But you get the idea :) For the "Remove" Sub, it is gonna be very similar.

How to select next item in Data Validation list using VBA

I have a Data Validation list with dates in it. When you change the date it effects what data is shown in the rest of the worksheet.
I would like to to create 2 command buttons,
"Next" - when clicked will move to the next date in the list, when it reaches the end of the list it goes back to the beginning of the list
"Previous" - when clicked will move to the previous date in the list, when it reaches the beginning of the list it will go to the end of the list
Is this possible?
I did look at List Box and Combo Box but got highly confused with the coding!!!
Any help would be great!
You have to distinguish 3 cases:
data validation with in-line list elements (e.g. Source = "1;2;3;4;5")
data validation with list elements in a range
List/Combo box
Case 1 is maybe the most difficult, because you can access the list elements only in a string and have to split them up into an array, get the index of the current selection and move the index with two buttones in order to get the wanted result
Case 2 is a bit simpler, but again you need to somehow keep track of the current position within the range defining your dates
Case 3 is maybe the most easiest to implement ... but still requires a certain coding effort, like
load list of dates into the list/combo box before first display (OnLoad or OnActivate
create code for the UP and DOWN buttons to increment/decrement the list box index, with automatic wrap-around
I suggest a 4th case to you ... use an ActiveX Spin Button ... this provides up and down functions in one element:
create your list of dates in a vertical named range DateList
reserve one more cell for the index of the Spin Button and name it DateIndex
using Developer ribbon, insert an ActiveX Spin Button (default name is SpinButton1)
set the LinkedCell property in SpinButton1 (be sure to be in Developer / Design mode; right click the Spin button and select "properties") to DateIndex
create the following code (still in design mode, right click the SpinButton and select "view code")
code
Private Sub SpinButton1_SpinDown()
If SpinButton1 = 0 Then
SpinButton1 = Range("DateList").Rows.Count
End If
End Sub
Private Sub SpinButton1_SpinUp()
If SpinButton1 = Range("DateList").Rows.Count + 1 Then
SpinButton1 = 1
End If
End Sub
At the cell where you want to display the selected date, enter formula =INDEX(DateList,DateIndex)
since you are working with named ranges, DateList and DateIndex may be in a different sheet (even a hidden one) than the user sheet.
if you want to create a copy of the currently chosen date at the cell where the user has currently put the cursor, add the following statement to the end of the SpinDown/Up Sub's (after End If: Selection = Range("DateList").Cells(SpinButton1, 1)

How to randomize Excel rows

How can I randomize lots of rows in Excel?
For example I have an excel sheet with data in 3 rows.
1 A dataA
2 B dataB
3 C dataC
I want to randomize the row order. For example
2 B dataB
1 A dataA
3 C dataC
I could make a new column and fill it with random numbers using =RAND() and sort based on that column.
But is this the best way to do it? The RAND equation will provide up to a million random numbers and I have a quarter of a million rows so it seems like it would work.
Thanks
I searched for a bit and while this answer about randomizing columns is close it seems like way overkill.
Perhaps the whole column full of random numbers is not the best way to do it, but it seems like probably the most practical as #mariusnn mentioned.
On that note, this stomped me for a while with Office 2010, and while generally answers like the one in lifehacker work,I just wanted to share an extra step required for the numbers to be unique:
Create a new column next to the list that you're going to randomize
Type in =rand() in the first cell of the new column - this will generate a random number between 0 and 1
Fill the column with that formula. The easiest way to do this may be to:
go down along the new column up until the last cell that you want to randomize
hold down Shift and click on the last cell
press Ctrl+D
Now you should have a column of identical numbers, even though they are all generated randomly.
The trick here is to recalculate them! Go to the Formulas tab and then click on Calculate Now (or press F9).
Now all the numbers in the column will be actually generated randomly.
Go to the Home tab and click on Sort & Filter. Choose whichever order you want (Smallest to Largest or Largest to Smallest) - whichever one will give you a random order with respect to the original order. Then click OK when the Sort Warning prompts you to Expand the selection.
Your list should be randomized now! You can get rid of the column of random numbers if you want.
I usually do as you describe:
Add a separate column with a random value (=RAND()) and then perform a sort on that column.
Might be more complex and prettyer ways (using macros etc), but this is fast enough and simple enough for me.
Here's a macro that allows you to shuffle selected cells in a column:
Option Explicit
Sub ShuffleSelectedCells()
'Do nothing if selecting only one cell
If Selection.Cells.Count = 1 Then Exit Sub
'Save selected cells to array
Dim CellData() As Variant
CellData = Selection.Value
'Shuffle the array
ShuffleArrayInPlace CellData
'Output array to spreadsheet
Selection.Value = CellData
End Sub
Sub ShuffleArrayInPlace(InArray() As Variant)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' ShuffleArrayInPlace
' This shuffles InArray to random order, randomized in place.
' Source: http://www.cpearson.com/excel/ShuffleArray.aspx
' Modified by Tom Doan to work with Selection.Value two-dimensional arrays.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim J As Long, _
N As Long, _
Temp As Variant
'Randomize
For N = LBound(InArray) To UBound(InArray)
J = CLng(((UBound(InArray) - N) * Rnd) + N)
If J <> N Then
Temp = InArray(N, 1)
InArray(N, 1) = InArray(J, 1)
InArray(J, 1) = Temp
End If
Next N
End Sub
You can read the comments to see what the macro is doing. Here's how to install the macro:
Open the VBA editor (Alt + F11).
Right-click on "ThisWorkbook" under your currently open spreadsheet (listed in parentheses after "VBAProject") and select Insert / Module.
Paste the code above and save the spreadsheet.
Now you can assign the "ShuffleSelectedCells" macro to an icon or hotkey to quickly randomize your selected rows (keep in mind that you can only select one column of rows).
Use Excel Online (Google Sheets).. And install Power Tools for Google Sheets.. Then in Google Sheets go to Addons tab and start Power Tools. Then choose Randomize from Power Tools menu. Select Shuffle. Then select choices of your test in excel sheet. Then select Cells in each row and click Shuffle from Power Tools menu. This will shuffle each row's selected cells independently from one another.
For example this is our data set.
Then type this formular and add it B1 tO B9 cell.
Now you can go to Data tab, and select Sort smallest to largest or Sort largest to smallest as you need.
***Then there is a popped dialog, and check Expand the selection option. And click Sort.
Data range has been shuffled by rows randomly.Then you can remove the formula cells.(B column)

Resources