Просматривая свои заметки по проектированию 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 <file tcl from Page> sh-4.3$
Вот код tcl-скрипта fromPageToTcl.tcl:
Код tcl-скрипта fromPageToTcl.tcl
#!/usr/bin/tclsh # 1 -- source file #НЕ ЗАБЫВАТЬ КОММЕНТИРОВТЬ return # if {[winfo exists $base]} { #### wm deiconify $base; return # } # и строку ## vTcl:FireEvent $base <<Ready>> # 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 set largv [llength $argv] if {$largv != 1} { puts "Usage: [info script] <file tcl from Page>\n" exit } set fconf [file exists "$argv"] if { $fconf == "0" } { puts "File=\"$argv\" don't exist\n" puts "Usage: [info script] <file tcl from Page>\n" exit } set srcMy [info script] set fp [open $srcMy r] while {![eof $fp]} { gets $fp res puts $res if { $res == "#END"} { 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 "#Soft" puts "\t\twm deiconify \$base;" continue } if { [string first "vTcl:FireEvent \$base <<Ready>>" $res] != -1 } { puts "#Soft" puts "#\tvTcl:FireEvent \$base <<Ready>>" continue } </spoiler> if { [string first "vTcl:toplevel \$top -class Toplevel \\" $res] != -1 } { puts "#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 "#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, это просто великолепная троица.
