Tcl/Tk. Тематические виджеты TTK и дизайнер TKproE-2.20
Просматривая свои заметки по проектированию GUI с использованием виджетов Tk, я почувствовал какую-то неудовлетворенность. А дело оказалось в том, что я фактически упустил работу с тематическими виджетами ttk (themed tk). Они в скользь были задействованы при рассмотрении пакета Tkinter для Python и использовании дизайнера Page. Там речь шла о виджете TNotebook (блокнот, записная книжка) из пакета ttk.
Виджет TNotebook это один из новых виджетов, наряду с TCombobox, TProgressbar, TSeparator и TSizegrip, появившихся в ttk. Перевод приложений с классического tk на ttk может потребовать минимальных усилий. Порой бывает достаточно вместо, например, button написать ttk: buton. Отдельно хочется сказать о combobox. Если вы в своих приложениях, написанных на tk, использовали виджет spinbox и теперь замените его на ttk: combobox, то в приложении появится этот виджет, которого вам так не хватало:
Но все же главное в ttk, это не новые виджеты, а их оформление (themеd). Настройка тем, стилей заслуживает отдельного разговора. А здесь бы я отправил разработчиков к вышедшему буквально на днях (11.11.2017 г.) замечательному учебному пособию:
Насмотря на то, что оно написано на немецком языке, но я, даже не знаю английского языка, читал его с превеликим удовольствием. Как говорится, написано для «дурака».
Нас интересует, есть ли интегрированная среда разработки/дизайнеры применительно к виджетам ttk. Если говорить о Python и Tkinter, то это пакет Page. Этот пакет поддерживает как классические виджеты, так и тематические виджеты. Правда почему-то отсутствуют поддержка разделителей TSeparator. Средой проектирования является скриптовый язык Tcl. Именно по этой причине мы еще вернемся к этому пакету. Отметим несомненное достоинство Page — это наличие мольберта, на котором можно просто и удобно размещать виджеты.
Но это для Python-а, а нас интересует конечный продукт на Tcl. Оказалось, что ровно год назад (27.11.2016 г.) был анансирован Tcl/Tk-дизайнер TKproE 2.20:
И в нем присутствуют разделители TSeparator. Уникальной особенностью этого дизайнера является то, что он принимает практически любой tcl/tk скрипт.
Для демонстрации возможностей как виджетов ttk, так и дизайнера TKproE-2.20, был разработан GUI-интерфейс для утилиты «изящной печати» pp из пакета NSS с устраненными косяками и поддержкой oid-ов российской pki (инфраструктура открытых ключей). Тем более, что такая утилита по просмотру сертификатов, включая квалифицированные сертификаты, просмотру электронной подписи документа, является востребованной. И именно для нее был спроектирован и реализован графический интерфейс с помощью TKproE:
Утилиту и графическую обвязку к ней можно скачать здесь. Еще одна очень приятная неожиданность в отличии от других дизайнеров ждала при работе с изображения (image) и иконками. Если раньше приходилось «ручками» переводить изображение в PEM-кодировку и самому вставлять код в скрипт, то теперь достаточно указать файл с изображением и TKproE сам заберет его и конвертирует:
Единственное неудобство здесь — это необходимость ввода имени файла. Но оказалось это легко исправить. Достаточно в скрипте tkproe.tcl в строках 7474 и 7516 код (процедура proc ShowWindow.tpimages)
label .tpimages.propbitmap.frame14.label12 -activebackground {#dcdcdc} \
-anchor {w} - background {#dcdcdc} -borderwidth {2} -font {Helvetica 10} \
-highlightbackground {#dcdcdc} -text {File:} -width {10}
заменить на код:
button .tpimages.propbitmap.frame14.label12 -activebackground {#dcdcdc} \
-anchor {w} -background {#dcdcdc} -borderwidth {2} -font {Helvetica 10} \
-highlightbackground {#dcdcdc} -text {File:} -width {10} \
-command { global userDir; set fileTypes {{"File image" *}}; \
set file [tk_getOpenFile -title "Find Image" -filetypes $fileTypes \
-initialdir "$userDir"]; \
.tpimages.propphoto.frame14.entry13 delete 0 end; \
.tpimages.propphoto.frame14.entry13 insert end $file; }
А чтобы выбор файла начинался с домашнего каталога после 5 строки в скрипт tkproe.tcl добавляем следующий код:
global userDir
set userDir $env(HOME)
Теперь для выбора файла с изображением достаточно нажать кнопку «File:»:
Желающие могут улучшить и этот код, например, по выбранному файлу заполнять поле «Format», либо наоборот, по формату организовать поиск файла, рассматривая формат как расширение файла («Тип файлов» на скриншоте).
Еще одно неудобство, с которым пришлось столкнуться, это получение домашнего каталога пользователя, например, mHOME set $env (HOME). А домашний каталог, как правило, нужен для обеспечения платформанезависимости скрипта. Оказалось через TKproE вставить его некуда. Глобальные переменные в TKproE определяются через процедуру TPinitVars. Естественно, если определяешь переменную mHOME через область глобальных переменных:
то переменная mHome будеть иметь значение »$env (HOME)». Казалось бы, возьми и вставь вычисление текущего каталога ручками в основное тело скрипта. Можно, если только вы больше не будете править скрипт в TKproE. В противном случае TKproE озаботится оптимизацией и вместо кода set nHOME $env (HOME) вставит в процедуру инициализации глобальных переменных код с домашним каталогом компьютера, на котором создавался/правился скрипт. Это ужасно.
Здесь пришлось во второй раз залезть в код TKproE. Выход из данной ситуации мы нашли в определении глобальной переменной myHOME и включении в генерируемый скрипт дополнительного кода. И так, в процедуру proc TP_Clone_app сразу же за строкой (13-ая):
set outstr "# Generated by TKproE $TPinfo(revision) - [clock format [clock seconds]]\n\n"
добавляем строку
append outstr "#Add me\nencoding system utf-8\nglobal myHOME\nset myHOME \$env(HOME)\n\n"
,
, а в процедуру proc TP_Clone_variables после строки (10-ая)
foreach varname $varlist {
вставляем код
if { $varname == "myHOME" } {
continue
}
,
который отключает переустановление глобальной переменной myHOME.
И вот после этих доработок, и удалось создать графическую оболочку для утилиты PP:
Если выбрать файл с документом и нажать кнопку «Показать в окне», то мы увидим следующее:
Распечатку сертификата, как и любого другого документа, можно сохранить в файле (кнопка «Сохранить в файле» на вкладке «Сертификаты и т.п.»).
Исходный код скрипта GUIPP, а он же является и проектом TKproE, можно скачать здесь.
Выше мы говорили о том, что вернемся к дизайнеру Page. В дизайнере Page очень удобно проектировать GUI со свободным размещением виджетов (place). Как уже говорилось, проект GUI дизайнер Page хранит как Tcl/Tk скрипт, и было бы заманчиво использовать готовый скрипт из Page в TKproE или дописывать его в ручном режиме. В чистом виде проект из Page нельзя выполнить или загрузить в TKproE-2.20. Для устранения этой «несправедливости» был разработан tcl-скрипт, который приводит проекты из Page в выполняемые скрипты tcl/tk:
sh-4.3$ ./fromPageToTcl.tcl
Usage: ./fromPageToTcl.tcl
sh-4.3$
Вот код tcl-скрипта fromPageToTcl.tcl:
#!/usr/bin/tclsh
# 1 -- source file
#НЕ ЗАБЫВАТЬ КОММЕНТИРОВТЬ return
# if {[winfo exists $base]} {
#### wm deiconify $base; return
# }
# и строку
## vTcl:FireEvent $base <>
#LISSI-Soft
namespace eval vTcl::widgets::ttk::sizegrip {
proc CreateCmd {target args} {
grid [ttk::sizegrip $target] -column 999 -row 999 -sticky se
}
}
proc vTcl:font:add_GUI_font {font_name font_descr} {
# This is called when we load an existing GUI-tcl file. It get rid
# of actual fonts and replaces them with the definitions from the
# GUI-tcl file.
#set font_descr [font configure $font_name]
if {[catch {
font delete $font_name
set newfont [font create $font_name {*}$font_descr ]
} result]} {
# Create failed
set newfont "TkDefaultFont"
}
#set newkey NEEDS WORK
#set ::vTcl(fonts,$newfont,type) $font_type
#set ::vTcl(fonts,$newfont,key) $newkey
set ::vTcl(fonts,$newfont,font_descr) $font_descr
set ::vTcl(fonts,$font_descr,object) $newfont ;# Rozen 8/24/13
#set ::vTcl(fonts,$newkey,object) $newfont
}
proc {vTcl:font:add_font} {font_descr font_type {newkey {}} {check_fonts {1}}} {
global vTcl
## This procedure may be used free of restrictions.
## Exception added by Christian Gavin on 08/08/02.
## Other packages and widget toolkits have different licensing requirements.
## Please read their license agreements for details.
# With tk you are not allowed to specify the the font name when
# creating a fomt. So if you want to assign a name to a font then
# specify then the variable ::vTcl(fonts,$newkey,object) will hold
# the correspondance between the actual name of the created name
# and the name you wish to use. Rozen
set defined_fonts [font names]
if {$newkey != ""} {
if {[info exists ::vTcl(fonts,$font_descr,object)]} {
set test_font $::vTcl(fonts,$font_descr,object)
if {[lsearch $defined_fonts $newkey] == -1} {
set ::vTcl(fonts,$newkey,object) $test_font
return $test_font
}
}
}
if {$check_fonts} {
if {[info exists ::vTcl(fonts,$font_descr,object)]} {
# It already exists
return $::vTcl(fonts,$font_descr,object)
}
if {[lsearch $defined_fonts $font_descr] > -1} {
# It's a font already defined..
return $font_descr
}
}
incr ::vTcl(fonts,counter)
set newfont [eval font create $font_descr]
lappend ::vTcl(fonts,objects) $newfont
## each font has its unique key so that when a project is
## reloaded, the key is used to find the font descriptio
if {$newkey == ""} {
set newkey vTcl:font$::vTcl(fonts,counter)
## let's find an unused font key
while {[vTcl:font:get_font $newkey] != ""} {
incr ::vTcl(fonts,counter)
set newkey vTcl:font$::vTcl(fonts,counter)
}
}
set ::vTcl(fonts,$newfont,type) $font_type
set ::vTcl(fonts,$newfont,key) $newkey
set ::vTcl(fonts,$newfont,font_descr) $font_descr
set ::vTcl(fonts,$font_descr,object) $newfont
set ::vTcl(fonts,$newkey,object) $newfont
lappend ::vTcl(fonts,$font_type) $newfont
## in case caller needs it
return $newfont
}
proc vTcl:DefineAlias {a b c d e} {
# puts \"$a.$b\"
# puts \"$c.$d.$e\"
return
}
proc Window {type w} {
if {$w == "."} {
return
}
toplevel $w
set base $w
vTclWindow$w $base
}
#END LISSI-Soft
set largv [llength $argv]
if {$largv != 1} {
puts "Usage: [info script] \n"
exit
}
set fconf [file exists "$argv"]
if { $fconf == "0" } {
puts "File=\"$argv\" don't exist\n"
puts "Usage: [info script] \n"
exit
}
set srcMy [info script]
set fp [open $srcMy r]
while {![eof $fp]} {
gets $fp res
puts $res
if { $res == "#END LISSI-Soft"} {
break
}
}
close $fp
#Обрабатываем файл из Page
set fp [open $argv r]
while {![eof $fp]} {
gets $fp res
if { [string first "wm deiconify \$base; return" $res] != -1 } {
puts "#LISSI-Soft"
puts "\t\twm deiconify \$base;"
continue
}
if { [string first "vTcl:FireEvent \$base <>" $res] != -1 } {
puts "#LISSI-Soft"
puts "#\tvTcl:FireEvent \$base <>"
continue
}
if { [string first "vTcl:toplevel \$top -class Toplevel \\" $res] != -1 } {
puts "#LISSI-Soft"
puts "#vTcl:toplevel \$top -class Toplevel \\"
puts "\$top configure \\"
continue
}
if { [string first "vTcl::widgets::core::toplevel::createCmd \$top -class Toplevel \\" $res] != -1 } {
puts "#LISSI-Soft"
puts "#vTcl::widgets::core::toplevel::createCmd \$top -class Toplevel \\"
puts "\$top configure \\"
continue
}
puts $res
}
close $fp
exit
И так, берем пример из папки ~/page/examples/complex и получаем посредством скрипта fromPageToTcl.tcl исполняемый tcl-скрипт:
sh-4.3$cd ~/page/examples/complex
sh-4.3$ ./fromPageToTcl.tcl complex.tcl > complex_new.tcl
sh-4.3$
Теперь, чтобы получить совсем хороший код, открываем полученный скрипт complex_new.tcl в дизайнере TKproE-2.20 и удаляем из проекта все ненужные теперь процедуры, а это процедура Window и все процедуры с префиксом vTcl, и пересохраняем его:
Отметим, что Page поддерживает еще «Scrolled widgets», мы их, естественно, не поддерживаем.
В целом, дизайнер TKproE оставил очень хорошее впечатление и с ним приятно вести разработку GUI. А тройка Page x TKproE x fromPageToTcl.tcl, состоящая из дизайнеров Page, TKproE и скрипта fromPageToTcl.tcl, это просто великолепная троица.