QlistWidget how to select items one by one - python-3.x

i want open images sequence and i load all images in a list; i want when i click to a button called next the current list index change from 1 to 2 than from 2 to 3 ...etc right the last frame.
this code always give me the last frame
self.nextbtn.clicked.connect(self.changeitem)
def changeitem(self):
self.listWidget.count()
count_list = self.listWidget.count()
self.listWidget.setCurrentRow(0)
for i in range(count_list):
self.listWidget.setCurrentRow(i+1)

solution :
def changeitem(self):
cur = self.listWidget.currentRow()
self.listWidget.setCurrentRow(cur+1)

Related

Issues when freezing wx.Grid columns with FreezeTo: bad cell position in grid, and HOME keyboard key behavior

Context
I have put in place column freezing in a wx.grid.Grid, using FreezeTo method.
def __init__(self, parent):
# relevant lines
self.grid = wx.grid.Grid(self.sbox_grid, size=(1000, 800))
self.grid.CreateGrid(self.row_number, self.col_number)
self.grid.FreezeTo(0, self.frozen_column_number)
The freezing by itself works well, as soon as I keep the standard label renderer (*).
The first few columns I have frozen always stay visible, and moving the horizontal scrollbar by hand is also ok.
(*) I was initially using the GridWithLabelRenderersMixin of wx.lib.mixins.gridlabelrenderer, but it totally breaks consistency between column label width and column width. Anyway I can deal with the standard renderer, so it is not really a problem.
I faced several issues, now all solved and detailed below.
Capture the cell position for frozen columns: cells or labels (SOLVED)
For cells, the window can be captured with GetFrozenColGridWindow.
So mouseover can be done simply with:
if widget == self.grid.GetFrozenColGridWindow():
(x, y) = self.grid.CalcUnscrolledPosition(event.GetX(), event.GetY())
row = self.grid.YToRow(y)
col = self.grid.XToCol(x)
# do whatever your want with row, col
For labels, the window exists but is NOT accessible with a method.
With a GetChildren on the grid, I have found that it is the last of the list (corresponding to the latest defined).
So it is not very reliable, but a relatively good placeholder for the missing GetGridFrozenColLabelWindow method.
wlist = self.grid.GetChildren()
frozen_col_label_window = wlist[-1]
if widget == frozen_col_label_window:
x = event.GetX()
y = event.GetY()
col = self.grid.XToCol(x, y)
# do stuff with col
Mouse position from non-frozen columns (labels or cells) is shifted (SOLVED)
The effective position for non-frozen columns labels or cells is shifted from the total width of all the frozen columns.
This one is easily handled by a shift in position, computed before calls to YToRow or XToCol methods.
The following code shows the position corrections:
class Report(wx.Panel):
def _freeze_x_shit(self):
"""Returns the horizontal position offset induced by columns freeze"""
offset = 0
for col in range(self.frozen_column_number):
offset += self.grid.GetColSize(col)
return offset
def on_mouse_over(self, event):
widget = event.GetEventObject()
# grid header
if widget == self.grid.GetGridColLabelWindow():
x = event.GetX()
y = event.GetY()
x += self._freeze_x_shit() # <-- position correction here
col = self.grid.XToCol(x, y)
# do whatever grid processing using col value
# grid cells
elif widget == self.grid.GetGridWindow():
(x, y) = self.grid.CalcUnscrolledPosition(event.GetX(), event.GetY())
x += self._freeze_x_shit() # <-- and also here
row = self.grid.YToRow(y)
col = self.grid.XToCol(x)
# do whatever grid cell processing using row and col values
event.Skip()
HOME keyboard key not working as intended (SOLVED)
I generally use the HOME key to immediately go at the utmost left of the grid, and the END key to go far right. This is the normal behavior with a non-frozen grid.
The END key does its jobs, but not the HOME key.
When pushing HOME on any grid cell, I got two effects:
the selected cell becomes the first column: this is OK
but the scrollbar position is not changed at all: I would expect the scroll position to be fully left
I have corrected it by a simple remapping using EVT_KEY_DOWN event:
def __init__(self, parent):
self.grid.Bind(wx.EVT_KEY_DOWN, self.on_key_event)
def on_key_event(self, event):
"""Remap the HOME key so it scrolls the grid to the left, as it did without the frozen columns
:param event: wx.EVT_KEY_DOWN event on the grid
:return:
"""
key_code = event.GetKeyCode()
if key_code == wx.WXK_HOME:
self.grid.Scroll(0, -1)
event.Skip()
my 3 issues concerning column Freeze in a grid are now solved. I have edited my initial post with my solutions.

Getting index of every optionmenu from multiple optionmenus

Given multiple optionmenu with stringvar trace,how to get the index of every option menu's first option?
self.path_list=[]
def load(self,inputt):
for i in range(0,self.x):
self.variable = StringVar(root)
self.variable.set("abc")
self.variable.trace('w',partial(self.rb_mode,i=i))
w=OptionMenu(root,self.variable,"Hello","World")
w.grid(row=i+100,column=1)
self.opt_menu.append(self.variable.get())
def rb_mode(self,*args,inx):
self.variable.get.__setitem__(i)
if(self.variable.get()=="Hello"):
do something
Here as shown,the drop down box index should be mapped with the corresponding file path
There are multiple dynamic drop down lists. Each drop down box index should be mapped with the corresponding path.
Am getting the output like:
World(printing 2 times which is the 2nd option,on clicking the 1st option)

How to get random item every time a key is pressed?

My program should get a random name from a list every time a key is pressed, and then delete that name from the list. With the code I have now a random name is selected, but the list is emptied completely.
I have tried both a for and a while loop, but I'm uncertain as what to use in this situation.
x = win.getKey()
while len(members) > 0:
if x:
name = random.choice(members)
members.remove(name)
As mentioned above I want to draw a random name and delete that name from the list every time a key is pressed, until the list is empty.
You'll need to put your key prompt in the loop to cause the loop to pause and wait for input for each element, otherwise the loop will run to completion and instantly empty the entire list.
import random
members = [1,2,3,4,5,6,7,8,9]
while members:
if win.getKey():
choice = random.choice(members)
members.remove(choice)
print(choice)
Output:
8
4
2
3
7
6
9
5
1
If the list is very large, remove is a slow linear operation that needs to inspect each element in the list one by one to find a match. Consider using a fast (amortized) constant time pop operation which uses an index to find the target element without searching:
import random
members = [1,2,3,4,5,6,7,8,9]
while members:
if win.getKey():
choice = members.pop(random.randint(0, len(members) - 1))
print(choice)

Tkinter - bug that shows forgotten labels on corner of screen?

Screenshot: http://puu.sh/wiuP7/957bca09ea.png
My code is attached and roughly does the following:
Grabs data from SQL server
For each product it lists all printed products and all scanned products. There could be multiple prints (see P039 on the bottom). It does this using labels.
Every 5 seconds it runs using after(), and when it does it uses grid_forget() to remove all of the "old" labels.. it runs the for loop and the updated labels are there, 5 seconds later with no blinking or any noticeable interruption.
The issue: After an undetermined amount of time it will no longer loop correctly. It will not show any labels and there will be what looks like a pile of labels sitting on the top left corner of my window. However in the screenshot, the pile in the corner is actually because of an instance of the script that I had closed, so the open instance (with the labels all there correctly) is good, so far. The bug in the corner is from a previously closed instance. Nonetheless, if I haven't had opened it again, you would see nothing instead of the list of products and numbers.
This always occurs, no matter what. I have learned that increasing the after() delay also increases the time it takes for it to happen. For example, it happened in roughly 20-30 mins when the delay was 2000 ms, but it took hours (not sure how long, at least 5) when the delay was 10000 ms.
Manually running the for loop at the top of the function using a different function and a button does not fix it.
I am using tkinter on python 3.6.1. I have seen it on windows 10 and Windows Server 2008 r2 through remote desktop
The code of interest is below, this does all the work with updating and such. My entire code is long, but here are my includes as well:
EDIT: It appears that if the program is minimized it will not occur.
import time
import threading
import logging
import datetime
import tkinter as tk
from tkinter import *
import tkinter.scrolledtext as ScrolledText
from tkinter.ttk import Separator, Style
from collections import Counter
import pypyodbc
And here is the updating function:
def build_productionrpt(self, LotDate, Shift):
for label in self.grid_slaves():
if int(label.grid_info()["column"]) >= 0 and int(label.grid_info()["row"]) >= 3:
label.grid_forget()
self.products = []
self.r = 3
self.c = 0
# Get all printed labels for current date and shift
labels = ProductionReport.LabelsByShift(LotDate,Shift,option="Labels")
# Only look at the DatePrinted and the ProductID
product_batch_sets = [(record[0], record[1]) for record in labels]
# Sort so that we only get unique values, otherwise there would be a duplicate for every unit.
product_batch_unique_sets = list(set(product_batch_sets))
# Get all units for each batch and sort by PrintedDate, UnitNumber, and RFIDSerial. Currently we are not using RFIDSerial for anything in this report
batch_unit_sets = [(record[0], record[6], record[5]) for record in labels]
# Sort so that we only get unique values, otherwise there would be a duplicate for every unit.
batch_unit_unique_sets = list(set(batch_unit_sets))
# Get all scanned labels for current date and shift, sort by PrintedDate, UnitNumber, and RFIDSerial. Currently we are not using RFIDSerial for anything in this report
units_scanned_sets = [(record[0], record[6], record[5]) for record in ProductionReport.LabelsByShift(LotDate,Shift,option="Scanned")]
# Remove all duplicate scans
units_scanned_unique_sets = list(set(units_scanned_sets))
# Begin by going through each row
for record in product_batch_unique_sets:
# Get each unique product from the list, if it's already in self.products then it's been Labeled and we can move on
if record[1] not in self.products:
# Add to self.products so we don't label it twice
self.products.append(record[1])
# Label it on the GUI, using row 3 as the first row.
# First, make sure the labels haven't gotten too far. If it has, reset the row count and change the column
if self.r > 35:
self.c = 1
self.r = 2
elif self.r > 35 and self.c == 1:
self.c = 2
self.r = 2
Label(self, text=record[1], font=("Helvetica", 16, "bold")).grid(row=self.r, column=self.c, sticky="nw", rowspan=3)
# Add 3 rows to r, to make room for the Product label.
self.r+=3
# Set the current product to what we just labeled, so we know what to use as a reference when getting batches.
current_product = record[1]
# Get all batches for this product and put them in a list
all_batches = [i for i,v in product_batch_unique_sets if v == current_product]
# Now take each batch from the list we just made..
for batch in all_batches:
# Label it
# Label(self, text=str(batch), font=("Helvetica", 10)).grid(row=self.r, column=0, sticky="nw")
# Add 2 rows to make room for the batch and for the unit numbers
# Get all unitnumbers printed for this specific batch
all_unitnumbers = [v for i,v,u in batch_unit_unique_sets if i == batch]
# Sort them so it's readable
all_unitnumbers.sort()
# Get all units that were scanned from this batch
all_scannedunits = [v for i,v,u in units_scanned_unique_sets if i == batch]
# Sort them so it's readable
all_scannedunits.sort()
# Label the scanned units, they are green
Label(self, text=str(all_scannedunits), font=("Helvetica", 8), wraplength=500, justify="left", fg="green").grid(row=self.r, column=self.c, sticky="nw", padx=20)
# Add 2 rows to make room for the unscanned units
self.r+=2
# This takes all printed units, and subtracts all scanned units from that. What's left is the units that were printed but not scanned
a = Counter(all_unitnumbers)
b = Counter(all_scannedunits)
c = a - b
# Label all units printed but not scanned
Label(self, text=str(list(c.elements())), font=("Helvetica", 8), wraplength=500, justify="left", fg="red").grid(row=self.r, column=self.c, sticky="nw", padx=20)
# Add two rows to make room for the next Product
self.r+=2
# all_noscans = [v for i,v,u in units_noscan_unique_sets if i == batch]
# Label(self, text=str(all_noscans), font=("Helvetica", 8)).grid(row=self.r, column=0, sticky="nw", padx=20)
# self.r+=3
if should_continue_prod_report:
self.after(5000, lambda:self.build_productionrpt(self.lotdate_e.get(), self.shift_e.get()))
I was able to fix this issue by:
Storing all labels in a dictionary with the key being something like ProductVariable + "_label" and the value would be the label
Storing all textvariables in the same dictionary. I had to change the label(root, text=..) to label(root, textvariable=..)
In every for loop, check and see if the label existed. If it does, use set() to update it.
Basically, it was bad practice to create labels every 5 seconds and forget the old ones as a method to updating data live.

How to get correct data from QSortFilterProxyModel PyQt

I have a QTreeView with QSortFilterProxyModel. When I click on any item of the view, this prints the index from the clicked row. Before and after setting the filter it prints the same index.
I need this for removeRow(), but when I put index, it removes the incorrect item from the list :/
How to delete the correct selected row?
Example:
Not Sorted
ITEM 1
ITEM 2 -> Click: Print ITEM2 and this is removed
ITEM 3
ITEM 4
Sorted
ITEM 2
ITEM 4 -> Click: Print ITEM2 and ITEM2 is removed :/
init
self.menuIndex = None
self.mModelMenu = QtGui.QStandardItemModel(0, 4, self)
self.mModelMenu.setHeaderData(0, QtCore.Qt.Horizontal, "Id")
self.mModelMenu.setHeaderData(1, QtCore.Qt.Horizontal, "Descripcion")
self.mModelMenu.setHeaderData(2, QtCore.Qt.Horizontal, "Categoria")
self.mModelMenu.setHeaderData(3, QtCore.Qt.Horizontal, "Cantidad")
self.mProxyModelMenu = QtGui.QSortFilterProxyModel()
self.mProxyModelMenu.setDynamicSortFilter(True)
self.mProxyModelMenu.setSourceModel(self.mModelMenu)
self.mTreeView = QtGui.QTreeView()
self.mTreeView.setRootIsDecorated(False)
self.mTreeView.setAlternatingRowColors(True)
self.mTreeView.setModel(self.mProxyModelMenu)
self.mTreeView.setSortingEnabled(True)
self.mTreeView.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.mTreeView.resizeColumnToContents(0)
self.mTreeView.clicked.connect(self.getIndex)
mPushButton1 = QtGui.QPushButton("Borrar")
mPushButton1.clicked.connect(self.newInMenu)
def getIndex()
print(index.row())
self.menuIndex = index.data(index.row())
print(index.data(index.row()))
# print(index.data(QtCore.Qt.DisplayRole))
def removeFromMenu(self):
toRemove = self.menuIndex
self.mModelMenu.removeRow(0) #this delete the last row, but i need delete current selected row
The simplest way to work with the proxy model is to avoid working on the original model and use QSortFilterProxyModel methods when possible.
E.g. simply invoking removeRow on the proxy model:
def removeFromMenu(self)
selIndexes = self.mTreeView.selectedIndexes()
if len(selIndexes):
first = selIndexes[0]
self.mProxyModelMenu.removeRow(first.row(), first.parent())
should do the trick.
When using a QSortFilterProxyModel, you have two types of index: the proxy indexes (the order you see on the view) and the source indexes from the original model (the order in the QStandardItemModel).
The view will give you the proxy index, and you can go back to the source index with mapToSource, like this:
#get first selected index
proxy_index = self.mTreeView.selectedIndexes()[0]
#convert
source_index=self.mProxyModelMenu.mapToSource(proxy_index)
#delete row in source model
self.mModelMenu.removeRow(source_index.row())

Resources