I have a question regarding QTableWidget at PyQt4. Say I have a QTableWidget, where I want to have an event connected to a cell click using:
table.cellClicked.connect(cellClick)
then, cellClick function does its job. The thing is: some cells contain a button, like so (coordinates 0,0 as an example):
button = QtGui.QPushButton()
table.setCellWidget(0,0, button)
Now comes my problem. The presence of the button in a cell (which takes up the entire cell space) completely disables the cellClicked - the cell doesn't get clicked, the action can't be detected. But I do want my button to fill the cell.
So, in short, my question is - how can I select (click) a QTableWidget cell when there's a button inside it?
The simple answer would seem to be:
button.clicked.connect(cellClick)
But then how would you know the row/column of the cell that was clicked? So the next iteration might be:
button = QPushButton()
table.setCellWidget(row, column, button)
button.clicked.connect(
lambda *args, row=row, column=column: cellClick(row, column))
Which is okay if the table is static. But what if the rows and columns can be rearranged? The row/column cached in the button's clicked handler could be invalidated in that case. So the next iteration might be:
index = QtCore.QPersistentModelIndex(table.model().index(row, column))
button.clicked.connect(
lambda *args, index=index: cellClick(index.row(), index.column()))
(NB: you can connect other slots to the button's clicked signal, but the order they will be invoked in is undefined)
Related
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.
TIA for the help.
I am having a problem with the row for shapes returning an incorrect value. Here is my setup:
Each of the buttons on the right is named after the corresponding column header, the value in column A of the same row, and a number combining the row and column number (ex. Edit_555_10 would be the name of the first shape in cell G3). This ensures there are no duplicate names for when Application.Caller is used in the code.
All of the "Import Data" buttons run the same macro to import data. The Batch IDs have a corresponding sheet which the data is pasted to by using this code:
variable = Shapes(Application.Caller).TopLeftCell.Row
Worksheets(Range("A" & variable).Value).Range("A1").PasteSpecial xlPasteAll
However, during runtime, any and all "Import Data" buttons will return 3 when assigning a value to the variable. I have tested it with 3 different "Import Data" buttons in I3, I4, and I5 at the same time, each returning 3. During runtime I have moved the buttons to all areas of the sheet, and they all return row 3 every time. I have even moved all shapes out of row 3 and 4 so it wouldn't be possible to return 3, and it still returns 3.
All the "Edit" and "Create Tabs" buttons behave normally and have no problems. But for some reason, even without duplicate names, and setting the "Import Data" buttons down as far as row 8, they always return row 3 during runtime, which is obviously causing data to be put in the incorrect place.
Any ideas?
You have not posted the full code for the miscreant Shapes, but if you substitute your code for the Import Data Shapes with:
Dim s As Shape, r As Long, sname As String
sname = Application.Caller
Set s = ActiveSheet.Shapes(sname)
r = s.TopLeftCell.Row
MsgBox r
do you get a different value for each Shape ??
So I actually figured it out. But I am still confused as to why it is happening.
When pressing the "Edit" button, an option is to delete the entry. This grabs the row from Shapes(Application.Caller).TopLeftCell.Row and sets it to a variable. It then deletes the entry with Range("A" & variable & ":I" & variable).Delete xlShiftUp When doing this, the "Edit" and "Create Tabs" buttons are deleted no problem. However, the "Import Data" buttons are just scrunched up to 0 height and end up between I2 and I3. So every time I had deleted the entries in the past, it just kept adding to the stack of invisible buttons; meaning when I was running a test with the same names, there were 10 or so duplicate buttons for each unique name, and the row of one of the invisible buttons was returned.
My workaround for this is to cycle through the shapes, compare each Shapes(name).TopLeftCell.Row with Shapes(Application.Caller).TopLeftCell.Row and delete the shapes in the same row.
So while the problem is fixed, I am not sure why the "Edit" and "Create Tabs" buttons were deleted correctly each time while the "Import Data" buttons remained and were scrunched to be invisible. If anyone has thoughts I would be interested.
i have a matrix (6*6) in sheet1 like below, and Inside its cells, I've written the countifs() formula for the number of correct items, Now i want a Listbox for Returning the name of items from sheet2
for example: by right-click in the cell (K10) Listbox show the name (USER 1)
sheet2.database;
matrix:
Create an ActiveX listbox on the page keeping the data (your matrix);
Access its Properties and set the ColumnCount to 6;
Set its ListFilRange as your 'matrix' address (A2:F7, for instance);
Now access its code (View Code in Developer Tab) and select your list box name from the upper left corner;
Then select ListBox_Change event from the top left corner;
Fill the next code inside it (supposing that your listbox name would be ListBox1):
Private Sub ListBox1_Change()
MsgBox ListBox1.Value
End Sub
Take care to exit Design Mode (from Developer Tab).
Now, changing the line in the listbox, a message with the first column content, for the selected range will appear...
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
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)