Написать программу в которой будет две кнопки и поле для ввода имени, нажимая на первую кнопку пользователя поприветствуют, нажимаю на другую с пользователем попрощаются.
Работа с интерфейсом
Доработать предыдущую задачу так чтобы можно было выполнить любую операцию сложение/умножение/вычитание/деление.
Какой-то такой:
Написать программу с помощью которой можно открыть текстовый файл и показать его содержимое.
Добавить на форму дополнительную кнопку и два поля для ввода текста. И настроить кнопку так чтобы по нажатию на нее в первое поле для ввода выводилось самое длинное слово с текста на форма, а во второе самое короткое. Вот так:
Как делать задание 1
И так, чтобы создать приложение с интерфейсом в python достаточно написать следующее:
from tkinter import * # подключаем библиотечку для работы с интерфейсом
gui = Tk() # создаем интерфейс в памяти
gui.mainloop() # запускаем интерфейс и отображаем его пользователю
если это запустить увидим следующее:
тут важный момент — вот этот вызов функции gui.mainloop() должен быть обязательно в конце файла.
Так как это не всегда удобно отслеживать мы будем использовать так называемый процедурный подход. То есть то что будет отвечать за логику работы интерфейса мы вынесем в отдельную функцию, а сам блок создания интерфейса поместим в конец файла. Вот так:
from tkinter import *
# добавили новую функцию, я назвал ее my_setup, вы можете назвать ее как хотите
def my_setup(gui):
print("Настройка интерфейса") # в ней просто пока сообщение вывожу
gui = Tk() # создаем интерфейс в памяти
my_setup(gui) # <<< НОВАЯ СТРОЧКА, собственно вызов нашей функции
gui.mainloop() # запускаем интерфейс и отображаем его пользователю
если запустить, то все сработает как прежде, только еще и в консоль будет написано Настройка интерфейса
Вообще полезно понимать, как работает программа в таком случае. Вот последовательность вызова команд при таком коде
Добавляем поле для ввода
Дальше я буду писать код который у меня в my_setup. Вообще, сразу хочу предупредить что делать интерфейсы в большинстве языков программирования дело не благородное и требует написание не очень веселого кода. Например, прописывать размеры и расположение объектов на форме, так что сильно не пугайтесь.
Иногда для создания интерфейсов есть специальные редакторы, которые сильно упрощают процесс, но в тоже время требуют лишних настроек в коде. Вообще нормальные редакторы есть
- в языке C# для Windows Forms и так называемый WPF, к сожалению питоном не поддерживаются
- в языке С++, есть громадный фреймворк именуемый Qt, питоном поддерживается, но весит больше гигабайта, и требует написание большого количества неочевидного кода
- GTK, питоном поддерживается, но требует установки дополнительных библиотек
- ну и собственно все
tkinter – один из простейших фреймворков для разработки интерфейсов, но нормального редактора к сожалению, не имеет. Зато он встроен в питон и его можно сразу использовать. Так что придется запастись линейкой и бумажкой для расчетов.
И так, первое что хочется сделать это изменить размеры окна.
Для этого есть функция geometry где указываются размеры в пикселях:
def my_setup(gui):
gui.geometry("300x200")
получается такое окно:
теперь попробуем добавить на него поле для ввода. Делается это так:
def my_setup(gui):
gui.geometry("300x200")
txtName = Entry(gui) # создали поле для ввода
txtName.place(x=10,y=10, width=150) # поставили его на форму
надо помнить, что во всех интерфейсах центр координат располагается в левом верхнем углу, а ось y перевернута, поэтому если запустить, то получаем такое:
Добавляем кнопку
Давайте теперь добавим кнопку, кликая на которую будем получать приветствие:
def my_setup(gui):
gui.geometry("300x200")
txtName = Entry(gui)
txtName.place(x=10, y=10, width=150)
# у кнопки указываем свойство текст
button = Button(gui, text="Привет!")
# тут получается, что координату X считаю, как 10 пикселей отступ слева,
# + 150px -- ширина поля для ввода
# + 10px -- отступ от поля для ввода
button.place(x=10 + 150 + 10, y=10)
о как:
теперь попробуем добавить реакцию на клик.
Чтобы на кнопку можно было кликнуть и получить какую-то реакцию необходимо создать функцию которая будет вызываться. Делается это так:
from tkinter import *
# добавил новую функцию
def on_button_click():
print("Привет!")
def my_setup(gui):
gui.geometry("300x200")
txtName = Entry(gui)
txtName.place(x=10,y=10, width=150)
# добавил аргумент command, куда передал новую нашу новую функцию on_button_click
button = Button(gui, text="Привет!", command=on_button_click)
button.place(x=150 + 10 + 10, y=10)
# ...
запускаем и проверяем:
Считываем текст с поля для ввода
Допустим я хочу, чтобы мне выводился текст с поля для ввода, как мне это сделать?
У поля для ввода есть функция get() которая позволяет узнать что введено в поле, работает это так:
def on_button_click():
print(txtName.get()) # запрашиваю текст с поля
def my_setup(gui):
gui.geometry("300x200")
txtName = Entry(gui)
txtName.place(x=10,y=10, width=150)
button = Button(gui, text="Привет!", command=on_button_click)
button.place(x=150 + 10 + 10, y=10)
ну во всяком случае должно работать, на деле ж получаем:
ошибка выглядит так:
Exception in Tkinter callback
Traceback (most recent call last):
File "D:\_DISTR\WPy32-3760\python-3.7.6\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "D:\_DISTR\WPy32-3760\settings\.spyder-py3\temp.py", line 5, in on_button_click
print(txtName.get())
NameError: name 'txtName' is not defined
в ней сказано что функция on_button_click ничего не знает про txtName и поэтому не знает что ей делать. Оно и понятно почему.
txtName создается внутри функции my_setup и только внутри нее можно работать с этой переменной. Эта так называемая область видимость переменных. Каждая функция знает только про переменные, которые находятся внутри нее.
Чтобы обойти эту проблему мы можем запихать функцию внутрь функции, это не очень хороший подход, но на пару раз сойдет.
И так, утаскиваем on_button_click внутрь my_setup
# удаляем
# def on_button_click():
# print(txtName.get())
def my_setup(gui):
gui.geometry("300x200")
txtName = Entry(gui)
txtName.place(x=10,y=10, width=150)
# объявляю функцию теперь здесь, прямо перед созданием кнопки
def on_button_click():
print(txtName.get())
button = Button(gui, text="Привет!", command=on_button_click)
button.place(x=150 + 10 + 10, y=10)
проверяем:
Собственно, такие дела. Теперь добавьте еще одну кнопку и сделаете задание:
Написать программу в которой будет две кнопки и поле для ввода имени, нажимая на первую кнопку пользователя поприветствуют, нажимаю на другую с пользователем попрощаются.
Как делать задание 2
Рассмотрим еще одну компоненту, которую можно использовать просто для вывода значения на форме. Именуется она Label. То есть подпись или метка по-русски.
Возьмем болванку нашего кода:
from tkinter import *
def my_setup(gui):
gui.geometry("300x200")
gui = Tk()
my_setup(gui)
gui.mainloop()
и добавим label на форму:
def my_setup(gui):
gui.geometry("300x200")
label = Label(gui, text="Имя")
label.place(x=10, y=10)
получится текст без возможности редактирования:
теперь можно добавить поле для ввода, например,:
def my_setup(gui):
gui.geometry("300x200")
label = Label(gui, text="Имя")
label.place(x=10, y=10)
entry = Entry(gui)
entry.place(x=40, y=10, width=150)
вот что выйдет
главный неприятный момент здесь в том, что нам надо как-то угадывать длину Label, чтобы верно разместить поле для ввода. Ну, чтобы оно случайно не налезло на метку. На самом деле этого можно избежать. Для этого во все компоненты на форме встроен метод winfo_width
который позволяет узнать размер объекта на форме. Посмотрим, что он выдает:
def my_setup(gui):
gui.geometry("300x200")
label = Label(gui, text="Имя")
label.place(x=10, y=10)
print(label.winfo_width()) # добавил
# ...
напишет оно
“почему 1?” – спросите вы. А потому, что пока компонента не отобразилась на форме никто не может сказать какого она размера. Поэтому прежде чем вызвать метод winfo_width, надо обязательно попросить tkinter обновить форму. Для этого надо вызвать метод gui.update(). То есть так:
def my_setup(gui):
gui.geometry("300x200")
label = Label(gui, text="Имя")
label.place(x=10, y=10)
gui.update() # добавил
print(label.winfo_width())
entry = Entry(gui)
entry.place(x=40, y=10, width=150)
смотрим:
Ура! 30 пикселей – похоже на правду. Теперь можно более корректно расположить entry на форме:
def my_setup(gui):
gui.geometry("300x200")
label = Label(gui, text="Имя")
label.place(x=10, y=10)
gui.update()
print(label.winfo_width())
entry = Entry(gui)
# считаю теперь положение учитывая размеры label
entry.place(x=10 + label.winfo_width() + 10, y=10, width=150)
вообще, если так подумать, то сильно много кода надо писать, чтобы просто расположить поле для ввода на форме. А если еще и второе надо добавить, а если третье?
В общем чтобы жизнь нам упростить можно просто создать дополнительную функцию по созданию поля для ввода с меткой и использовать ее, если надо несколько полей. Попробуем это сделать:
# добавил функцию с 4-мя параметрами
def create_entry(x, y, label_text, entry_width):
pass
def my_setup(gui):
gui.geometry("300x200")
label = Label(gui, text="Имя")
label.place(x=10, y=10)
gui.update()
print(label.winfo_width())
entry = Entry(gui)
entry.place(x=10 + label.winfo_width() + 10, y=10, width=150)
теперь копируем в нее код по созданию метки:
def create_entry(x, y, label_text, entry_width):
# скопировал
label = Label(gui, text="Имя")
label.place(x=10, y=10)
gui.update()
print(label.winfo_width())
entry = Entry(gui)
entry.place(x=10 + label.winfo_width() + 10, y=10, width=150)
def my_setup(gui):
gui.geometry("300x200")
# а тут наоборот убрал
и заменяем в ней значения на параметры которые передаются в функцию:
def create_entry(x, y, label_text, entry_width):
label = Label(gui, text=label_text) # тут вставил label_text
label.place(x=x, y=y) # тут вставил x и y
gui.update()
#print(label.winfo_width()) убрал
entry = Entry(gui)
# тут вставил x, y и entry_width
entry.place(x=x + label.winfo_width() + 10, y=y, width=entry_width)
и теперь немного улучшим функцию, так чтобы она возвращала метку которая была создана:
def create_entry(x, y, label_text, entry_width):
label = Label(gui, text=label_text)
label.place(x=x, y=y)
gui.update()
entry = Entry(gui)
entry.place(x=x + label.winfo_width() + 10, y=y, width=entry_width)
gui.update() # еще раз обновим интерфейс
return entry # вернем метку
теперь можно эту функцию смело использовать для создания полей с метками:
def create_entry(x, y, label_text, entry_width):
label = Label(gui, text=label_text)
label.place(x=x, y=y)
gui.update()
entry = Entry(gui)
entry.place(x=x + label.winfo_width() + 10, y=y, width=entry_width)
gui.update() # еще раз обновим интерфейс
return entry # вернем метку
def my_setup(gui):
gui.geometry("300x200")
txtName = create_entry(10, 10, "Имя", 150)
получится так:
если вы не совсем понимаете, как срабатывает функция то вот вам схемка
то есть если немного упростить то, когда у вас есть функция и вы пишите
create_entry(10, 10, "Имя", 150)
то вызывается такой код:
def create_entry(10, 10, "Имя", 150):
label = Label(gui, text="Имя")
label.place(x=10, y=10)
gui.update()
entry = Entry(gui)
entry.place(x=10 + label.winfo_width() + 10, y=10, width=150)
gui.update()
return entry
то есть ваши параметры пробрасываются внутрь функции и заменяю соответствующие значения.
Плюс есть еще страшный return, который позволяет передать значение переменной созданной внутри функции во вне. То есть значение переменной entry
созданной внутри функции create_entry передается в переменную txtName внутри функции my_setup
. Для этого ее располагают слева от вызванной функции:
txtName = create_entry(10, 10, "Имя", 150)
Собственно, в чем прелесть этой функции? А в том, что я теперь могу быстро создать хоть 5 полей:
def my_setup(gui):
gui.geometry("300x200")
txtName = create_entry(10, 10, "Имя", 150)
# следите как я постепенно увеличиваю значения координаты y, для второго 40
txtSecondName = create_entry(10, 40, "Фамилия", 150)
txtPatronymic = create_entry(10, 70, "Отчество", 150) # двигаюсь по 30px, для третьего 70
txtGroup = create_entry(10, 100, "Группа", 150) # для четвертого 100
txtAge = create_entry(10, 130, "Возраст", 150) # для пятого 130
вот так получится:
кстати я могу сразу все эти поля заполнить:
def my_setup(gui):
gui.geometry("300x200")
txtName = create_entry(10, 10, "Имя", 150)
txtSecondName = create_entry(10, 40, "Фамилия", 150)
txtPatronymic = create_entry(10, 70, "Отчество", 150)
txtGroup = create_entry(10, 100, "Группа", 150)
txtAge = create_entry(10, 130, "Возраст", 150)
# добавил заполнение полей
txtName.insert(0, "Катя")
txtSecondName.insert(0, "Маргариткина")
txtPatronymic.insert(0, "Цветущая")
txtGroup.insert(0, "АМБ-20")
txtAge.insert(0, "17")
но, если я захочу значение поле поменять, то нельзя просто взять и написать новый insert
def my_setup(gui):
gui.geometry("300x200")
txtName = create_entry(10, 10, "Имя", 150)
txtSecondName = create_entry(10, 40, "Фамилия", 150)
txtPatronymic = create_entry(10, 70, "Отчество", 150)
txtGroup = create_entry(10, 100, "Группа", 150)
txtAge = create_entry(10, 130, "Возраст", 150)
txtName.insert(0, "Катя")
txtSecondName.insert(0, "Маргариткина")
txtPatronymic.insert(0, "Цветущая")
txtGroup.insert(0, "АМБ-20")
txtAge.insert(0, "17")
# пробую поменять
txtName.insert(0, "Оля")
получится вот так:
поэтому перед заменой значения надо очищать поле:
def my_setup(gui):
gui.geometry("300x200")
txtName = create_entry(10, 10, "Имя", 150)
txtSecondName = create_entry(10, 40, "Фамилия", 150)
txtPatronymic = create_entry(10, 70, "Отчество", 150)
txtGroup = create_entry(10, 100, "Группа", 150)
txtAge = create_entry(10, 130, "Возраст", 150)
txtName.insert(0, "Катя")
txtSecondName.insert(0, "Маргариткина")
txtPatronymic.insert(0, "Цветущая")
txtGroup.insert(0, "АМБ-20")
txtAge.insert(0, "17")
txtName.delete(0, END) # удаляю старый текст
txtName.insert(0, "Оля")
теперь все ок:
И так теперь обладая этими знаниями напишите программу:
Написать калькулятор который позволяет сложить два числа
Как делать задание 3
Как делать задание 4
Снова начнем с болванки кода, сделаем интерфейс чуть побольше
from tkinter import *
def my_setup(gui):
gui.geometry("600x400")
gui = Tk()
my_setup(gui)
gui.mainloop()
тут мы попробуем сразу две вещи
- Использование компоненты для работы с многострочным текстом
- Чтение данных из файла
Для многострочного текста используется компонента Text, добавляется она очень просто:
def my_setup(gui):
gui.geometry("600x400")
text = Text(gui) # создали компоненту в памяти
text.place(x=0, y=0, width=600, height=350) # поставили ее на форму
я установил ширину компоненты равной ширине формы, а высоту сделал чуть меньше, чтобы потом можно было добавить кнопочки какие-нибудь, получится так:
то есть вполне рабочая компонента, можно вставлять, писать, удалять текст. В общем по сути обыкновенный блокнот.
Создаем проект в Spyder
Давайте теперь настроим наш редактор Spyder так чтобы удобнее было работать с проектом. Так как я хочу читать файлик, а также чего-нибудь писать в него. Было бы удобно работать не с отдельным файлом, а с целой папкой. Делается это так:
Выбираем в меню
затем
жмем create
он скорее всего немного подумает, а затем слева откроется новая панелька с содержимым папки
тычем на имя папки и создаем новый файл
откроется новосозданный файлик
и теперь просто скопируем в него содержимое, нашего старого файлика
from tkinter import *
def my_setup(gui):
gui.geometry("600x400")
text = Text(gui)
text.place(x=0, y=0, width=600, height=350)
gui = Tk()
my_setup(gui)
gui.mainloop()
Создаем файл с текстом
Давайте теперь создадим еще один файлик, но просто текстовый:
я назову его text.txt
И загоним в него какой-нибудь текст
Червонные Король и Королева сидели на троне, а вокруг толпились остальные карты и множество всяких птиц и зверюшек. Перед троном стоял между двумя солдатами Валет в цепях. Возле Короля вертелся Белый Кролик - в одной руке он держал трубу, а в другой - длинный пергаментный свиток. Посередине стоял стол, а на столе - большое блюдо с кренделями. Вид у них был такой аппетитный, что у Алисы прямо слюнки потекли.
- Скорее бы кончили судить, - подумала она, - и подали угощение.
Особых надежд на это, однако, не было, и она начала смотреть по сторонам, чтобы как-то скоротать время.
Раньше Алиса никогда не бывала в суде, хотя и читала о нем в книжках. Ей было очень приятно, что все почти здесь ей знакомо.
- Вон судья, - сказала она про себя. - Раз в парике, значит судья.
Судьей, кстати, был сам Король, а так как корону ему пришлось надеть на парик, он чувствовал себя не слишком уверенно. К тому же это было не очень красиво.
и так подготовительные действия окончены
Добавляем кнопку на форму
Теперь давайте добавим кнопку с помощью которой можно будет прочитать содержимое файла и вывести его в наш Text.
Добавляем кнопку:
def my_setup(gui):
gui.geometry("600x400")
text = Text(gui)
text.place(x=0, y=0, width=600, height=350)
button = Button(gui, text="Прочитать файл")
button.place(x=10, y=360)
вот так получается:
она правда пока не работает.
Добавляем ей команду:
def my_setup(gui):
gui.geometry("600x400")
text = Text(gui)
text.place(x=0, y=0, width=600, height=350)
def button_click():
print("Тыкнул")
button = Button(gui, text="Прочитать файл", command=button_click)
button.place(x=10, y=360)
проверяем:
кнопка реагирует! =)
Теперь пропишем код с помощью которого можно прочитать содержимое файла, выглядит он немного хитро, пишем внутрь button_click:
def button_click():
with open("text.txt") as f: # эта страшная строчка означает открой файл text.txt и подключи файл к переменной f
print(f.read()) # f.read() -- значит прочитай содержимое файла и верни в качестве строки, ну а print это чтобы напечатать
проверяем:
ну, э… что-то на текст похожее вывел, но вот что именно не понятно… такое себе в общем
Кстати если у вас вывелось все ок, то можете пропустить следующий пункт
Чиним кодировку
На самом деле тут проблема в кодировке (в компьютере текст хранится в виде чисел, а кодировка определяет какому числу какую букву сопоставить). В windows стандартной считается кодировка cp1251, и питон именно ее и ожидает. В то же время все современные инструменты для программирования хранят текст в кодировке UTF8. Если открыть любой файл, то вот ее видно внизу будет:
поэтому чтобы символы приобрели смысл надо явно указать, что мы ожидаем, что в файле находится utf8 кодировка, делается это вот так:
def button_click():
with open("text.txt", encoding="utf8") as f:
print(f.read())
запускаем:
от теперь другое дело!)
Выводим содержимое файла на форму
Как я выше говорил, f.read() возвращает содержимое файла в виде строки, мы можем его спокойно загнать в переменную и потом вывести ее на форму, вот так:
def button_click():
with open("text.txt", encoding="utf8") as f: # открыл файл
content = f.read() # сохранил его содержимое в переменную content
text.insert("1.0", content) # вставил в компоненту текст
и сразу очевидный вопрос, что за 1.0?
Короче так как вставлять текст в многострочную компоненту это вам не работа с одной строкой, то и механизм немного сложнее. Вставляя текст надо указывать две координаты, номер строки и номер символа в строке.
Таким образом 1 – означает первую строку (причем нумерация идет с 1 для строк), 0 – означает нулевой символ в строке (тут нумерация с 0). Так как у нас текст на форме изначально отсутствует то на самом деле большого значения что указывать в качестве координаты значения не имеет.
В общем проверяем:
ура задание готово! =)