Access windows for notebook in Tkinter - authentication

I have been trying to create an open windows which asks you for username and password before opening a notebook in Tkinter, I have both, but I don't know how to put them together. In other words, what I want is to open a notebook once the username and password requested are correct.
Thank you very much in advance!
What I have done so far is as follows:
import Tkinter
from Tkinter import *
import ttk
from ttk import * #Combobox Definition
import tkMessageBox #for Welcome Message
import Tkinter as tk # For Main Frame Definition
from Tkinter import Tk, Text, BOTH, W, N, E, S
from ttk import Frame, Button, Label, Style
root = Tk()
root.title("Model A")
root.minsize(400, 220)
root.maxsize(410, 240)
# start of Notebook (multiple tabs)
notebook = ttk.Notebook(root)
notebook.pack(fill='both', expand='yes')
notebook.pressed_index = None
# create a child frame for each page
frameOne = Tkinter.Frame(notebook, bg='white',width=560, height=100)
frameOne.pack(fill='both', expand=True)
# create the pages
notebook.add(frameOne, text='Simple calculation')
#Login Starts
failure_max = 8
passwords = [('name','password')]
def make_entry(parent, caption, width=None, **options):
tk.Label(parent, text=caption).pack(side=tk.TOP)
entry = tk.Entry(parent, **options)
if width:
entry.config(width=width)
entry.pack(side=tk.TOP, padx=10, fill=tk.BOTH)
return entry
def enter(event):
check_password()
def check_password(failures=[]):
if (user.get(), password.get()) in passwords:
root.destroy()
return
failures.append(1)
if sum(failures) >= failure_max:
root.destroy()
raise SystemExit('Unauthorized login attempt')
else:
root.title('Try again. Attempt %i/%i' % (sum(failures)+1, failure_max))
parent = Tkinter.Frame(notebook, padx=10, pady=18, bg='white')
parent.pack(fill=tk.BOTH, expand=True)
user = make_entry(parent, "User name:", 16, show='')
password = make_entry(parent, "Password:", 16, show="*")
b = tk.Button(parent,borderwidth=4,text="Login",width=10,pady=8,command=check_password)
b.pack(side=Tkinter.BOTTOM)
password.bind('<Return>', enter)
#Close Application Button
def quit(root):
root.destroy()
tk.Button(root, text="Close Application", command=lambda root=root:quit(root)).pack()
#Calculation Starts
def defocus(event):
event.widget.master.focus_set()
def multiply(*args):
try:
product.set(round(float(Num_One.get())*float(Num_Two.get())))
except ValueError:
pass
Num_One = StringVar()
Num_Two = StringVar()
product = DoubleVar()
ttk.Label(frameOne, text="Select First Number:").grid(column =3, row = 0)
NumOne_Select = Combobox(frameOne, values=("1", "2", "3","4", "5"),textvariable=Num_One)
NumOne_Select.grid(column=4, row=0, columnspan="5", sticky="nswe")
Num_One.trace("w", multiply)
ttk.Label(frameOne, text="Select Second Number:").grid(column =3, row = 6 )
NumTwo_Select = Combobox(frameOne, values=("1", "2", "3","4", "5"),textvariable=Num_Two)
NumTwo_Select.grid(column=4, row=6, columnspan="5", sticky="nswe")
Num_Two.trace("w", multiply)
ttk.Label(frameOne, text = "Product:").grid(column = 3, row = 8)
ttk.Label(frameOne, textvariable=product).grid(column = 4, row = 8)
user.focus_set()
parent.mainloop()
root.mainloop()

You have several things going wrong in your code:
you're calling mainloop twice; you should only ever call it once.
you shouldn't pack or grid widgets inside the notebook. You are packing a widget and then using notebook.add; omit the pack.
you are calling destroy on the root window if the password is good. This causes your application to exit. Don't call destroy.
Normally the way this is done is that the notebook is a child of the root window, and the username/password dialog is an instance of Toplevel. You can hide the root window and pop up the dialog, and then if the user logs in, you can destroy the dialog and un-hide the main window.

Related

QApplication.focusWidget().pos() always returning 0

I have a custom QWidget that I have embedded into a QTableWidget.
When I toggle the QCheckBoxes and modify the text in the QLineEdit widgets, the program is not able to distinguish the widgets in rows 2 and 1 from the widgets in row 0. How can I change the program so that it prints the correct row and column of the QLineEdit widget that is being edited or the Checkbox that is being toggled?
Figure 1 shows a screenshot of the program with the output after selecting the third checkbox many times in Visual Studio Code. The output is expected to read “2 0” repeatedly but instead it reads “0 0”.
Figure 2 Similarly, when I modify the text in the QLineEdit in cell 2,0 from “My Custom Text” to “Text” the program prints “Handle Cell Edited 0,0”, although it is expected to print “Handle Cell Edited 2,0 Cell 2,0 was changed to Text”.
Code:
# Much of this code is copy pasted form user: three_pineapples post on stackoverflow:
# https://stackoverflow.com/a/26311179/18914416
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QTableWidget, \
QApplication, QTableWidgetItem, QLineEdit, QCheckBox
from PyQt5 import QtGui
class SimpleTable(QTableWidget):
def __init__(self,window):
# Call the parent constructor
QTableWidget.__init__(self)
self.window = window
class myWidget(QWidget):
#This code is adapted paritally form a post by user sebastian at:
#https://stackoverflow.com/a/29764770/18914416
def __init__(self,parent=None):
super(myWidget,self).__init__()
self.Layout1 = QHBoxLayout()
self.item = QLineEdit("My custom text")
#https://stackabuse.com/working-with-pythons-pyqt-framework/
self.Checkbox = QCheckBox()
self.Checkbox.setCheckState(Qt.CheckState.Unchecked)
self.Layout1.addWidget(self.Checkbox)
self.Layout1.addWidget(self.item)
#https://stackoverflow.com/questions/29764395/adding-multiple-widgets-to-qtablewidget-cell-in-pyqt
self.item.home(True)
#https://www.qtcentre.org/threads/58387-Left-text-alignment-for-long-text-on-QLineEdit
self.setLayout(self.Layout1)
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QHBoxLayout()
self.setLayout(layout)
self.table_widget = SimpleTable(window=self)
layout.addWidget(self.table_widget)
self.table_widget.setColumnCount(3)
self.table_widget.setHorizontalHeaderLabels(['Colour', 'Model'])
items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
for i in range(len(items)):
c = QTableWidgetItem(items[i][0])
m = QTableWidgetItem(items[i][1])
self.table_widget.insertRow(self.table_widget.rowCount())
self.table_widget.setItem(i, 1, c)
self.table_widget.setItem(i, 2, m)
myWidget1 = myWidget()
myWidget1.Checkbox.stateChanged.connect(self.handleButtonClicked)
myWidget1.item.editingFinished.connect(self.handle_cell_edited)
self.table_widget.setCellWidget(i,0,myWidget1)
myWidget1.Layout1.setContentsMargins(50*i+10,0,0,0)
self.show()
self.table_widget.itemChanged.connect(self.handle_cell_edited)
def handleButtonClicked(self):
#Adapted from a post by user: Andy at:
# https://stackoverflow.com/a/24149478/18914416
button = QApplication.focusWidget()
# or button = self.sender()
index = self.table_widget.indexAt(button.pos())
if index.isValid():
print(index.row(), index.column())
# I added this fuction:
def handle_cell_edited(self):
if QApplication.focusWidget() != None:
index = self.table_widget.indexAt(QApplication.focusWidget().pos())
x,y = index.column(),index.row()
if index.isValid():
print("Handle Cell Edited",index.row(), index.column())
if self.table_widget.item(y,x)!= None:
print(f"Cell {x},{y} was changed to {self.table_widget.item(y,x).text()}.")
def main():
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
main()
What I've Tried So Far:
I learned that QT has two types of widgets that can be embedded in a table; a QTableWigetItem which can be inserted into a table using setItem()(3) and Qwidgets, which can be placed into a table using setCellWidget().(4) Generally, I know that using a QTableWigetItem one can set the item.setFlags(Qt.ItemFlag.ItemIsUserCheckable)
flag to create a checkbox in the cell. (3) However, when using the QTableWigetItem, I wasn’t able to find a way to indent the checkboxes. Because giving each checkbox its own indentation level is important in the context of my program, I’ve decided to use Qwidgets instead of QTableWigetItems in the few select cells where indenting is important.
I’ve read that by creating a QItemDelegate(5)(6), you can do a lot more with setting QWidgets in boxes. However, creating a delegate seems complicated, so I’d prefer to avoid this if possible. If there is no other way to make the program register the correct cell number of the cell being edited, creating a delegate will be the next thing I look into.
For anyone who might want to experiment with QTableWigetItems in this application, here is an equivalent program that uses QTableWigetItems instead of QWidgets but doesn't permit separate indentation or editing of the text field in column 0. For either and both of these two reasons, a QTableWigetItem seems not to be usable for the checkboxes in column 0.
Less Successful Attempt using QTableWidgetItem:
#Much of this code is copy pasted form user: three_pineapples post on stackoverflow:
# https://stackoverflow.com/a/26311179/18914416
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QTableWidget, \
QApplication, QTableWidgetItem, QLineEdit, QCheckBox
from PyQt5 import QtGui
class SimpleTable(QTableWidget):
def __init__(self,window):
QTableWidget.__init__(self)
self.window = window
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QHBoxLayout()
self.setLayout(layout)
self.table_widget = SimpleTable(window=self)
layout.addWidget(self.table_widget)
self.table_widget.setColumnCount(3)
self.table_widget.setHorizontalHeaderLabels(['Colour', 'Model'])
items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
for i in range(len(items)):
c = QTableWidgetItem(items[i][0])
m = QTableWidgetItem(items[i][1])
self.table_widget.insertRow(self.table_widget.rowCount())
self.table_widget.setItem(i, 1, c)
self.table_widget.setItem(i, 2, m)
item = QTableWidgetItem("My Custom Text")
item.setFlags(Qt.ItemFlag.ItemIsUserCheckable| Qt.ItemFlag.ItemIsEnabled)
item.setCheckState(Qt.CheckState.Unchecked)
self.table_widget.setItem(i,0,item)
#https://youtu.be/DM8Ryoot7MI?t=251
self.show()
#I added this line:
self.table_widget.itemChanged.connect(self.handle_cell_edited)
def handleButtonClicked(self):
#Adapted from a post by user: Andy at:
# https://stackoverflow.com/a/24149478/18914416
button = QApplication.focusWidget()
# or button = self.sender()
index = self.table_widget.indexAt(button.pos())
if index.isValid():
print(index.row(), index.column())
# I added this fuction:
def handle_cell_edited(self):
if QApplication.focusWidget() != None:
index = self.table_widget.indexAt(QApplication.focusWidget().pos())
x,y = index.column(),index.row()
if index.isValid():
print("Handle Cell Edited",index.row(), index.column())
if self.table_widget.item(y,x)!= None:
print(f"Cell {x},{y} was changed to {self.table_widget.item(y,x).text()}.")
def main():
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
main()
Bibliography:
1.https://i.stack.imgur.com/FudE3.png
2.https://i.stack.imgur.com/C2ypp.png
3.https://youtu.be/DM8Ryoot7MI?t=251
4.https://stackoverflow.com/questions/24148968/how-to-add-multiple-qpushbuttons-to-a-qtableview/24149478#24149478
5.Creating a QItemDelegate for QWidgets, https://stackoverflow.com/a/35418141/18914416
6.Need to create a QItemDelegate to add a stylesheet to QTableWidgetItems: https://forum.qt.io/topic/13124/solved-qtablewidgetitem-set-stylesheet
The geometry of a widget is always relative to its parent.
In your first example, the problem is that the pos() returned for the widget is relative to the myWidget container, and since the vertical position is always a few pixels below the top of the parent (the layout margin), you always get the same value.
The second example has another conceptual problem: the checkbox of a checkable item is not an actual widget, so the widget you get is the table itself.
def handle_cell_edited(self):
# this will print True
print(isinstance(QApplication.focusWidget(), QTableWidget))
As explained above, the geometry is always relative to the parent, so you will actually get the position of the table relative to the window.
The solution to the first case is quite simple, as soon as you understand the relativity of coordinate systems. Note that you shall not rely on the focusWidget() (the widget might not accept focus), but actually get the sender(), which is the object that emitted the signal:
def handleButtonClicked(self):
sender = self.sender()
if not self.table_widget.isAncestorOf(sender):
return
# the widget coordinates must *always* be mapped to the viewport
# of the table, as the headers add margins
pos = sender.mapTo(self.table_widget.viewport(), QPoint())
index = self.table_widget.indexAt(pos)
if index.isValid():
print(index.row(), index.column())
In reality, this might not be that necessary, as an item delegate will suffice if the indentation is the only requirement: the solution is to properly set the option.rect() within initStyleOption() and use a custom role for the indentation:
IndentRole = Qt.UserRole + 1
class IndentDelegate(QStyledItemDelegate):
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
indent = index.data(IndentRole)
if indent is not None:
left = min(opt.rect.right(),
opt.rect.x() + indent)
opt.rect.setLeft(left)
class SimpleTable(QTableWidget):
def __init__(self,window):
QTableWidget.__init__(self)
self.window = window
self.setItemDelegateForColumn(0, IndentDelegate(self))
class Window(QWidget):
def __init__(self):
# ...
for i in range(len(items)):
# ...
item.setData(IndentRole, 20 * i)

Tkinter: How can I check if any of the widgets of a specific frame have changed?

What is the best way to check if any widget (or variable linked to it) of a given frame(frm1) has changed and take an action. For example activate a button.
I would like that when something was typed in the entries or changed the combobox or checkbox, the 'changed_content' function would be executed
from tkinter import *
from tkinter import ttk
from tkinter.messagebox import showinfo
class Defaultframe(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry('500x300')
self.title('Tkinter')
self.text1 = StringVar()
self.text2 = StringVar()
self.text3 = StringVar()
self.var1 = IntVar()
self.var2 = IntVar()
self.set_widgets()
return
def changed_content(self):
showinfo('Information', 'The content has been changed')
self.btn2.configure(state='normal')
return
def set_widgets(self):
#Frame1
self.frm1 = ttk.Frame(self).pack(side=TOP)
self.lbl = ttk.Label(self.frm1, text='Text1').pack(pady=5)
self.ent1 = ttk.Entry(self.frm1, textvariable=self.text1).pack(pady=5)
self.lbl = ttk.Label(self.frm1, text='Text2').pack(pady=5)
self.my_ent = ttk.Entry(self.frm1, textvariable=self.text2).pack(pady=5)
self.cbb = ttk.Combobox(self.frm1,
values=[0, 30, 60, 90, 120, 150, 180],
state='readonly',
textvariable=self.var2)
self.cbb.pack(pady=5)
self.cbb.current(3)
self.ckb = ttk.Checkbutton(self.frm1, text='Hello', variable=self.var1, onvalue=1, offvalue=0).pack(pady=5)
#---
#Frame2
self.frm2 = ttk.Frame(self).pack(side=BOTTOM, fill=X)
ttk.Separator(self.frm2, orient=HORIZONTAL).pack(side=TOP, expand=1, fill=X)
self.my_ent3 = ttk.Entry(self.frm2, textvariable=self.text3).pack(side=LEFT, padx=1)
self.btn1 = ttk.Button(self.frm2, text='Cancel').pack(side=RIGHT, padx=1)
self.btn2 = ttk.Button(self.frm2, text='Save')
self.btn2.pack(side=RIGHT, padx=1)
self.btn2.configure(state=DISABLED)
#---
if __name__== '__main__':
app = Defaultframe()
app.mainloop()
Solving for any widget is tough - you'll have to write code specifically for a canvas or a scrollbar or any other widget that isn't associated with a tkinter variable.
For widgets that are associated with a tkinter variable you can apply a trace that will call a function whenever the value changes.
In your code it might look something like this:
class Defaultframe(Tk):
def __init__(self):
...
self._watch_variables(self.text1, self.text2, self.text3, self.var1, self.var2)
def _watch_variables(self, *vars):
for var in vars:
var.trace_add("write", self._handle_trace)
def _handle_trace(self, *args):
self.changed_content()

How to modify drop event in QListView

I have a QListView where items can be reordered with drag-and-drop by setting dragDropMode to DragDrop and defaultDropAction to MoveAction. How can I intercept the drop event, to find out what is trying to be moved where in relation to the rest of the list, so that I can cancel this action under certain conditions? E.g. I want to forbid moving some items behind other.
You can access the indexes and items involved in the dropEvent and setDropAction to Qt.IgnoreAction to cancel the drop depending on your criteria. Since you weren't specific, for this demonstration I just created one item that stays at the bottom.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class List(QListView):
def dropEvent(self, event):
i = self.selectedIndexes()[0]
j = self.indexAt(event.pos())
# To access the items involved
source = self.model().itemFromIndex(i)
target = self.model().itemFromIndex(j)
bottom = (self.model().rowCount() - 1, -1)
if i.row() in bottom or j.row() in bottom:
event.setDropAction(Qt.IgnoreAction)
else:
super().dropEvent(event)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QWidget()
lv = List()
lv.setDragDropMode(QAbstractItemView.DragDrop)
lv.setDefaultDropAction(Qt.MoveAction)
model = QStandardItemModel(5, 1)
for i in range(4):
item = QStandardItem(f'Item {i + 1}')
item.setFlags(item.flags() & ~Qt.ItemIsDropEnabled)
model.setItem(i, 0, item)
item = QStandardItem('I stay at the bottom ._.')
model.setItem(4, 0, item)
lv.setModel(model)
vbox = QVBoxLayout(window)
vbox.addWidget(lv)
window.show()
sys.exit(app.exec_())

Take user input through tkinter, create barcode from that input

I want to take user input through tkinter gui. When the user confirms his entry by pressing a button, I want to create a barcode from that input.
The barcode will be saved as an image and it will be named after the user input, so for example "456789.png".
I have the gui working, but I have a problem with the barcode-creation-process.
When I confirm my entry, I get the error message:
"TypeError: StringVar object is not iterable".
Here is my code
from barcode import generate
from barcode.writer import ImageWriter
from tkinter import Tk, Entry, Label, Button
root = Tk()
root.geometry("640x640+0+0")
heading = Label(root, text="Barcode Creator", fg="steelblue", font=("arial", 16)).pack()
label1 = Label(root, text = "Enter Barcode: ", font=("arial", 16, "bold"), fg = "black").place(x=10, y = 200)
name = StringVar()
entry_box = Entry(root, textvariable = name, width=25, bg="lightgreen").place(x=280, y=210)
def create_code():
create = generate('code128', name, output = name, writer = ImageWriter())
create
work = Button(root, text = "Create Barcode", width = 30, height = 5,
bg = "lightblue", command = create_code).place(x=250, y=300)
root.mainloop()
the error is here
from tkinter import Tk, Entry, Label, Button
thet should be
from tkinter import Tk, Entry, Label, Button, StringVar
pay attention when you select your import

How to create a dropdown from a gtk.Entry's icon?

I have a gtk.Entry with an icon after the text, intending to be a text search field:
What I'm trying to do is to display a dropdown (i.e. a gtk.ComboBox) when the user clicks on the icon, to choose the type of search. A mock of that feature would be:
I have tried several things without any success. For example, trying to pack an empty gtk.ComboBox only showing an arrow right after the Entry, and stuffing it only on icon-press, which creates the illusion, but it has two drawbacks: a) when I stuff the ComboBox, the toolbar grows, and b) when I clear() the ListStore, the ComboBox retains its width and leaves an ugly grey box.
At this point I guess that I need to create a CellRenderer on icon-press that pops down the icon of the Entry, and I tried without a lot of success to understand the code of gtk.ComboBoxEntry (in gtkcomboboxentry.c), but as far as I understood it uses a vertical Container on the whole piece together with a CellRenderer.
Also GTK+3 doesn't have any ideas on this respect.
Any ideas, or some guidance in how to create this in PyGTK?
I was looking for something similar, so I came up with the code below. I haven't really worried about the aesthetics. I did pass a list of tuples to the MyPopup class, with the idea of passing handlers for each of the menu items in the dropdown. Note that the item.show() is necessary, even though there is a show_all():
from gi.repository import Gtk
class MyPopup(Gtk.MenuButton):
def __init__(self, btndefs):
super(MyPopup, self).__init__()
self.menu = Gtk.Menu()
self.set_popup(self.menu)
#self.set_label(">")
self.set_direction(Gtk.ArrowType.RIGHT)
for btndef in btndefs:
item = Gtk.MenuItem()
item.set_label(btndef[0])
item.show()
self.menu.append(item)
class MainWindow(Gtk.Window):
def __init__(self):
super(MainWindow, self).__init__()
self.set_size_request(100, -1)
self.connect("destroy", lambda x: Gtk.main_quit())
self.hbox = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
self.entry = Gtk.Entry()
self.popup = MyPopup( (("String",),
("String no case",),
("Hexadecimal",),
("Regexp",)) )
self.hbox.pack_start(self.entry, True, True, 0)
self.hbox.pack_start(self.popup, False, True, 0)
self.add(self.hbox)
self.show_all()
def run(self):
Gtk.main()
def main():
mw = MainWindow()
mw.run()
return 0
if __name__ == '__main__':
main()
yup its year late, but lets not make next person stumbled here to be sad like me.
this is the example using Gtk.Menu() popup, you can also similar feat. with Gtk.Popover()
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
opts = {
'hex' : "system-run-symbolic",
'regex' : "font-select-symbolic",
'string' : "font-x-generic-symbolic",
'no-case' : "tools-check-spelling-symbolic",
}
def make_menu(entry, opts):
menu = Gtk.Menu()
for label, icon in opts.items():
item = Gtk.MenuItem()
item.set_label(label)
item.connect(
"activate",
lambda w: entry.set_icon_from_icon_name(0, opts[w.get_label()])
)
menu.append(item)
# NOTE you can use Gtk.ImageMenuItem to add image but its
# Deprecated since version 3.10
menu.show_all()
return menu
def on_icon_release(widget, pos, event):
menu = make_menu(widget, opts)
menu.popup(
parent_menu_shell = None,
parent_menu_item = None,
func = None,
data = None,
button = Gdk.BUTTON_PRIMARY,
activate_time = event.get_time()
)
def make_entry():
entry = Gtk.Entry()
entry.set_icon_from_icon_name(0, 'action-unavailable-symbolic')
entry.set_icon_from_icon_name(1, 'fonts')
entry.set_icon_sensitive(1, True)
entry.set_icon_activatable(1, True)
entry.connect("icon-release", on_icon_release)
return entry
root = Gtk.Window()
root.add(make_entry())
root.show_all()
Gtk.main()