Страницы

Поиск по вопросам

Показаны сообщения с ярлыком tkinter. Показать все сообщения
Показаны сообщения с ярлыком tkinter. Показать все сообщения

среда, 4 марта 2020 г.

Общие параметры

#python #button #tkinter


Можно ли задать параметры для всех Button/text/Label одной функцие, чтобы не писать
к каждой кнопке одинаковые параметры? К примеру, я создал много однотипных кнопок для
ввода данных калькулятора:

bttn1_1 = Button(root, text='MC', font='Arial 15', width=3, height=1, bd=4,
                 background='SkyBlue2', activebackground='green yellow')

    


Ответы

Ответ 1



Примерно так можно: buttons = {} for button_text in map(str, range(10)): button = Button(root, text=button_text, font='Arial 15', width=3, height=1, bd=4, background='SkyBlue2', activebackground='green yellow') buttons[button_text] = button Кнопка с текстом 1: buttons['1'] Еще можно создать функцию которая обрезает/исправляет параметры: def create_button(text): return Button(root, text=text, font='Arial 15', width=3, height=1, bd=4, background='SkyBlue2', activebackground='green yellow')

Ответ 2



Можно опции для кнопок, созданных в разных местах кода, менять, изменив соответствующий стиль: #!/usr/bin/env python3 import tkinter.ttk root = tkinter.Tk() style = tkinter.ttk.Style() for color in ['red', 'green']: tkinter.ttk.Button( text=color, command=lambda c=color: style.configure('TButton', background=c)).pack() root.mainloop() Нажимая на любую из кнопок, изменяется цвет у всех кнопок со стилем 'TButton' в данном случае. Сгруппировать кнопки по своим стилям, можно используя style параметр: Button(style='C.TButton').

воскресенье, 9 февраля 2020 г.

Не отображается изображение в Label

#python #python_3x #tkinter


Разрабатываю своё первое оконное приложение, и вот столкнулся с проблемой. Я вызываю
функцию с параметром и в зависимости от параметра в Label должно выводиться разное
изображение. Но вместо этого просто серый экран, изображения на кнопке тоже нет, но
свою функцию кнопка исправно выполняет. Что с этим делать ума не приложу раньше все
работало нормально. Заранее спасибо

from tkinter import *


def eclick(event):
   root.destroy()

def edscr():

    e_sc_1 = PhotoImage(file='e_scr_1.png')
    e_scr_1 = Label(root, image=e_sc_1)
    e_scr_1.place(x=0, y=0)

    e_but = PhotoImage(file='but_e.png')
    but_e = Button(root, width=477, height=16, image=e_but)
    but_e.place(x=130, y=350)
    but_e.bind('', eclick)


root = Tk()
edscr()

root.mainloop()

    


Ответы

Ответ 1



Как прокомментировал @jfs "ссылку на изображение следует сохранять" по этому в данном ответе весь объект Tk помещен в class пример кода, а само изображение помещено в переменную self.image т.е. вы присваиваете изображение в блок класса: import tkinter class Ex(tkinter.Tk): def __init__(self): super().__init__() self.image = tkinter.PhotoImage(file='2.gif') label = tkinter.Label(self, image=self.image) label.pack() if __name__ == "__main__": main = Ex() main.mainloop() Для того чтобы использовать другие виды расширения используйте библиотеку PIL пример: import tkinter from PIL import Image, ImageTk class Ex(tkinter.Tk): def __init__(self): super().__init__() pil_image = Image.open("2.jpg") self.image = ImageTk.PhotoImage(pil_image) image_sprite = tkinter.Label(self, image=self.image) image_sprite.pack() if __name__ == "__main__": main = Ex() main.mainloop() Так же можно поместить изображение в глобальную переменную в функции, но лучше через class import tkinter def main(): global image image = tkinter.PhotoImage(file='2.gif') label = tkinter.Label(root, image=image) label.pack() root = tkinter.Tk() main() root.mainloop()

Как в tkinter регулировать размер, цвет, и шрифт текста?

#python #tkinter #текст


Как в tkinter регулировать размер, цвет, и шрифт текста?
    


Ответы

Ответ 1



tkinter.Text(root, font="Courier 20", bg="Black", fg=color) Так можно регулировать и бэкграунд, и шрифт, и цвет текста.

Ответ 2



При помощи font вы регулируете размер и стиль шрифта, а при помощи fg вы регулируете цвет шрифта. Подробнее можете посмотреть вот тут import tkinter root = tkinter.Tk() label = tkinter.Label(root, text='Hello word', font='Times 30', fg='#0F0').grid() root.mainloop()

воскресенье, 2 февраля 2020 г.

Мультизадачность на Python: выполнить две долгие функции одновременно, не блокируя GUI

#python #многопоточность #gui #tkinter


Всем доброго времени суток.
Изучаю возможности графического модуля tkinter на Python. Возникла необходимость
одновременного запуска нескольких функций, т.е. имеется, например, две кнопки, каждая
со своим функционалом. Нужно сделать так, чтобы окно с кнопками после нажатия на одну
из них не зависало и позволяло нажать другую кнопку, незамедлительно запустив ее функцию.
Пишу на питоне версии 3.5.2.

from tkinter import *

def whilefunc1():
    n = 1
    while (n <= 100000):
        print(n)
        n = n + 1

def whilefunc2():
    n = 100001
    while (n <= 200000):
        print(n)
        n = n + 1

root = Tk()
root.geometry("200x200")

but1 = Button(root, text="press me", command = whilefunc1)
but1.pack() 
but2 = Button(root, text="me 2!!", command = whilefunc2)
but2.pack()

root.mainloop()

    


Ответы

Ответ 1



Общая особенность GUI программирования в том, что не следует использовать блокирующие функции в обработчиках событий, которые выполняются как правило в GUI потоке, иначе это приводит к "подвисанию" GUI. В вашем случае вместо self.status[ip] = os.system(f'ping {ip}') можно использовать self.process[ip] = subprocess.Popen(['ping', ip]) и позже проверять статус используя self.process[ip].poll() (оба вызова возвращаются сразу, не дожидаясь пока команда завершится). Периодический опрос можно организовать используя root.after(), например как в start_process(). Можно избежать опроса, если настроить обработчик сигнала, который вызывается когда запущенный дочерний процесс завершается, например, на Unix (усложнённая опция в данном случае). Cross-platform пример кода это QProcess.finished сигнал из Qt GUI библиотеки, который позволяет подключить свой обработчик, который вызывается, когда команда, запущенная QProcess::start, завершается (finished сигнал реализован эффективно, используя функциональность, предоставляемую соответствующей операционной системой, не требуя периодического опроса статуса запущенного дочернего процесса). Альтернативно, если необходимо запустить числодробительную задачу (CPU-bound), то для этого можно создать пул процессов: from concurrent.futures import ProcessPoolExecutor pool = ProcessPoolExecutor() и использовать command=lambda: pool.submit(whilefunc1) в качестве обработчика событий для кнопки (.submit() ставит выполнение whilefunc1() функции в очередь и сразу же возвращается). Если вы нажмёте 1000 раз на кнопку, то вы скорее всего не хотите запускать 1000 процессов (системе вероятно плохо будет). ProcessPoolExecutor() по умолчанию использует все доступные CPU ядра, не запуская излишние процессы. В этом смысле, это решение более предпочтительно чем решения, использующие threading.Thread(..).start() или multiprocessing.Process(..).start(), где каждое нажатие на кнопку приводит к запуску нового потока/процесса. Если задача связана с вводом-выводом, то можно использовать Widget.tk.createfilehandler() или аналогичную функциональность в других GUI библиотеках (например, GObject.io_add_watch()). Вот пример кода, где root.createfilehandler() используется чтобы читать вывод внешней команды, не дожидаясь её завершения. Поддержка createfilehandler() может быть ограничена на некоторых платформах (Windows), поэтому ту же задачу можно переносимо выполнить используя фоновый поток (попытка использовать socketpair, чтобы получить переносимый код, оказалась неуспешной: Popen() не принимает соответствующий fileno() на Windows).

Ответ 2



Можно сделать декоратор def thread(fn): def execute(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs).start() return execute тогда можно вынести(@thread) выполнение функции в отдельный поток, без изменения остального кода @thread def whilefunc1(): # эта функция всегда будет вызватся в отдельном потоке n = 1 while (n <= 100000): print(n) n = n + 1 but1 = Button(root, text="press me", command = whilefunc1) если выполнение в потоке всегда, ненужно, то задекорировать можно и так def whilefunc2(): n = 100001 while (n <= 200000): print(n) n = n + 1 but2 = Button(root, text="me 2!!", command = thread(whilefunc2)) это избавляет от постоянного объявления threading.Thread

Ответ 3



Ну в общем-то нашел решение: Изменил but1 = Button(root, text="press me", command = whilefunc1) but1.pack() but2 = Button(root, text="me 2!!", command = whilefunc2) but2.pack() на but1 = Button(root, text="press me", command = lambda: threading.Thread(target = whilefunc1).start()) but1.pack() but2 = Button(root, text="me 2!!", command = lambda: threading.Thread(target = whilefunc2).start()) but2.pack() Однако я до сих пор не понимаю зачем там нужна lambda:, однако без нее не работает

Как сделать постоянное обновление окна Tkinter? Как избежать подвисания на время ожидания ответа от сервера

#python #python_3x #tkinter


Всем привет! для обновления окна использую 

root.update()


Но если программа делает например запрос к серверу и долго не получает ответ- программа
подвисает на время ожидания ответа. Как этого избежать и сделать так, чтобы программа
была всегда активна? Спасибо!
пример кода:

root=tk.Tk()
def work ():
    requests.get(url)
    time.sleep(5)
work()
root.mainloop()

    


Ответы

Ответ 1



Никогда никогда не используйте time.sleep(5) внутри цикла событий -- это замораживает ваше GUI на ~5 секунд. Используйте root.after(5000, some_action), чтобы выполнить код через 5 секунд в Tkinter. Чтобы выполнить работу, которая требует не фиксированное время (такую как отправка/чтение данных из сети, используя requests.get()), следует либо вынести в фоновый поток (threading), либо встроить операции ввода/вывода в сам цикл событий (Widget.tk.createfilehandler()). Примеры кода по ссылкам можно посмотреть: Мультизадачность на Python: выполнить две долгие функции одновременно, не блокируя GUI Конкретно, вот пример для tkinter, который читает вывод из внешней команды (self.proc.stdout.readline()), не блокируя цикл событий: вариант с фоновым потоком вариант без потока с createfilehandler(). Вот полный пример кода, который запускает requests.get() в фоновом потоке и обновляет показ текущего времени каждые 100 миллисекунд, пока запрос не завершится: #!/usr/bin/env python3 import time import tkinter from datetime import datetime from queue import Empty, Queue from threading import Thread import requests def display_result(label, q): try: label['text'] = q.get(block=False) except Empty: # update time at 100ms boundary label.after(round(100 - (1000 * time.time()) % 100), display_result, label, q) label['text'] = str(datetime.now().strftime("%H:%M:%S.%f")[:-3]) def get_result(q): # blocking function q.put('ip=' + requests.get('https://httpbin.org/delay/3').json()['origin']) # get result in a background thread result_queue = Queue() Thread(target=get_result, args=[result_queue], daemon=True).start() # display result root = tkinter.Tk() label = tkinter.Label(font=(None, 100)) label.pack() display_result(label, result_queue) # center window root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id())) root.mainloop() Чтобы обновить надпись (tkinter.Label) из другого потока, самый безопасный метод это использовать queue.Queue, как показано выше (то есть обращаться к label, только из потока с root.mainloop(), который её создал). Но если показывать прогресс не нужно (надпись прежняя остаётся пока блокирующий запрос не завершится), то в простых случаях можно обратиться и из другого потока напрямую: #!/usr/bin/env python3 import tkinter from threading import Thread import requests def fetch(url, on_response): requests.get(url, hooks=dict(response=on_response)) # blocking function def on_response(response, **unused_kwargs): # NOTE: it is called from another thread label['text'] = response.json()['origin'] # get result in a background thread root = tkinter.Tk() label = tkinter.Label(font=(None, 100), text='no result yet') label.pack() url = 'https://httpbin.org/delay/3' Thread(target=fetch, args=[url, on_response], daemon=True).start() root.mainloop() Современная реализация tkinter модуля в CPython пытается поддерживать обращение к tkinter объектам из других потоков. К примеру, если не включены потоки в Tcl интерпретаторе, то mutex расставляются, чтобы только один поток имел доступ к Tcl интерпретатору одновременно. Если включены потоки (tkinter.Tcl().eval('info exists ::tcl_platform(threaded)') == '1'), то обращения к Tcl из других потоков в очередь автоматически внутри tkinter добавляются и выполняются, когда управление вернётся на поток, где запущен Tcl интерпретатор (see all the gory details). Возможны баги, поэтому при возникновении любых проблем, обычно проще использовать упрощённую модель: все обращения к GUI объектам только в GUI потоке производятся.

вторник, 28 января 2020 г.

как обновлять график matplotlib в tkinter раз в 5 минут данными из базы данных?

#python #tkinter #matplotlib


Что нужно: Каждые 5 секунд, считываются координаты из БД и отображаются на графике
matplotlib в окне tkinter.

Что сделано: рисуются только первый набор данных и не рисуются второй набор

ВОПРОС: как исправить чтобы рисовался сначала первый набор данных , а через 5 секунд
второй?

Вот обновленный минимальный рабочий код:

from threading import Thread
from queue import Empty, Queue
import time
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg#, NavigationToolbar2TkAgg
from matplotlib.figure import Figure

class tkChartGUI(tk.Frame):       

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)   
        self.parent = parent
        self.initUI()

    def get_latest_data(self, dataid):
        x_array=[]
        y_array=[]
        if (dataid == 1):
            x_array=[1,2,3,4,5,6,7,8,9,10,11,12,13,14];
            y_array=[0.5,0.7,0.3,1.0,0.6,0.9,0.5,0.2,0.1,0.5,0.33,0.55,0.3,0.6] 
        if (dataid == 2):
            x_array=[1,2,3,4,5,6,7,8,9,10,11,12,13,14];
            y_array=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1,1.1,1.2,1.3] 
        return (x_array, y_array)

    def initUI(self):
        self.parent.title("Simple chart")
        self.parent.geometry("800x600+300+100")

        result_queue = Queue()
        Thread(target=self.get_latest_data, args=[result_queue], daemon=True).start()

        x_array, y_array = self.get_latest_data(1)

        f = Figure(figsize=(5, 3), dpi=150)        
        a = f.add_subplot(111)
        a.set_xlabel("Values_X")
        a.set_ylabel("Values_Y")
        a.yaxis.grid(True, which='major')
        a.xaxis.grid(True, which='major')
        a.plot(x_array, y_array)

        canvas = FigureCanvasTkAgg(f, master=self.parent)
        canvas.show()                        
        canvas.get_tk_widget().grid(row=0,column=0)

        def display_result(a, q):
            x_array = []
            y_array = []
            try:                
                x_array = q.get(block=False) # get data
                y_array = q.get(block=False)                           
                a.plot(x_array, y_array)
                canvas.draw() 
            except Empty:
                #a.clear()                            
                timeout_millis = round(100 - (5000 * time.time()) % 100)
                self.parent.after(timeout_millis, display_result, a, q)

        def get_result(q):
            x_array, y_array = self.get_latest_data(2)
            q.put(x_array) # put data in FIFO queue x coords array
            q.put(y_array) # put data in FIFO queue y coords array

        display_result(a, result_queue)

    def onExit(self):
        self.quit()

def main():
    root = tk.Tk()
    my_gui = tkChartGUI(root) 
    root.mainloop()

if __name__ == '__main__':
    main()

    


Ответы

Ответ 1



Чтобы постоянно не считывать данные из базы данных (polling), можно определить trigger, чтобы вызвать функцию (callback), когда в БД интересное событие произойдёт. К примеру, когда в нужную таблицу новое значение добавляется (Launch a Python Script from a sqlite3 Trigger): CREATE TRIGGER tt AFTER INSERT ON t BEGIN SELECT got_y(NEW.y); END Обновления графика происходят в got_y() обратном вызове, определённом в make_callback() ниже. Код похож на ответ c loop(), определённой с помощью .after(), с той разницей, что здесь функция вызывается, когда нужные данные готовы, вместо того чтобы периодически непрерывно новые данные запрашивать без блокировки: #!/usr/bin/env python3 import datetime as DT import sqlite3 import random import threading import time import tkinter as tk from collections import deque import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.dates import date2num def generate_data(db): # generate dummy data for the example while True: time.sleep(.1) db.execute('insert into t values(?)', (random.randrange(100),)) def make_callback(root): # plot something # see @MaxU answer https://ru.stackoverflow.com/q/801923/23044 n = 300 def get_t(): return date2num(DT.datetime.now()) xx = deque([get_t()], maxlen=n) yy = deque([0], maxlen=n) fig, ax = plt.subplots() ax.set_ylim(0, 100) line, = ax.plot_date(xx, yy, marker='') format_time = '{:%Y-%m-%d %H:%M:%S}'.format time_text = ax.text(0.5, 0.9, '', transform=ax.transAxes) # add to GUI canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea. canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) def got_y(y): # update plot xx.append(get_t()) yy.append(int(y)) line.set_data(xx, yy) ax.fill_between(xx, 0, yy, color='lightgrey') time_text.set_text(format_time(DT.datetime.now())) ax.relim() # update axes limits ax.autoscale_view(scaley=False) canvas.draw() return got_y def main(): root = tk.Tk() # a dummy db for the example db = sqlite3.connect(':memory:', check_same_thread=False) db.execute('create table t(y)') db.create_function('got_y', 1, make_callback(root)) db.execute('CREATE TRIGGER tt AFTER INSERT ON t BEGIN SELECT got_y(NEW.y); END') threading.Thread(target=generate_data, args=[db], daemon=True).start() root.mainloop() if __name__ == '__main__': main() Здесь db.create_function() определяет какая Питон-функция используется в качестве got_y(y) обратного вызова. В примере, callback вызывается в дочернем потоке, что не всегда желаемо. Поток с generate_data() используется только для примера, фактически данные могут вставляться в базу данных из другого процесса или других машин. How to receive automatic notifications about changes in tables? Для сравнения вот вариант с polling, где база данных постоянно опрашивается в фоновом потоке, используя простой цикл (про достоинства и недостатки цикла подробно описано в Как правильно сделать временный цикл?) def poll_db(interval=5): while True: time.sleep(interval - time.time() % interval) # avoid drift emit(get_y_from_db()) здесь get_y_from_db() делает запрос к базе данных, а emit() генерирует событие для GUI: #!/usr/bin/env python3 import random import threading import time import tkinter as tk def get_y_from_db(): # generate dummy data time.sleep(random.random()) # emulate blocking function return random.randrange(100) def poll_db(emit, interval=5): while True: time.sleep(interval - time.time() % interval) # avoid drift emit(get_y_from_db()) root = tk.Tk() root.bind('<>', lambda e, f=make_callback(root): f(e.y)) # subscribe threading.Thread(target=poll_db, args=[lambda y: root.event_generate('<>', when='tail', y=y)], daemon=True).start() root.mainloop() Пример кода (где этот метод также используется): вывод процесса показывается в Tkinter GUI с помощью root.event_generate() Не на всех реализациях можно root.event_generate() в фоновом потоке вызвать. В таких случаях можно использовать queue, чтобы данные между потоками передавать: #!/usr/bin/env python3 import datetime as DT import random import threading import time import tkinter as tk from collections import deque from queue import Empty, Queue from time import time as timer import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.dates import date2num def get_y_from_db(): # generate dummy data time.sleep(random.random()) # emulate blocking function return random.randrange(100) def poll_db(emit, interval=1): # NOTE: interval is independant from the delay while True: time.sleep(interval) emit(get_y_from_db()) def start_polling_loop(root, q, delay): # plot something # see @MaxU answer https://ru.stackoverflow.com/q/801923/23044 n = 600 def get_t(): return date2num(DT.datetime.now()) xx = deque([get_t()], maxlen=n) yy = deque([0], maxlen=n) fig, ax = plt.subplots() ax.set_ylim(0, 100) line, = ax.plot_date(xx, yy, marker='') format_time = '{:%Y-%m-%d %H:%M:%S}'.format time_text = ax.text(0.5, 0.9, '', transform=ax.transAxes) # add to GUI canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea. canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # update in a loop def loop(): timeout_millis = round(delay - (1000 * timer()) % delay) root.after(timeout_millis, loop) # avoid drift try: y = q.get(block=False) except Empty: return # no new data # update plot xx.append(get_t()) yy.append(y) line.set_data(xx, yy) ax.fill_between(xx, 0, yy, color='lightgrey') time_text.set_text(format_time(DT.datetime.now())) ax.relim() # update axes limits ax.autoscale_view(scaley=False) canvas.draw() root.after_idle(loop) # start root = tk.Tk() q = Queue() threading.Thread(target=poll_db, args=[q.put], daemon=True).start() start_polling_loop(root, q, delay=40) root.mainloop() Упрощённый вариант (с одним обновлением данных из потока), см. в Как сделать постоянное обновление окна Tkinter? Как избежать подвисания на время ожидания ответа от сервера. Ещё пример кода (где этот метод используется): вывод процесса показывается в Tkinter GUI, используя widget.after(), q.get()/q.put().

Ответ 2



Чтобы в tkinter периодически действие выполнять, можно его запускать с помощью after tcl команды. См. Обновление Label из цикла в tkinter. На каждой итерации, обновляйте желаемые элементы, к примеру, как показано в Python (jupyter) анимированный график и вызывайте canvas.draw(): #!/usr/bin/env python3 import datetime as DT import tkinter as tk from collections import deque from time import time as timer import matplotlib.pyplot as plt import psutil from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.dates import date2num def start_loop(root, delay): # plot something # see @MaxU answer https://ru.stackoverflow.com/q/801923/23044 n = 600 def get_t(): return date2num(DT.datetime.now()) xx = deque([get_t()], maxlen=n) yy = deque([0], maxlen=n) fig, ax = plt.subplots() ax.set_ylim(0, 100) line, = ax.plot_date(xx, yy, marker='') format_time = '{:%Y-%m-%d %H:%M:%S}'.format time_text = ax.text(0.5, 0.9, '', transform=ax.transAxes) # add to GUI canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea. canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # update in a loop def loop(): timeout_millis = round(delay - (1000 * timer()) % delay) root.after(timeout_millis, loop) # avoid drift # update plot # cpu sine https://stackoverflow.com/q/551494/4279 xx.append(get_t()) yy.append(psutil.cpu_percent()) line.set_data(xx, yy) ax.fill_between(xx, 0, yy, color='lightgrey') time_text.set_text(format_time(DT.datetime.now())) ax.relim() # update axes limits ax.autoscale_view(scaley=False) canvas.draw() root.after_idle(loop) # start def quit(): root.quit() root.destroy() # https://matplotlib.org/gallery/user_interfaces/embedding_in_tk_sgskip.html root = tk.Tk() start_loop(root, 100) root.protocol("WM_DELETE_WINDOW", quit) root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id())) root.mainloop() Сутью решения является loop() функция здесь.

четверг, 23 января 2020 г.

NameError: name 'mainloop' is not defined

#python #python_3x #tkinter


объясните пожалуйста почему так работает:
import tkinter

def recAction(*args):
    print('ddd')

root = tkinter.Tk()

row6 = tkinter.Frame(root, borderwidth = 10)
row6.pack(side = 'top', fill = 'x')
button = tkinter.Button(row6, text = 'Send', height = 20, width = 20, relief = 'raised',
cursor = 'hand1', font = ('times', 14, 'bold'))
button.bind('', recAction)
button.pack()

mainloop()

а так не работает:
import tkinter

def recAction():
    print('ddd')

root = tkinter.Tk()

row6 = tkinter.Frame(root, borderwidth = 10)
row6.pack(side = 'top', fill = 'x')
button = tkinter.Button(row6, text = 'Send', height = 20, width = 20, relief = 'raised',
cursor = 'hand1', font = ('times', 14, 'bold'))
button.bind('', recAction)
button.pack()

mainloop()

сообщение об ошибке такое:

Traceback (most recent call last):
File
"C:\Python33\projects\DVD_LIST\p3_dvd_list_shelve_3d_class_edit_menubar\q.py",
line 14, in 
    mainloop() NameError: name 'mainloop' is not defined

мне это не  понятно так как в интернетах много примеров именно второго типа. например
таких    


Ответы

Ответ 1



На вызов mainloop ругается потому, что эта функция определена внутри модуля tkinter. Нужно либо вызывать её как tkinter.mainloop(), либо включить её в пространство имен текущего модуля с помощью from tkinter import mainloop и вызывать просто как mainloop(). "Привязываемые" к событиям функции должны иметь как минимум один параметр, в который попадёт объект-событие. Опять же два варианта: смириться, и дать tkinter этот параметр, или объявить функцию recAction без параметров, а привязывать её через прослойку lambda, как предложил alexlz: button.bind('', lambda event: recAction())

Ответ 2



ну спасибо, помогли. хотя и я не понял почему именно нужно передавать аргумент, но теперь по крайней мере проблему буду решать следующим образом: button.bind('', lambda event: recAction(event)) ....... def recAction(event): print('ddd') и mainloop теперь буду записывать так: tkinter.mainloop :)

среда, 22 января 2020 г.

Вывод белого пустого места, вместо картинки

#python #python_3x #tkinter


Начал изучать возможности питона 3.6 и столкнулся с такой проблемой. 
Если я хочу открыть фотку из программы, то она корректно открывается. Если из функции
то вместо фотки отображается только белый прямоугольник. 
Может кто помочь?

from tkinter import *
import json
import string
from PIL import ImageTk, Image
import requests
import urllib.request, io
root=Tk()
root.title("Join")
root.geometry("1000x1000")
root.configure(background='grey')
text1=Text(root,height=7,width=7,font='Arial 14',wrap=WORD)
text1.pack()
button1=Button(root,text='ok',width=25,height=5,bg='black',fg='red', 
font='arial 14')
button1.pack()
def leftclick(event):
 l=text1.get('1.0',END)
 pay={'q':l}
 r=requests.get("https://api.vk.com/method/photos.search?v=5.64",params=pay)
 l = r.json()
 print (l["response"]["items"])
 URL=l["response"]["items"][0]["photo_130"]
 with urllib.request.urlopen(URL) as url:
      f = io.BytesIO(url.read())
 img1 = Image.open(f)
 img = ImageTk.PhotoImage(img1)
 panel = Label(root, image=img)
 panel.pack()
 button1.bind('', leftclick)
 root.mainloop()

    


Ответы

Ответ 1



Вот так катит) panel.mainloop() добавьте в функцию from tkinter import * import json import string from PIL import ImageTk, Image import requests from time import sleep root = Tk() root.title("Join") root.geometry("500x500") root.configure(background='grey') def img_load(event): r = requests.get("https://api.vk.com/method/photos.search?v=5.64") l = r.json() #print (l["response"]["items"]) URL=l["response"]["items"][0]["photo_130"] print(URL) r1 = requests.get(URL) f = open('image.jpg','wb') f.write(r1.content) f.close() img = Image.open('image.jpg') image = ImageTk.PhotoImage(img) panel = Label(image=image) panel.pack() panel.mainloop() button = Button(root,text='ok',width=5,height=1,fg='red', font='arial 14') button.pack() button.bind('', img_load) root.mainloop()

Ответ 2



Добавлю и свою реализацию from tkinter import * root = Tk() root.title("Join") root.geometry("500x1000") root.configure(background='grey') text1 = Text(root, height=7, width=7, font='Arial 14', wrap=WORD) text1.pack() button1 = Button(root, text='Add new image', width=25, height=5, bg='black', fg='red', font='arial 14') button1.pack() def left_click(event): import requests rs = requests.get("https://api.vk.com/method/photos.search?v=5.64") img_urls = rs.json()["response"]["items"] # Перемешивает список import random random.shuffle(img_urls) url = img_urls[0]["photo_130"] rs = requests.get(url) import io bytes_io = io.BytesIO(rs.content) from PIL import ImageTk, Image img = Image.open(bytes_io) img = ImageTk.PhotoImage(img) panel = Label(root, image=img) panel.pack() panel.mainloop() button1.bind('', left_click) root.mainloop() Результат:

суббота, 11 января 2020 г.

Функция блокировка изменения размера окна в Qt

#python_3x #tkinter #pyqt5


Существует ли функция в PyQt5 блокировка расширения окна как в tkinter .resizable(False,
False)?

Пример:

import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

app = QApplication(sys.argv)
root = QMainWindow()
root.resize(500, 700)
root.setWindowTitle('Детские кружки')
root.show()

sys.exit(app.exec())

    


Ответы

Ответ 1



Функцией setFixedSize(x, y) import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * app = QApplication(sys.argv) root = QMainWindow() root.setFixedSize(500, 700) root.setWindowTitle('Детские кружки') root.show() sys.exit(app.exec())

Как полностью удалить все виджеты из окна tkinter в python?

#python #python_3x #tkinter


Каким образом можно полностью удалить все виджеты которые присутствуют в окне tkinter
на примере данного кода:

from tkinter import *
tk = Tk()
tk.title('Разрушители')
main_lbl = Label(tk, text='Приветствую в "Разрушителях"', fg='red', font='Arial 20')
wellcome_lbl = Label(tk, text='Бродя по лесу ты наткнулся на своего первого противника - '
                              'ВОЛКА \n эти опасные твари никогда не отступают ,
так что В БОЙ!!!!!', 
                     fg='black', font='Arial 20')
next_btn=Button(tk, text='Далее', width=30, height=5, font=20)
def next1(event):
    pass
    #Как сделать что бы кнопка всё удаляла?
next_btn.bind(tk, next1)
main_lbl.pack()
wellcome_lbl.pack()
next_btn.pack()
tk.mainloop()

    


Ответы

Ответ 1



при помощи функции dectroy можно уничтожить объекты. Пример на вашем коде: from tkinter import * def next_1(): destroy_object = [welcome_lbl, main_lbl, next_btn] for object_name in destroy_object: object_name.destroy() tk = Tk() tk.title('Разрушители') main_lbl = Label(tk, text='Приветствую в "Разрушителях"', fg='red', font='Arial 20') welcome_lbl = Label(tk, text='Бродя по лесу ты наткнулся на своего первого противника - ВОЛКА' '\n эти опасные твари никогда не отступают, так что В БОЙ!!!!!', fg='black', font='Arial 20') next_btn = Button(tk, text='Далее', width=30, height=5, font=20, command=next_1) main_lbl.pack() welcome_lbl.pack() next_btn.pack() tk.mainloop() Так же если в дальнейшем будете пользоваться теми виджетами которые хотите "временно скрыть" можно вместо .pack() использовать.grid() и при помощи winfo.viewable() скрывать объекты и заново делать видимыми. Пример from tkinter import * def next_1(): destroy_object = [welcome_lbl, main_lbl] for object_name in destroy_object: if object_name.winfo_viewable(): object_name.grid_remove() else: object_name.grid() tk = Tk() tk.title('Разрушители') main_lbl = Label(tk, text='Приветствую в "Разрушителях"', fg='red', font='Arial 20') welcome_lbl = Label(tk, text='Бродя по лесу ты наткнулся на своего первого противника - ВОЛКА' '\n эти опасные твари никогда не отступают, так что В БОЙ!!!!!', fg='black', font='Arial 20') next_btn = Button(tk, text='Далее', width=30, height=5, font=20, command=next_1) main_lbl.grid() welcome_lbl.grid() next_btn.grid() tk.mainloop()

пятница, 10 января 2020 г.

Tkinter, Canvas. Нарисовать спираль

#python #python_3x #tkinter


Здравствуйте! Подскажите, пожалуйста, как правильно нарисовать спираль?
Написал такую пародию на код 

from tkinter import *

root = Tk()

canvas = Canvas(root, width=300, height=300)
canvas.pack()
step = 10
p = [145, 145, 155, 155]
iters = 0
canvas.create_arc(p[0], p[1], p[2], p[3], start=0, extent=180, style=ARC)

while p[0] > 10:
    if (iters % 2):
        canvas.create_arc(p[0] - step, p[1] - step, p[2], p[3],
                          start=0, extent=180, style=ARC)
        p[0], p[1] = p[0] - step, p[1] - step
    else:
        canvas.create_arc(p[0], p[1], p[2] + step, p[3] + step,
                          start=0, extent=-180, style=ARC)
        p[2], p[3] = p[2] + step, p[3] + step
    iters += 1
root.mainloop()

На выходе получаю такое:

 

Вопрос в том как избавиться от этой щели? 

Я пробовал играться с аргументами start и extent, но какого-либо качественного результата
это не дало, потому что две части не совпадали друг с другом, т.е. что-то типо этого:



^ Тут я от балды ввел значения, попиксельно не подгоняя. 

Сталкивался ли кто с такой проблемой? 
    


Ответы

Ответ 1



На каждом шаге нужно расширять эллипс (частью которого является отрисовываемая дуга) вверх и вниз одинаково. Если делать как у вас сейчас, то центры четных и нечетных эллипсов будут смещаться вверх и вниз от горизонтальной оси, из-за чего и получается зазор между верхней и нижней частью. Рабочий код: from tkinter import * root = Tk() canvas = Canvas(root, width=300, height=300) canvas.pack() step = 10 p = [145, 145, 155, 155] iters = 0 canvas.create_arc(*p, start=0, extent=180, style=ARC) while p[0] > step: p[1] -= step/2 # расширение вниз p[3] += step/2 # расширение вверх if iters % 2: p[0] -= step # расширение и сдвиг левой границы влево canvas.create_arc(*p, start=0, extent=180, style=ARC) else: p[2] += step # расширение и сдвиг правой границы вправо canvas.create_arc(*p, start=0, extent=-180, style=ARC) iters += 1 root.mainloop()

воскресенье, 29 декабря 2019 г.

Как записать этот код элегантнее?

#python #инспекция_кода #tkinter


from tkinter import *

root=Tk()
root.title("Нумерация")

flats = []
flats1 = []
flats2 = []
flats3 = []



def addFlat():


    kom_kv=Label(root,text='Введите номер квартиры с несколькими собственниками:')
    kom_kv.grid()
    kom_kv1=Entry(root)
    kom_kv1.grid()
    sobs=Label(root,text='Введите колличество собственников:')
    sobs.grid()
    sobs1=Entry(root)
    sobs1.grid()

    flats.append(kom_kv)
    flats1.append(kom_kv1)
    flats2.append(sobs)
    flats3.append(sobs1)

def deleteFlat():
    if flats!=0 and flats1!=0 and flats2!=0 and flats!=0: 
        flats.pop().grid_forget()
        flats1.pop().grid_forget()
        flats2.pop().grid_forget()
        flats3.pop().grid_forget()

plus=Button(root,text='Добавить квартиру', command=addFlat)
plus.grid()

plus2=Button(root,text='Удалить квартиру', command=deleteFlat)
plus2.grid()

root.mainloop()

    


Ответы

Ответ 1



1. Один список вместо четырех Вместо параллельных списков используем один список из объектов. Условно это можно представить как таблицу с несколькими строками. Каждая строка может быть представлена как: кортеж (tuple), список, словарь (dict), namedtuple, объект своего класса, dataclass (Python 3.7 и новее). Преимущество списка перед своим контейнерным типом (вариант @RafaelOsipov): не нужно самому реализовывать велосипед все свойства и методы, которые могут пригодиться, а просто используем уже готовый встроенный тип, который все умеет, и может хранить все что в него положишь. Теперь по поводу типов данных, которые мы можем использовать для хранения "строк" таблицы внутри списка: Минус кортежей и списков: поля кортежа и элементы списка не имеют имен, можно что-то случайно перепутать, если записать не в том порядке. Минус словаря: в словаре может быть больше полей или меньше чем нужно, у полей нет определенного порядка. Минус namedtuple: namedtuple (кортеж с именованными полями) - это неизменяемый тип (как и обычный кортеж), т.е. значения в нем нельзя изменить, только создать новый другой кортеж с другими значениями. Но если значения в строке таблицы не планируется изменять, то это вполне рабочий вариант. Минус своего класса: нужно полностью описывать поведение класса (например, инициализацию значений, строковое представление, методы сравнения на равенство или порядок). dataclass в этом плане удобнее - просто описываются поля класса, а большая часть полезных методов генерируется автоматически. Вариант с dataclass всем прекрасен, но лично я пока не перешел на Python 3.7 :) Кстати да, можно сгруппировать элементы управления в фрейм и хранить в списке фреймы. Но это сделает более сложным процесс извлечения данных из элементов управления, например, если вы захотите сохранить данные в файл. Пример с кортежем: flats = [] def addFlat(): ... flats.append((kom_kv, kom_kv1, sobs, sobs1)) def deleteFlat(): if flats: kom_kv, kom_kv1, sobs, sobs1 = flats.pop() kom_kv.destroy() kom_kv1.destroy() sobs.destroy() sobs1.destroy() Элементы управления в tkinter лучше удалять с помощью destroy, а не с помощью grid_forget, если вы не собираетесь потом возвращать их обратно. Все элементы управления "удаленные" через grid_forget остаются висеть в памяти до закрытия программы. 2. Отделение данных от графического интерфейса У вас данные хранятся прямо в окне. Желательно так не делать, а хранить данные во внутреннем представлении, а уже при необходимости их вывода создавать элементы управления. В качестве бонуса у вас не будет такой жесткой привязки к тому же tkinter. А внутреннее представление можно уже будет перевести в любое внешнее представление: с помощью другой библиотеки GUI, на консоли, в файле, в базе данных. 3. Таблица для отображения данных вместо кучи текстовых полей Можно оставить фиксированное количество текстовых полей (2 в вашем случае) для ввода данных и таблицу для отображения данных. Представьте, что вы вводите данные о многоквартирном доме: всего лишь 5 подъездов по 5 этажей по 4 квартиры на лестничной площадке и по 4 элемента управления = 400 элементов управления уезжают далеко вниз за границы экрана. Можно прикрутить полосу прокрутки, но это будет довольно костыльно. Минимальный пример с таблицей: from tkinter import * import tkinter.ttk as ttk root=Tk() root.title("Нумерация") def addFlat(): flat_number = entry_flat_number.get() owner_number = entry_owner_number.get() table.insert('', 'end', values=(flat_number, owner_number)) def deleteFlat(): selected = table.selection() if selected: table.delete(selected) Label(root,text = 'Введите номер квартиры с несколькими собственниками:').grid() entry_flat_number = Entry(root) entry_flat_number.grid(row=0, column=1) Label(root,text='Введите количество собственников:').grid() entry_owner_number = Entry(root) entry_owner_number.grid(row=1, column=1) Button(root,text='Добавить квартиру', command=addFlat).grid() Button(root,text='Удалить выбранные квартиры', command=deleteFlat).grid(row=2, column=1) table = ttk.Treeview(root) table.grid(columnspan=2, sticky='nswe') table['columns'] = ['flat_number', 'owner_number'] table.column('#0', width=0) table.heading('#1', text='Номер квартиры') table.heading('#2', text='Количество собственников') root.mainloop()

Ответ 2



Вместо того, чтобы держать несколько параллельно обрабатываемых массивов, сделайте класс Flat, в котором будут поля: Номер квартиры Количество собственников Затем сделайте класс FlatCollection, внутри которого и будет массив объектов класса Flat и у которого будут методы addFlat и deleteFlat Затем создавайте экземпляр класса FlatCollection и добавляйте/удаляйте объекты класса Flat в него. Так будет намного элегантнее. Небольшое дополнение: Согласно PEP-8 методы классов в пайтоне принято именовать не в camelCase а в snakeCase. То есть вместо addFlat и deleteFlat принято называть методы add_flat и delete_flat.

понедельник, 9 декабря 2019 г.

Как сделать мигание на панели задач?

#python #python_3x #windows #winapi #tkinter


Есть программа на подобие чата. Пользователей около 5. Обмен информацией не постоянный,
то есть пишут по необходимости и могут быть большие временные перерывы. Хочу сделать
так, чтобы после того как кто-то написал сообщение можно было привлечь внимание других
пользователей у которых запущена программа. Звук без вариантов (колонок нет). В идеале
было бы мигание программы на панели. Программа будет работать на windows server 2008 r2.
В гугле не могу найти ничего применимого к Python (у меня 3.4).
Это моя первая программа.

# -*- coding: utf-8 -*-
import tkinter
from tkinter import *
from tkinter import messagebox as mb
import time

name = str()
fname = str(time.strftime("%d_%m_%Y"))

def messange():
    global mdname
    if entry.get() < '0':
        mb.showerror('Ошибка', 'Вы не написали сообщение, сообщение не должно начинаться
с пробела') # Ошибка
    else:
        mess = entry.get()
        entry.delete(0, END)
        f = open(fname + '.txt', 'a')
        f.write(time.strftime("%H:%M") + ' ' + name + ' пишет: ' + mess + '\n')
        f.close()

def login(event):
    global name
    name = entry.get()
    if name < '0':
        mb.showerror('Ошибка', 'Напишите ваше имя, имя не должно начинаться с пробела')
# Ошибка
    else:
        event.widget.pack_forget() # Скрыть скнопку
        name = entry.get()
        entry.delete(0, END)
        f = open(fname + '.txt', 'a')
        f.write('Привет ' + name + '\n')
        f.close()
        button_visible_false.pack() # Отобразить кнопку
        while True:
            f = open(fname + '.txt')
            data = f.read()
            time.sleep(0.3)
            f.close()
            textbox.delete(1.0, END)
            textbox.insert(1.0, data)
            textbox.see("end")
            root.update_idletasks()
            root.update()

root = Tk()
root.title('Гармоничный чат v.1')

panelFrame = Frame(root, height = 60, bg = 'green')
textFrame = Frame(root, height = 340, width = 600)
panelFrame.pack(side = 'top', fill = 'x')
textFrame.pack(side = 'bottom', fill = 'both', expand = 1)

textbox = Text(textFrame, font='Arial 10', wrap='word')
scrollbar = Scrollbar(textFrame)

scrollbar['command'] = textbox.yview
textbox['yscrollcommand'] = scrollbar.set

textbox.pack(side = 'left', fill = 'both', expand = 1)
scrollbar.pack(side = 'right', fill = 'y')

entry = Entry(width = 80)
entry.pack(pady = 10)

btn1 = Button(panelFrame, text='Напишите ваше имя', width=17, height=3, bg="white",
fg="black")
btn1.bind('', login)
btn1.pack()
button_visible_false = tkinter.Button(panelFrame, text='Написать сообщение', width=17,
height=3, bg="white", fg="black", command = messange)

try:
    f = open(fname + '.txt')
except:
    f = open(fname + '.txt', 'w')
else:
    pass

root.mainloop()

    


Ответы

Ответ 1



Для мигания можно использовать функцию FlashWindow: import ctypes import tkinter as tk root = tk.Tk() def flash_window(): hwnd = int(root.wm_frame(), 16) # wm_frame() возвращает хэндл окна в виде строки в 16-ричной системе, переводим в число ctypes.windll.user32.FlashWindow(hwnd, True) root.after(1000, flash_window) root.mainloop() Активное окно мигнет один раз, свернутое подсветится (по крайней мере на Windows7 работает так). Не обязательно нужно делать через after, достаточно чтобы функция flash_window вызывалась откуда-то из обработчика событий, уже после запуска root.mainloop(). Чтобы мигнуть несколько раз, можно использовать FlashWindowEx (это немного сложнее из-за того, что данные в функцию передаются через структуру FLASHINFO): import ctypes import tkinter as tk class FLASHWINFO(ctypes.Structure): _fields_ = [ ("cbSize", ctypes.c_uint), ("hwnd", ctypes.c_uint), ("dwFlags", ctypes.c_uint), ("uCount", ctypes.c_uint), ("dwTimeout", ctypes.c_uint) ] # Codes for dwFlags FLASHW_ALL = 3 # Flash both the window caption and taskbar button. This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. FLASHW_CAPTION = 1 # Flash the window caption. FLASHW_STOP = 0 # Stop flashing. The system restores the window to its original state. FLASHW_TIMER = 4 # Flash continuously, until the FLASHW_STOP flag is set. FLASHW_TIMERNOFG = 0xC # Flash continuously until the window comes to the foreground. FLASHW_TRAY = 2 # Flash the taskbar button. def __init__(self, hwnd, dwFlags, uCount, dwTimeout): super().__init__() self.cbSize = ctypes.sizeof(self) self.hwnd = hwnd self.dwFlags = dwFlags self.uCount = uCount self.dwTimeout = dwTimeout root = tk.Tk() def flash_window(): hwnd = int(root.wm_frame(), 16) flashinfo = FLASHWINFO(hwnd, FLASHWINFO.FLASHW_ALL, 3, 100) ctypes.windll.user32.FlashWindowEx(ctypes.byref(flashinfo)) root.after(1000, flash_window) root.mainloop()

воскресенье, 26 мая 2019 г.

Tkinter, Canvas. Нарисовать спираль

Здравствуйте! Подскажите, пожалуйста, как правильно нарисовать спираль? Написал такую пародию на код
from tkinter import *
root = Tk()
canvas = Canvas(root, width=300, height=300) canvas.pack() step = 10 p = [145, 145, 155, 155] iters = 0 canvas.create_arc(p[0], p[1], p[2], p[3], start=0, extent=180, style=ARC)
while p[0] > 10: if (iters % 2): canvas.create_arc(p[0] - step, p[1] - step, p[2], p[3], start=0, extent=180, style=ARC) p[0], p[1] = p[0] - step, p[1] - step else: canvas.create_arc(p[0], p[1], p[2] + step, p[3] + step, start=0, extent=-180, style=ARC) p[2], p[3] = p[2] + step, p[3] + step iters += 1 root.mainloop()
На выходе получаю такое:

Вопрос в том как избавиться от этой щели?
Я пробовал играться с аргументами start и extent, но какого-либо качественного результата это не дало, потому что две части не совпадали друг с другом, т.е. что-то типо этого:

^ Тут я от балды ввел значения, попиксельно не подгоняя.
Сталкивался ли кто с такой проблемой?


Ответ

На каждом шаге нужно расширять эллипс (частью которого является отрисовываемая дуга) вверх и вниз одинаково. Если делать как у вас сейчас, то центры четных и нечетных эллипсов будут смещаться вверх и вниз от горизонтальной оси, из-за чего и получается зазор между верхней и нижней частью. Рабочий код:
from tkinter import *
root = Tk()
canvas = Canvas(root, width=300, height=300) canvas.pack() step = 10 p = [145, 145, 155, 155] iters = 0 canvas.create_arc(*p, start=0, extent=180, style=ARC)
while p[0] > step: p[1] -= step/2 # расширение вниз p[3] += step/2 # расширение вверх
if iters % 2: p[0] -= step # расширение и сдвиг левой границы влево canvas.create_arc(*p, start=0, extent=180, style=ARC) else: p[2] += step # расширение и сдвиг правой границы вправо canvas.create_arc(*p, start=0, extent=-180, style=ARC)
iters += 1
root.mainloop()

понедельник, 20 мая 2019 г.

Общие параметры

Можно ли задать параметры для всех Button/text/Label одной функцие, чтобы не писать к каждой кнопке одинаковые параметры? К примеру, я создал много однотипных кнопок для ввода данных калькулятора:
bttn1_1 = Button(root, text='MC', font='Arial 15', width=3, height=1, bd=4, background='SkyBlue2', activebackground='green yellow')


Ответ

Примерно так можно:
buttons = {}
for button_text in map(str, range(10)): button = Button(root, text=button_text, font='Arial 15', width=3, height=1, bd=4, background='SkyBlue2', activebackground='green yellow') buttons[button_text] = button
Кнопка с текстом 1
buttons['1']

Еще можно создать функцию которая обрезает/исправляет параметры:
def create_button(text): return Button(root, text=text, font='Arial 15', width=3, height=1, bd=4, background='SkyBlue2', activebackground='green yellow')

пятница, 5 апреля 2019 г.

Мультизадачность на Python: выполнить две долгие функции одновременно, не блокируя GUI

Всем доброго времени суток. Изучаю возможности графического модуля tkinter на Python. Возникла необходимость одновременного запуска нескольких функций, т.е. имеется, например, две кнопки, каждая со своим функционалом. Нужно сделать так, чтобы окно с кнопками после нажатия на одну из них не зависало и позволяло нажать другую кнопку, незамедлительно запустив ее функцию. Пишу на питоне версии 3.5.2.
from tkinter import *
def whilefunc1(): n = 1 while (n <= 100000): print(n) n = n + 1
def whilefunc2(): n = 100001 while (n <= 200000): print(n) n = n + 1
root = Tk() root.geometry("200x200")
but1 = Button(root, text="press me", command = whilefunc1) but1.pack() but2 = Button(root, text="me 2!!", command = whilefunc2) but2.pack()
root.mainloop()


Ответ

Общая особенность GUI программирования в том, что не следует использовать блокирующие функции в обработчиках событий, которые выполняются как правило в GUI потоке, иначе это приводит к "подвисанию" GUI.
В вашем случае вместо self.status[ip] = os.system(f'ping {ip}') можно использовать self.process[ip] = subprocess.Popen(['ping', ip]) и позже проверять статус используя self.process[ip].poll() (оба вызова возвращаются сразу, не дожидаясь пока команда завершится). Периодический опрос можно организовать используя root.after(), например как в start_process(). Можно избежать опроса, если настроить обработчик сигнала, который вызывается когда запущенный дочерний процесс завершается, например, на Unix (усложнённая опция в данном случае). Cross-platform пример кода это QProcess.finished сигнал из Qt GUI библиотеки, который позволяет подключить свой обработчик, который вызывается, когда команда, запущенная QProcess::start, завершается (finished сигнал реализован эффективно, используя функциональность, предоставляемую соответствующей операционной системой, не требуя периодического опроса статуса запущенного дочернего процесса).
Альтернативно, если необходимо запустить числодробительную задачу (CPU-bound), то для этого можно создать пул процессов:
from concurrent.futures import ProcessPoolExecutor
pool = ProcessPoolExecutor()
и использовать command=lambda: pool.submit(whilefunc1) в качестве обработчика событий для кнопки (.submit() ставит выполнение whilefunc1() функции в очередь и сразу же возвращается). Если вы нажмёте 1000 раз на кнопку, то вы скорее всего не хотите запускать 1000 процессов (системе вероятно плохо будет). ProcessPoolExecutor() по умолчанию использует все доступные CPU ядра, не запуская излишние процессы. В этом смысле, это решение более предпочтительно чем решения, использующие threading.Thread(..).start() или multiprocessing.Process(..).start(), где каждое нажатие на кнопку приводит к запуску нового потока/процесса.
Если задача связана с вводом-выводом, то можно использовать Widget.tk.createfilehandler() или аналогичную функциональность в других GUI библиотеках (например, GObject.io_add_watch()). Вот пример кода, где root.createfilehandler() используется чтобы читать вывод внешней команды, не дожидаясь её завершения.
Поддержка createfilehandler() может быть ограничена на некоторых платформах (Windows), поэтому ту же задачу можно переносимо выполнить используя фоновый поток (попытка использовать socketpair, чтобы получить переносимый код, оказалась неуспешной: Popen() не принимает соответствующий fileno() на Windows).

четверг, 4 апреля 2019 г.

Как сделать постоянное обновление окна Tkinter? Как избежать подвисания на время ожидания ответа от сервера

Всем привет! для обновления окна использую
root.update()
Но если программа делает например запрос к серверу и долго не получает ответ- программа подвисает на время ожидания ответа. Как этого избежать и сделать так, чтобы программа была всегда активна? Спасибо! пример кода:
root=tk.Tk() def work (): requests.get(url) time.sleep(5) work() root.mainloop()


Ответ

Никогда никогда не используйте time.sleep(5) внутри цикла событий -- это замораживает ваше GUI на ~5 секунд. Используйте root.after(5000, some_action), чтобы выполнить код через 5 секунд в Tkinter.
Чтобы выполнить работу, которая требует не фиксированное время (такую как отправка/чтение данных из сети, используя requests.get()), следует либо вынести в фоновый поток (threading), либо встроить операции ввода/вывода в сам цикл событий (Widget.tk.createfilehandler()). Примеры кода по ссылкам можно посмотреть: Мультизадачность на Python: выполнить две долгие функции одновременно, не блокируя GUI Конкретно, вот пример для tkinter, который читает вывод из внешней команды (self.proc.stdout.readline()), не блокируя цикл событий:
вариант с фоновым потоком вариант без потока с createfilehandler()
Вот полный пример кода, который запускает requests.get() в фоновом потоке и обновляет показ текущего времени каждые 100 миллисекунд, пока запрос не завершится:
#!/usr/bin/env python3 import time import tkinter from datetime import datetime from queue import Empty, Queue from threading import Thread import requests
def display_result(label, q): try: label['text'] = q.get(block=False) except Empty: # update time at 100ms boundary label.after(round(100 - (1000 * time.time()) % 100), display_result, label, q) label['text'] = str(datetime.now().strftime("%H:%M:%S.%f")[:-3])
def get_result(q): # blocking function q.put('ip=' + requests.get('https://httpbin.org/delay/3').json()['origin'])
# get result in a background thread result_queue = Queue() Thread(target=get_result, args=[result_queue], daemon=True).start()
# display result root = tkinter.Tk() label = tkinter.Label(font=(None, 100)) label.pack() display_result(label, result_queue)
# center window root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id())) root.mainloop()
Чтобы обновить надпись (tkinter.Label) из другого потока, самый безопасный метод это использовать queue.Queue, как показано выше (то есть обращаться к label, только из потока с root.mainloop(), который её создал). Но если показывать прогресс не нужно (надпись прежняя остаётся пока блокирующий запрос не завершится), то в простых случаях можно обратиться и из другого потока напрямую:
#!/usr/bin/env python3 import tkinter from threading import Thread import requests
def fetch(url, on_response): requests.get(url, hooks=dict(response=on_response)) # blocking function
def on_response(response, **unused_kwargs): # NOTE: it is called from another thread label['text'] = response.json()['origin']
# get result in a background thread root = tkinter.Tk() label = tkinter.Label(font=(None, 100), text='no result yet') label.pack() url = 'https://httpbin.org/delay/3' Thread(target=fetch, args=[url, on_response], daemon=True).start() root.mainloop()
Современная реализация tkinter модуля в CPython пытается поддерживать обращение к tkinter объектам из других потоков. К примеру, если не включены потоки в Tcl интерпретаторе, то mutex расставляются, чтобы только один поток имел доступ к Tcl интерпретатору одновременно. Если включены потоки (tkinter.Tcl().eval('info exists ::tcl_platform(threaded)') == '1'), то обращения к Tcl из других потоков в очередь автоматически внутри tkinter добавляются и выполняются, когда управление вернётся на поток, где запущен Tcl интерпретатор (see all the gory details). Возможны баги, поэтому при возникновении любых проблем, обычно проще использовать упрощённую модель: все обращения к GUI объектам только в GUI потоке производятся.

понедельник, 25 марта 2019 г.

как обновлять график matplotlib в tkinter раз в 5 минут данными из базы данных?

Что нужно: Каждые 5 секунд, считываются координаты из БД и отображаются на графике matplotlib в окне tkinter.
Что сделано: рисуются только первый набор данных и не рисуются второй набор
ВОПРОС: как исправить чтобы рисовался сначала первый набор данных , а через 5 секунд второй?
Вот обновленный минимальный рабочий код:
from threading import Thread from queue import Empty, Queue import time import tkinter as tk from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg#, NavigationToolbar2TkAgg from matplotlib.figure import Figure
class tkChartGUI(tk.Frame):
def __init__(self, parent): tk.Frame.__init__(self, parent) self.parent = parent self.initUI()
def get_latest_data(self, dataid): x_array=[] y_array=[] if (dataid == 1): x_array=[1,2,3,4,5,6,7,8,9,10,11,12,13,14]; y_array=[0.5,0.7,0.3,1.0,0.6,0.9,0.5,0.2,0.1,0.5,0.33,0.55,0.3,0.6] if (dataid == 2): x_array=[1,2,3,4,5,6,7,8,9,10,11,12,13,14]; y_array=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1,1.1,1.2,1.3] return (x_array, y_array)
def initUI(self): self.parent.title("Simple chart") self.parent.geometry("800x600+300+100")
result_queue = Queue() Thread(target=self.get_latest_data, args=[result_queue], daemon=True).start()
x_array, y_array = self.get_latest_data(1)
f = Figure(figsize=(5, 3), dpi=150) a = f.add_subplot(111) a.set_xlabel("Values_X") a.set_ylabel("Values_Y") a.yaxis.grid(True, which='major') a.xaxis.grid(True, which='major') a.plot(x_array, y_array)
canvas = FigureCanvasTkAgg(f, master=self.parent) canvas.show() canvas.get_tk_widget().grid(row=0,column=0)
def display_result(a, q): x_array = [] y_array = [] try: x_array = q.get(block=False) # get data y_array = q.get(block=False) a.plot(x_array, y_array) canvas.draw() except Empty: #a.clear() timeout_millis = round(100 - (5000 * time.time()) % 100) self.parent.after(timeout_millis, display_result, a, q)
def get_result(q): x_array, y_array = self.get_latest_data(2) q.put(x_array) # put data in FIFO queue x coords array q.put(y_array) # put data in FIFO queue y coords array
display_result(a, result_queue)
def onExit(self): self.quit()
def main(): root = tk.Tk() my_gui = tkChartGUI(root) root.mainloop()
if __name__ == '__main__': main()


Ответ

Чтобы постоянно не считывать данные из базы данных (polling), можно определить trigger, чтобы вызвать функцию (callback), когда в БД интересное событие произойдёт. К примеру, когда в нужную таблицу новое значение добавляется (Launch a Python Script from a sqlite3 Trigger):
CREATE TRIGGER tt AFTER INSERT ON t BEGIN SELECT got_y(NEW.y); END
Обновления графика происходят в got_y() обратном вызове, определённом в make_callback() ниже. Код похож на ответ c loop(), определённой с помощью .after(), с той разницей, что здесь функция вызывается, когда нужные данные готовы, вместо того чтобы периодически непрерывно новые данные запрашивать без блокировки:
#!/usr/bin/env python3 import datetime as DT import sqlite3 import random import threading import time import tkinter as tk from collections import deque
import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.dates import date2num
def generate_data(db): # generate dummy data for the example while True: time.sleep(.1) db.execute('insert into t values(?)', (random.randrange(100),))
def make_callback(root): # plot something # see @MaxU answer https://ru.stackoverflow.com/q/801923/23044 n = 300
def get_t(): return date2num(DT.datetime.now()) xx = deque([get_t()], maxlen=n) yy = deque([0], maxlen=n) fig, ax = plt.subplots() ax.set_ylim(0, 100) line, = ax.plot_date(xx, yy, marker='') format_time = '{:%Y-%m-%d %H:%M:%S}'.format time_text = ax.text(0.5, 0.9, '', transform=ax.transAxes)
# add to GUI canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea. canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
def got_y(y): # update plot xx.append(get_t()) yy.append(int(y)) line.set_data(xx, yy) ax.fill_between(xx, 0, yy, color='lightgrey') time_text.set_text(format_time(DT.datetime.now())) ax.relim() # update axes limits ax.autoscale_view(scaley=False) canvas.draw() return got_y
def main(): root = tk.Tk() # a dummy db for the example db = sqlite3.connect(':memory:', check_same_thread=False) db.execute('create table t(y)') db.create_function('got_y', 1, make_callback(root)) db.execute('CREATE TRIGGER tt AFTER INSERT ON t BEGIN SELECT got_y(NEW.y); END') threading.Thread(target=generate_data, args=[db], daemon=True).start() root.mainloop()
if __name__ == '__main__': main()
Здесь db.create_function() определяет какая Питон-функция используется в качестве got_y(y) обратного вызова. В примере, callback вызывается в дочернем потоке, что не всегда желаемо. Поток с generate_data() используется только для примера, фактически данные могут вставляться в базу данных из другого процесса или других машин. How to receive automatic notifications about changes in tables?

Для сравнения вот вариант с polling, где база данных постоянно опрашивается в фоновом потоке, используя простой цикл (про достоинства и недостатки цикла подробно описано в Как правильно сделать временный цикл?)
def poll_db(interval=5): while True: time.sleep(interval - time.time() % interval) # avoid drift emit(get_y_from_db())
здесь get_y_from_db() делает запрос к базе данных, а emit() генерирует событие для GUI:
#!/usr/bin/env python3 import random import threading import time import tkinter as tk
def get_y_from_db(): # generate dummy data time.sleep(random.random()) # emulate blocking function return random.randrange(100)
def poll_db(emit, interval=5): while True: time.sleep(interval - time.time() % interval) # avoid drift emit(get_y_from_db())
root = tk.Tk() root.bind('<>', lambda e, f=make_callback(root): f(e.y)) # subscribe threading.Thread(target=poll_db, args=[lambda y: root.event_generate('<>', when='tail', y=y)], daemon=True).start() root.mainloop()
Пример кода (где этот метод также используется): вывод процесса показывается в Tkinter GUI с помощью root.event_generate()

Не на всех реализациях можно root.event_generate() в фоновом потоке вызвать. В таких случаях можно использовать queue, чтобы данные между потоками передавать:
#!/usr/bin/env python3 import datetime as DT import random import threading import time import tkinter as tk from collections import deque from queue import Empty, Queue from time import time as timer
import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.dates import date2num
def get_y_from_db(): # generate dummy data time.sleep(random.random()) # emulate blocking function return random.randrange(100)
def poll_db(emit, interval=1): # NOTE: interval is independant from the delay while True: time.sleep(interval) emit(get_y_from_db())
def start_polling_loop(root, q, delay): # plot something # see @MaxU answer https://ru.stackoverflow.com/q/801923/23044 n = 600
def get_t(): return date2num(DT.datetime.now()) xx = deque([get_t()], maxlen=n) yy = deque([0], maxlen=n) fig, ax = plt.subplots() ax.set_ylim(0, 100) line, = ax.plot_date(xx, yy, marker='') format_time = '{:%Y-%m-%d %H:%M:%S}'.format time_text = ax.text(0.5, 0.9, '', transform=ax.transAxes)
# add to GUI canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea. canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# update in a loop def loop(): timeout_millis = round(delay - (1000 * timer()) % delay) root.after(timeout_millis, loop) # avoid drift
try: y = q.get(block=False) except Empty: return # no new data
# update plot xx.append(get_t()) yy.append(y) line.set_data(xx, yy) ax.fill_between(xx, 0, yy, color='lightgrey') time_text.set_text(format_time(DT.datetime.now())) ax.relim() # update axes limits ax.autoscale_view(scaley=False) canvas.draw() root.after_idle(loop) # start
root = tk.Tk() q = Queue() threading.Thread(target=poll_db, args=[q.put], daemon=True).start() start_polling_loop(root, q, delay=40) root.mainloop()
Упрощённый вариант (с одним обновлением данных из потока), см. в Как сделать постоянное обновление окна Tkinter? Как избежать подвисания на время ожидания ответа от сервера. Ещё пример кода (где этот метод используется): вывод процесса показывается в Tkinter GUI, используя widget.after(), q.get()/q.put()

понедельник, 4 марта 2019 г.

Функция блокировка изменения размера окна в Qt

Существует ли функция в PyQt5 блокировка расширения окна как в tkinter .resizable(False, False)?
Пример:
import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import *
app = QApplication(sys.argv) root = QMainWindow() root.resize(500, 700) root.setWindowTitle('Детские кружки') root.show()
sys.exit(app.exec())


Ответ

Функцией setFixedSize(x, y)
import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import *
app = QApplication(sys.argv) root = QMainWindow() root.setFixedSize(500, 700) root.setWindowTitle('Детские кружки') root.show()
sys.exit(app.exec())