Шуман Х. - Python для детей - 2019. # Startwerte festlegen Red (255,0,0)
Скачать 5.95 Mb.
|
Глава Библиотека компонентов 8 152 Кнопки и ответы Ранее у нас было что-то подобное в игре-лотерее, где мы все включили в список. Сработает ли способ с компонента- ми? Попробуем, но давай сначала объявим пустой список. Knopka = [] Я не использую в коде слово Button, потому что это имя класса. В цикле списка ты можешь теперь генерировать шесть таких «кнопок»: for Nr in range(0,6) : Knopka.append(Button(Window, text=Answer[Nr])) После создания каждая кнопка должна быть добавлена в список с помощью функции append(). Я просто добавил еще один список, который нам нужно сгенерировать в на- чале программы: Answer = ["Супер", "Хорошо", "Так себе", "Плохо", "Ужасно", \ "Не скажу"] В другом цикле for мы организуем шесть кнопок (обе ин- струкции должны быть однострочными): for pos in range(0,3) : Knopka[pos].place(x=20, y=60+pos*40, width=120, height=30) Knopka[pos+3].place(x=160, y=60+pos*40, width=120, height=30) Это три строки, в каждой из которых есть пара кнопок. Зна- чения по оси x фиксированы, значения по оси y изменяют- ся в каждой строке. Поэтому счетчик pos учитывается, когда речь идет о вычислении позиции по оси y. На данный момент я не включил функции события, потому что это не так просто. Ты можешь оставить все как есть, на- значить функцию Knopka[0].config(command=buttonClick) и продублировать ее шесть раз (с параметром от 0 до 5). Я поместил параметр command в метод config, что вполне себе работает. Но здесь я бы предпочел цикл. Однако тогда нам сначала нужно сделать одну функцию для многих кнопок. Для этого нам нужен еще один список: Кнопки и ответы 153 Diagnose = ["Это здорово!", "Это радует!", "Все возможно.", \ "Это огорчает!", "Это плохо!", \ "Раз ты так думаешь..."] Здесь и в других похожих ситуациях ты можешь использовать символ \, чтобы разбить длинную инструкцию на две и более строк. Функция события выглядит следующим образом: def buttonClick(Nr) : Display.config(text=Diagnose[Nr]) И вот уже у нас возникла проблема. Сообщение об ошибке не появится, если мы расширим блок инструкций первого цикла for следующим образом: Knopka[Nr].config(command=buttonClick(Nr)) Однако начало программы вызывает разочарование: не- зависимо от того, какую кнопку я нажимаю, не появляется ответ. На самом деле мы не должны передавать какие-либо параметры функции buttonClick, но компьютер не ругается, а лишь игнорирует параметр. Как же использовать функцию buttonClick(), которая на- столько красива, с короткими параметрами? Ключевое сло- во lambda поможет нам в этом. Используется оно так: Knopka[Nr].config(command=lambda: buttonClick(Nr)) Если ты применишь его в первом цикле for, исходный код будет выглядеть так: for Nr in range(0,6) : Knopka.append(Button(Window, text=Answer[Nr])) Knopka[Nr].config(command=lambda: buttonClick(Nr)) Но даже это не приносит желаемого эффекта. Ты увидишь текст, когда нажмешь кнопку, но только последний вариант ответа: «Раз ты так думаешь...». Каждому параметру command нужен уникальный номер, по- этому у нас нет выбора, кроме как обойтись без цикла и на- значить определенное число каждой из шести кнопок: Глава Библиотека компонентов 8 154 Knopka[0].config(command=lambda: buttonClick(0)) Knopka[1].config(command=lambda: buttonClick(1)) Knopka[2].config(command=lambda: buttonClick(2)) Knopka[3].config(command=lambda: buttonClick(3)) Knopka[4].config(command=lambda: buttonClick(4)) Knopka[5].config(command=lambda: buttonClick(5)) В конце концов, мы смогли избежать проблем. Теперь я приведу исходный код целиком (⇒ hello4.py): # Приветствие с кнопками from tkinter import * # Константы текста Answer = ["Супер", "Хорошо", "Так себе", "Плохо", "Ужасно", \ "Не скажу"] Diagnose = ["Это здорово!", "Это радует!", "Все возможно.", \ "Это огорчает!", "Это плохо!", \ "Раз ты так думаешь..."] # Функция события def buttonClick(Nr) : Display.config(text=Diagnose[Nr]) # Основная программа Window = Tk() Window.title("Привет!") Window.config(width=300, height=190) Display = Label(Window, text="Как это сделать?") Display.place(x=80, y=10, width=160, height=30) # Кнопки Knopka = [] for Nr in range(0,6) : Knopka.append(Button(Window, text=Answer[Nr])) for pos in range(0,3) : Knopka[pos].place(x=20, y=60+pos*40, width=120, height=30) Knopka[pos+3].place(x=160, y=60+pos*40, width=120, height=30) # Индивидуальная настройка событий Knopka[0].config(command=lambda: buttonClick(0)) Knopka[1].config(command=lambda: buttonClick(1)) Knopka[2].config(command=lambda: buttonClick(2)) Knopka[3].config(command=lambda: buttonClick(3)) Knopka[4].config(command=lambda: buttonClick(4)) Knopka[5].config(command=lambda: buttonClick(5)) # Цикл событий Window.mainloop() Списки выбора 155 ¾ Лучше создай новый файл. Или сохрани изменения в файл под новым именем. Введи указанный исходный код и протестируй программу. Если хочешь, можешь по- пробовать реализовать такой функционал с помощью метода config в цикле. Списки выбора В поисках альтернативы кнопкам мы теперь немного поко- паемся в коллекции компонентов модуля tkinter. Без кно- пок наша программа может работать, если мы создадим список со всеми ответами, которые раньше были на кноп- ках. И тогда нам больше не понадобятся кнопки. Поэтому тебе нужно их удалить, либо ты можешь создать совершен- но новый файл и скопировать туда необходимые строки ис- ходного кода. Теперь мы имеем дело только с одним компонентом, кото- рый нам нужно создать: Box = Listbox(Window) Разумеется, сейчас этот список пуст, поэтому мы заполним его текстом: for Nr in range(0,6) : Box.insert(Nr, Answer[Nr]) Метод insert() принимает в качестве параметра сначала номер записи, затем текст ответа. Наконец, мы помещаем блок компонента и указываем его размеры: Box.place(x=30,y=50, width=200, height=150) (Тебе может понадобиться настроить высоту и ширину, осо- бенно если ты используешь другие, более длинные строки ответов.) Как связать список с функцией события? Здесь речь идет не о нажатии на кнопку, а о том, какой элемент был выбран. Поэтому мы используем совершенно другой метод: Box.bind("< Инструкция bind() привязывает событие к функции (или методу). Первый параметр определяет событие, которое не Глава Библиотека компонентов 8 156 только заключается в кавычки, но и оборачивается в двой- ные угловые скобки. Я переименовал функцию события: listboxSelect() (имя buttonClick() больше не подходит). И вот как выглядит эта функция сейчас: def listboxSelect(event) : Select = Box.curselection() Nr = Select[0] Display.config(text=Diagnose[Nr]) Здесь необходим параметр, который принимает событие (английское слово event). Сначала функция определяет, ка- кая запись была выбрана (отмечена или нажата): Select = Box.curselection() Результат – это не число, а выражение, из которого число сначала должно быть «выделено». Мы делаем это следую- щим образом: Nr = Select[0] Теперь можно отобразить соответствующий текст ответа. Давай рассмотрим исходный код целиком (⇒ hello5.py): from tkinter import * # Константы текста Answer = ["Супер", "Хорошо", "Так себе", "Плохо", "Ужасно", \ "Не скажу"] Diagnose = ["Это здорово!", "Это радует!", "Все возможно.", \ "Это огорчает!", "Это плохо!", \ "Раз ты так думаешь..."] # Функция события def listboxSelect(event) : Select = Box.curselection() Nr = Select[0] Display.config(text=Diagnose[Nr]) # Основная программа Window = Tk() Window.title("Привет!") Window.config(width=260, height=230) Display = Label(Window, text="Как это сделать?") О переключателях... 157 Display.place(x=20, y=10, width=160, height=30) # Список Box = Listbox(Window) for Nr in range(0,6) : Box.insert(Nr, Answer[Nr]) Box.bind("< Box.place(x=30,y=50, width=200, height=150) # Цикл событий Window.mainloop() ¾ Введи этот исходный код и запусти программу (рис. 8.2). Выбери каждую запись один раз. Рис. 8.2.Результат работы программы со списком О переключателях... Существует еще один способ визуализировать твое на- строение. Для этого ты можешь взять свой старый проект, работающий с помощью кнопок. В принципе, компоненты могут быть изменены непосредственно там. Изучаемый компонент носит имя Radiobutton и называется переключателем. Такой объект содержит текст, отображае- мый справа от кружочка. Если в кружочке установлена точ- ка – переключатель считается установленным. Как и в случае с кнопками, нам нужны шесть компонентов, которые мы создаем в виде пустого списка: Option = [] Глава Библиотека компонентов 8 158 Затем мы создаем шесть элементов, список параметров ко- торых будет немного отличаться от кнопок: for Nr in range(0,6) : Option.append(Radiobutton(Window, variable=Number, value=Nr, \ text=Answer[Nr])) Здесь создается позиция переключателя с определенным текстом. Пока это похоже на работу с кнопками. Но есть два новых параметра: – variable – вставляет дополнительную переменную, ко- торая делит переключатель с другими позициями. Они образуют группу, в которой можно активировать толь- ко один параметр. У нас это номер выбранного эле- мента; – value получает последовательный номер соответствую- щей позиции в группе. Теперь число не является обычной численной перемен- ной так, как это было ранее в классическом Python. Модуль tkinter использует свой собственный тип, который описы- вается следующим образом: Number = IntVar() Number.set(-1) Этой переменной присваивается значение с помощью ме- тода set(). Это не может быть число из списка с номерами позиций (от 0 до 5). В противном случае соответствующий компонент будет считаться активированным и переключа- тель будет установлен в соответствующую позицию при за- пуске программы. Тем не менее должен отмечаться только тот вариант, который мы выбираем. Функция IntVar() так же, как, например, StringVar(), – это соб- ственное творение модуля tkinter для работы с целыми чис- лами и строками. Это методы, которые создают «обычную» пе- ременную, нужную для работы программы. Важно, чтобы такая переменная не создавалась, пока не появится окно как экземп- ляр Tk()! Функция события не нуждается в параметре, потому что значение переменной Number глобально. Однако ты получа- ешь ее значение только с помощью метода get(): О переключателях... 159 def buttonClick() : Display.config(text=Diagnose[Number.get()]) Используя дополнительные переменные, многое можно упростить, как показывает следующий полный листинг (⇒ hello6.py): from tkinter import * # Константы текста Answer = ["Супер", "Хорошо", "Так себе", "Плохо", "Ужасно", \ "Не скажу"] Diagnose = ["Это здорово!", "Это радует!", "Все возможно.", \ "Это огорчает!", "Это плохо!", \ "Раз ты так думаешь..."] # Функция события def buttonClick() : Display.config(text=Diagnose[Number.get()]) # Основная программа Window = Tk() Window.title("Привет!") Window.minsize(width=260, height=260) Display = Label(Window, text="Как это сделать?") Display.pack() # Вспомогательная переменная Number = IntVar() Number.set(-1) # Опции Option = [] for Nr in range(0,6) : Option.append(Radiobutton(Window, variable=Number, value=Nr, \ text=Answer[Nr])) Option[Nr].config(command=buttonClick) Option[Nr].pack(anchor="w") # Цикл событий Window.mainloop() На этот раз я выбрал вариант pack(), который показался мне проще. Тем не менее пришлось помешать окну стать слишком маленьким. Вот почему я определил минимально допустимые размеры и использовал метод minsize() вместо config() : Window.minsize(width=260, height=260) Глава Библиотека компонентов 8 160 Кроме того, нам нужен параметр anchor="w", позволяющий убедиться, что кружочки позиций переключателя выровне- ны должным образом. (Вариант anchor="e" тоже подойдет.) ¾ Введи показанный код и запусти программу. Щелкни по каждому положению переключателя один раз. Рис. 8.3.Результат работы версии программы с переключателем ...и флажках Современная медицина уже признала, что помимо физи- ческого есть еще моральные и духовные аспекты состояния человека. Поэтому давай применим новейшие продвиже- ния в науке в нашем текущем проекте. Для этого мы используем новый объект типа Checkbutton, также называемый флажком. Этот компонент отображает квадратик слева от текста. Если в нем установлен флажок (галочка), запись считается отмеченной (рис. 8.4). Рис. 8.4.Сгруппированные переключатель и флажки Чтобы охватить все аспекты состояния человека, мы допол- ним нашу программу тремя флажками: ...и флажках 161 Choice = [] for Nr in range(0,3) : Choice.append(Checkbutton(Window, variable=Number, \ text=State[Nr])) Нам также нужен еще один список строк: State = ["Духовно", "Морально", "Физически"] Похоже на переключатели, но здесь нам нужен лишь па- раметр variable, и мы можем избавиться от переменной value . Однако мы не можем использовать одну и ту же пе- ременную здесь, если она уже используется в коде пере- ключателей. По этой причине я ее переименовал и кое-что дополнил: ChkNum = [0,0,0] OptNum = IntVar() OptNum.set(-1) В то время как элемент теперь назван OptNum, у меня есть пе- ременная ChkNum, которая сразу становится списком из трех элементов. Почему? В то время как переключатель может быть установлен только в одну позицию, флажков можно установить любое количество (даже все). Поэтому каждый элемент Checkbutton нуждается в собственной переменной. Затем мы создаем их в цикле for: for Nr in range(0,3) : ChkNum[Nr] = IntVar() Choice.append(Checkbutton(Window, variable=ChkNum[Nr], \ text= State [Nr])) Теперь что-то должно произойти, если установить фла- жок. Например, соответствующая «строка ответа» появится в строке заголовка окна. Для этого нам нужен другой метод событий, который я на- зову checkClick(): def checkClick() : Text = "Привет! " for Nr in range (0,3) : if ChkNum[Nr].get() == 1 : Text = Text + State[Nr] + " "; Window.title(Text) Глава Библиотека компонентов 8 162 Сначала мы создаем строку с содержимым «Привет!», кото- рое изначально присутствовало в заголовке окна. Затем эта строка проверяется в цикле, в котором установлен флажок. Так происходит, если связанный элемент ChkNum имеет зна- чение 1: if ChkNum[Nr].get() == 1 : Если это так, то добавляется соответствующий текст («Духов- но», «Морально», «Физически») (с пробелами между ними): Text = Text + State[Nr] + " "; Наконец, у нас есть полноценный заголовок окна: Window.title(Text) ¾ Теперь у нас есть все необходимое, чтобы собрать ис- ходный код программы воедино (⇒ hello7.py): from tkinter import * # Константы текста Answer = ["Супер", "Хорошо", "Так себе", "Плохо", "Ужасно", \ "Не скажу"] State = ["Духовно", "Морально", "Физически"] Diagnose = ["Это здорово!", "Это радует!", "Все возможно.", \ "Это огорчает!", "Это плохо!", \ "Раз ты так думаешь..."] # Функция для события def buttonClick() : Display.config(text=Diagnose[OptNum.get()]) def checkClick() : Text = "Привет! " for Nr in range (0,3) : if ChkNum[Nr].get() == 1 : Text = Text + State[Nr] + " "; Window.title(Text) # Основная программа Window = Tk() Window.title("Привет!") Window.minsize(width=420, height=260) Display = Label(Window, text="Как дела?") Display.grid(row=0, column=1) ...и флажках 163 # Вспомогательные переменные ChkNum = [0,0,0] OptNum = IntVar() OptNum.set(-1) # Опции Option = [] for Nr in range(0,6) : Option.append(Radiobutton(Window, variable=OptNum, value=Nr, \ text=Answer[Nr])) Option[Nr].config(command=buttonClick) Option[Nr].grid(row=Nr+1, column=0, sticky="w") # Управление Choice = [] for Nr in range(0,3) : ChkNum[Nr] = IntVar() Choice.append(Checkbutton(Window, variable=ChkNum[Nr], \ text= State [Nr])) Choice[Nr].config(command=checkClick) Choice[Nr].grid(row=Nr+1, column=2, sticky="w") # Цикл событий Window.mainloop() Опять же, я немного экспериментировал с дизайном, а за- тем решил использовать для разметки сетку. Сначала поле отображается в первой строке, но во втором столбце: Display.grid(row=0, column=1) Я расположил позиции переключателя в первом столбце, строка меняется в соответствии с нумерацией отдельных элементов: Option[Nr].grid(row=Nr+1, column=0, sticky="w") Флажки попадают в столбец 3: Choice[Nr].grid(row=Nr+1, column=2, sticky="w") Ключевое слово sticky гарантирует, что все пункты вырав- ниваются по левому краю. ¾ Запусти программу и протестируй все переключатели и галочки (рис. 8.5). Глава Библиотека компонентов 8 164 Рис. 8.5.Версия программы с флажками и переключателем Декорирование приложения Все эти пункты с возможностью выбора с их точками и флажками кажутся какими-то блеклыми. Посмотрим, удастся ли нам немного доработать внешний вид програм- мы. Для этого я использую два других компонента того же типа, с которыми я хотел бы визуально совместить две группы элементов управления – переключателя и флажков: Links = Frame(Window, borderwidth=2, relief="sunken") Links.place(x=20, y=50, width=180, height=220) Rechts = Frame(Window, borderwidth=2, relief="raised") Rechts.place(x=220, y=50, width=180, height=110) Frame – это тип компонента, который прежде всего предна- значен для генерации красивой рамки. С помощью пара- метра relief я могу влиять на то, как выглядит внутренняя поверхность этого компонента, например утопленная или выступающая. А параметр borderwidth определяет толщину границы. Кроме того, с помощью функции place() можно тщательно позиционировать элементы управления. Окно будет состоять лишь из двух новых компонентов, по- казанных на рис. 8.6. Декорирование приложения 165 Рис. 8.6.Примеры рамок – утопленной и выступающей Теперь мы должны убедиться, что позиции переключателя и флажки попадают в нужные рамки. Поэтому мы сделаем Radiobuttons и Checkbuttons элементами рамки: Option.append(Radiobutton(Links, variable=OptNum, value=Nr, \ text=Answer[Nr])) … Choice.append(Checkbutton(Rechts, variable=ChkNum[Nr], \ text= State [Nr])) И с этим мы приходим к полному исходному коду (⇒ hel lo8.py): from tkinter import * # Константы текста Answer = ["Супер", "Хорошо", "Так себе", "Плохо", "Ужасно", \ "Не скажу"] State = ["Духовно", "Морально", "Физически"] Diagnose = ["Это здорово!", "Это радует!", "Все возможно.", \ "Это огорчает!", "Это плохо!", \ "Раз ты так думаешь..."] # Функция для события def buttonClick() : Display.config(text=Diagnose[OptNum.get()]) def checkClick() : Text = "Привет! " |