GUI-фреймворки — на поток

    Приветствую вас, коллеги!

    Пару-тройку месяцев назад я начал приглядываться к Golang с целью использовать его для десктопных приложений. Язык понравился, объем и тематика написанных для него пакетов произвели впечатление, но вот с GUI дело обстоит не так радужно. Не буду сейчас подробно останавливаться на деталях, достаточно будет сказать, что после чтения нескольких обзоров и беглого просмотра существующих GUI-пакетов решил написать свой — тем более, что опыт в этом у меня есть.

    Первая мысль была — пойти по уже проторенному пути: написать набор соответствующих функций на С, точнее, адаптировать уже готовый — то, что я писал когда-то для Harbour и C++, сделать привязку к нему с помощью cgo (C API для Golang) и дружелюбную обертку. Я даже начал это делать, получил первое окошко, но как представил, сколько впереди еще работы, отдельно для Windows, отдельно для Linux, работы чисто технической, поскольку я это уже проходил, энтузиазм несколько поостыл.

    И тогда пришла другая идея.

    У меня ведь уже есть GUI-библиотека, HwGUI для Harbour, вполне функциональная, кросс-платформенная, которую я регулярно использую для своих приложений. В ней уже реализовано все, что мне надо. Почему бы не написать на ее основе программу, работающую как своеобразный GUI-сервер. Этот сервер после запуска будет молча слушать определенный порт, и, получив соединение от моей программы на Golang, будет в ответ на ее запросы создавать окошки, виджеты, производить с ними манипуляции и осуществлять обратную связь при появлении каких-то событий от виджетов — словом, реализовывать для нее GUI. Все низкоуровневые детали реализации GUI уже есть в библиотеке, для Windows — через прямые вызовы WinAPI, для Linux/Unix и, наверное, macOs — посредством GTK. Кроме того, я не предполагаю делать сервер в полном смысле этого слова, он не будет принимать соединения от разных программ — это привнесло бы дополнительные ненужные сложности. Для каждого экземпляра Golang-программы будет запускаться свой отдельный экземпляр GUI-сервера, что еще упрощает задачу. В целом программа будет состоять, таким образом, из двух процессов, один из которых выполняет основную задачу, другой — отвечает за интерфейс.

    Соответствующий пакет для Go должен включать в себя процедуру Init, которая запускает GUI-сервер и присоединяется к нему, и набор структур, методов, функций для создания окон и виджетов и манипуляций ими. Основная содержательная часть всех этих функций — отправка сообщений определенного формата (основанного на JSON) на сервер и прием сообщений от него. Связь поддерживается с помощью двух tcp/ip портов, один отвечает за отправку запросов на сервер, другой — за получение сигналов от сервера для обработки событий (нажатие кнопки, закрытие окна и пр.). Задача относительно несложная, сам пакет получается небольшой. Не нужно использовать cgo, не нужна привязка к сторонним библиотекам, весь код на чистом Go. Исполняемый файл самой программы и исполняемый файл GUI-сервера — все.

    Выбор Harbour + HwGUI для реализации GUI-сервера для меня обусловлен, прежде всего, тем, что это мои «родные» инструменты, самое простое и быстрое решение. Но это хороший выбор и с других точек зрения. Вот, на мой взгляд, основные достоинства:

    • кроссплатформенность «в коробке»;
    • то, что называется, native look and feel, поскольку под Windows это исключительно вызовы WinAPI, под Linux/Unix — GTK; насколько «native» GTK для macOs я, правда, не знаю;
    • возможность использовать Harbour как встроенный скриптовый язык, на сервер можно передавать фрагменты кода для исполнения — обработчики событий, например, что может разгрузить основную программу от некоторых деталей реализации. Кроме того, Harbour хорош для многих вещей, для работы с dbf и некоторыми базами данных, например;
    • реализация печати;
    • возможность использовать созданные Дизайнером (утилита HwGUI) экранные формы. Эти формы хранятся в XML формате и могут использоваться без изменений в любой ОС, в которой работает сервер;
    • возможность использовать созданные тем же Дизайнером формы отчетов для печати (тоже в XML).

    Короче, я начал это делать и значительная часть работы уже выполнена. Оба проекта, GuiServer и Golang GUI-фреймворк External — на Github'е, все ссылки в конце статьи. Ниже — пара скриншотов. Ничего особенного, просто тесты.

    Это — простое диалоговое окно:


    А это сделано на основе примера из книги Кернигана и Донована:


    Сейчас основная цель проектов — добиться того, чтобы эта сладкая парочка, External и GuiServer, могли все, что может HwGUI. Ну а по ходу создания каких-то реальных приложений, использующих External, будет ясно, что нужно еще.

    На этом можно было бы и закончить, описание Golang-пакета я отложил на другой раз. Но главное в этой статье только начинается. Ведь описанный способ реализации GUI-фреймворка с тем же самым GUI-сервером можно использовать и для других языков. C, Python, Java, ..., даже Perl и PHP (а почему бы и нет?) — см. заголовок статьи. Минимум затрат — и готово довольно функциональное GUI решение. Самое сложное для каждого конкретного языка — не реализация обмена с сервером, а то, чтобы это решение органично вписывалось в его парадигму, в его внутреннюю логику. Если кто хочет создать такой фреймворк для своего языка, постараюсь оказать посильную помощь в получении необходимой информации и, возможно, в добавлении каких-то фич к GUI-серверу.

    Параллельно с Golang'овским пакетом я делал аналог для Harbour, в основном в целях проверки/отладки. Фреймворк для Perl вряд ли буду делать, а вот для С или С++ — вполне вероятно. И вот почему: есть ведь еще одна интересная возможность, связанная с использованием GUI-сервера, его можно запускать на другом компьютере. Основная программа работает на одном компьютере, а ее интерфейс — на другом. Сходу видятся вот такие варианты использования этого дела:

    • основная программа работает на Linux/Unix сервере, на котором вообще не установлена графическая оболочка;
    • основная программа работает на чужом компьютере (у условного бухгалтера в вашей компании), а вы, не мешая ему, управляете ею на своем;
    • основная программа работает на смартфоне, а вы с нормального компа копаетесь в его внутренностях;
    • основная программа работает на контроллере, на Ардуино каком-нибудь, Распберри или их аналогах, где и монитора нормального, может, нет. Подключаетесь со своим ноутбуком — и вперед.

    Вот для этого последнего варианта пригодился бы, наверное, С-фреймворк под GUI-сервер, мне такая возможность кажется очень перспективной.

    И, наконец, ссылки:

    github.com/alkresin/guiserver — GuiServer на Github
    github.com/alkresin/external — External( Go пакет ) на Github
    www.kresin.ru/guisrv.html — страница GuiServer у меня на сайте, здесь можно скачать готовые бинарники
    habr.com/post/198618 — моя статья о Harbour здесь на Хабре
    ru.wikipedia.org/wiki/Harbour — Harbour в Википедии
    www.kresin.ru/harbour.html — страница Harbour у меня на сайте
    www.kresin.ru/hwgui.html — страница HwGUI у меня на сайте

    P.S.: Понятно, что устанавливать Harbour и HwGUI, чтобы собрать GuiServer из исходников мало кто будет, поэтому собранные бинарники я регулярно выкладываю на странице GuiServer у себя на сайте — для Windows, Debian 8 32-bit, Ubuntu 18.04 64-bit. Могу для Федоры собрать, а вот под macOs — увы, у меня в шаговой доступности его нет.
    Поделиться публикацией
    Комментарии 60
      +2
      Этот сервер после запуска будет молча слушать определенный порт, и, получив соединение от моей программы на Golang, будет в ответ на ее запросы создавать окошки, виджеты, производить с ними манипуляции и осуществлять обратную связь при появлении каких-то событий от виджетов


      Уже измеряли, в сколько сотен раз это все медленнее native gui? Сокеты — это одна из самых медленных сущностей вообще, зависящая скоростью от ядра и сетевой нагрузки в данный момент. Почему не shared memory?
        0
        Думаю, что скорость выполнения здесь не критична. Создание GUI-объектов — это ведь не сложный вычислительный процесс в длинном цикле. В реальной программе замедление должно быть практически незаметно.
          +1
          Создание GUI-объектов — это ведь не сложный вычислительный процесс в длинном цикле


          Когда у вас на GUI таблица настроек или что-то движущееся (например прогресс бар) — то задержки в отрисовке очень критичны. Хотите увидеть вживую — прокиньте X-сессию по ssh и попробуйте поуправлять удаленным компьютером.
            0
            Не очень понял про таблицу настроек, ну а прогресс бар — там же не секундные задержки будут.
            Для повышения скорости предусмотрены функции BeginPacket(), EndPacket(), которые позволяют отправлять группу сообщений — для создания окна, например, со всеми его виджетами и их свойствами, как один пакет. Кроме того, некоторые обработчики событий можно передать серверу — я об этом писал.
              0
              Пример из жизни. Делаю интерфейс к базе данных и вывожу табличку с тысячами записями.
                0
                Ну так вы эти тысячи записей не по одной будете передавать, и, наверное, не сразу все. Все зависит от реализации, можно сделать достаточно быстро.
                  +1
                  Я как фрондендер ожидаю, что все это на себя возьмет фреймворк, как например Qt
                    0
                    И здесь вы можете просто передать двумерный массив строк и GuiServer все возьмет на себя. Для этого есть функция BrwSetArray().
                      +1
                      Вообще не вариант. В ui фреймворке должен быть model/view, что бы view подтягивала данные по мере необходимости, а не загружать всё.
                        0
                        Модели могут быть разные, правила устанавливаем мы сами. Но я подумаю над вашим предложением.
                        Реализация подобной таблицы (browse) в HwGUI достаточно гибка, модели поведения можно создавать самые разные. По умолчанию два варианта — переданный массив данных или dbf файл, локальный или через соответствующую СУБД, Ads или LetoDB. Но подставив другие обработчики событий, можно реализовать и что-то еще для своих нужд. Эти другие предопределенные варианты можно будет задать в GuiServer, а из программы выбирать нужный.
                0

                А что с Drag'n'Drop?

                  0
                  Drag'n'Drop я не делал даже в самом HwGUI, если не считать сплиттеры.
                  В любом случае обрабатываться это дело должно GuiServer'ом, а пользовательской программе — передаваться начальное и конечное состояние.
          0
          А где примеры использования на Go?
            0
            Здесь в тексте я их не приводил, так как планирую на эту тему отдельную статью. А так примеры можно посмотреть в репозитории на github, каталог tests.
            +1

            В линуксе это получаются иксы над иксами внутри иксов?

              0
              Почему? К концепции X сервер/X клиент это отношения не имеет.
                +2
                Потому что вы строите абстракцию над X-сервером, который запущен как клиент-сервер, внутри клиента которого вы запускаете клиент-сервер, к которому подключаются еще клиенты.
                  0
                  … к которому подключаются еще клиенты.

                  один клиент, я об этом писал. Ну и, потом, мой GuiServer — это не иксы, так что выражение «иксы над иксами» здесь некорректно.
                    0

                    Если описать концепцию X-сервера в такой же "вкрации", как и ваша, разница будет нулевая.
                    Да, ну и чтобы два раза не вставать: если уж у вас "один сервер — один клиент", то чем объясняется выбор tcp/ip стека?

                      0
                      Для возможности использования клиента и сервера на разных компьютерах.
                        0

                        Тогда я запутался окончательно. Можете привести терминологию в статье к общему знаменателю, потому что не понятно какова роль GuiServer, клиента, кто такая "основная программа работает на Linux сервере". Где кто что отрисовывает?
                        Диаграммку набросайте, пожалуйста. Пока, как я это понял, всё выглядит как wxWidgets, но с клиент-серверной архитектурой.

                          0
                          Мне кажется, в статье это достаточно ясно изложено. Сервер здесь — это мой GuiServer, который предоставляет интерфейс, клиент — ваша программа на Go, C,…
                          Во фразе «основная программа работает на Linux сервере» имеется ввиду сервер вашей локальной сети.
                            0

                            Даже в этом вашем комментарии два "сервера". Вот что я понял. Старый добрый подход: молотилка данных отдельно, морда — отдельно. Это очень старый подход, вот примеры: mpd и множество его фронтэндов, из последнего — neovim и его tui и gui. Только у вас это с ног на голову. Правильно я понимаю?

                              0
                              молотилка данных отдельно, морда — отдельно

                              Примерно так. Только вот насчет «с ног на голову» я не соглашусь.
                                0

                                Ну, возможно, и не с ног на голову. В конце концов платформа с вашим подходом тоже уже существует.

                                  0
                                  А что, neovim тоже использует отдельный процесс для интерфейса?
                                  Я его даже не видел, пользуюсь старым добрым vim.
                                    0

                                    Там как-то вот так:


                                    Embeddable

                                    GUIs and other applications can nvim --embed to discover the msgpack API dynamically.

                                    И пачка проектов про это:
                                    https://github.com/neovim/neovim/wiki/Related-projects#gui

                                      0

                                      TUI — нет. Остальные UI — да. И, кстати, может общаться и через сокеты, и через обычные pipe. Только в случае с Neovim ожидается, что GUI запустит neovim (или, теоретически, присоединится к уже запущенному), а не наоборот.

                0
                Этот сервер после запуска будет молча слушать определенный порт, и, получив соединение от моей программы на Golang, будет в ответ на ее запросы создавать окошки, виджеты, производить с ними манипуляции и осуществлять обратную связь при появлении каких-то событий от виджетов — словом, реализовывать для нее GUI.

                А почему бы не написать такой сервер на tcl/tk?

                  0
                  Написать можно на чем угодно. Мне кажется, я обосновал в статье свой выбор.
                    0
                    то, что называется, native look and feel, поскольку под Windows это исключительно вызовы WinAPI, под Linux/Unix — GTK

                    В Linux GTK — это не native look and feel. Если вы используете GTK3 — то это есть от силы у 30% пользователей (тех, кто сидит на самых последних убунтах и ред хатах). Такое GTK3 приложение будет ужасно смотреться на системах с xfce4, KDE, тайловыми wm и проч. Не говоря уже об установке N GB дополнительного стаффа, который тянет ваш GTK3 на системах GTK2, Qt-based, где его библиотек нет. При старте соответственно он будет выглядеть чужеродно, не попадая в тему и размеры окон. А на wayland системах у него даже тень вокруг окна будет отображаться не так как по всей системе.

                    По-настоящему нативным для Linux решением было бы детектить текущее окружение\установленные библиотеки и на основе этого включать тот фреймворк, который для данной системы нативный.

                    Вот так будет выглядеть ваше приложение на дефолтной KDE системе, если установить все необходимые библиотеки.
                    image
                      +7
                      По-настоящему нативным для Linux решением было бы детектить текущее окружение\установленные библиотеки и на основе этого включать тот фреймворк, который для данной системы нативный.

                      Не могу себе представить такое. Мартышкин труд какой-то.

                        0
                        fbreader мог использовать gtk и qt, для отрисовки.
                          +2

                          Gtk2 или Gtk3, Qt4 или Qt5? Даже в рамках двух фреймворков уже 4 варианта.
                          Судя по вот таким штукам в Makefile fbreader, он это "детектил" на этапе сборки:


                          ifeq "$(UI_TYPE)" "qt"
                            QTSUBDIRS = src/qt/time src/qt/dialogs src/qt/view src/qt/image src/qt/util src/qt/filesystem src/qt/library src/unix/message src/qt/application-$(TARGET_ARCH)
                          endif
                          
                          ifeq "$(UI_TYPE)" "qt4"
                            QTSUBDIRS = src/qt4/time src/qt4/dialogs src/qt4/view src/qt4/image src/qt4/util src/qt4/filesystem src/qt4/library src/unix/message src/qt4/application
                          endif
                          
                          ifeq "$(UI_TYPE)" "gtk"
                            GTKSUBDIRS = src/gtk/time src/gtk/dialogs src/gtk/optionView src/gtk/image src/gtk/util src/gtk/filesystem src/gtk/library src/gtk/view src/unix/message src/gtk/application-$(TARGET_ARCH) src/gtk/pixbuf
                          endif
                            0
                            Его можно было собрать одновременно с несколькими бакендами, а потом при запуске указать необходимый. На самом деле это не так сложно.
                        0
                        У меня GTK2. А переключаться между GTK и QT — это, по-моему, слишком.
                          +1
                          У меня GTK2.

                          А с чем связано то, что для основы своего фреймворка вы взяли фреймворк, последняя версия которого вышла 30 января 2011 года?
                          Теперь все приложения вашего фреймворка будут выглядеть одинаково плохо и в GNOME (or any GTK3 based) и в KDE (or any Qt based).
                            0
                            Ну хотя бы с тем, что мой фреймворк (HwGUI), на котором реализован GuiServer, точнее, его Linux-версия, появился еще в 2005 году.
                            В Gnome он выглядит, кстати, вполне нормально. В KDE — не пробовал.
                              +1
                              Окей, вы взяли технологию 2005го года, без модификаций дождались 2011го, и теперь рекламируете ее в 2018м, когда все дистрибутивы и wm уже перешли на gtk3, который, к слову, скоро тоже сменится gtk4.
                                0
                                В 2005 он появился, но модифицировался и продолжает модифицироваться до сих пор. Если вдруг появится реальная необходимость, перейду на gtk3 или 4. Пока все в порядке и с gtk2.
                    +1
                    Идея «хороша», но не нова. www.gtk-server.org. Вы им пользуетесь? Вот то-то же, и я тоже. Но я думаю проблема в том, что народ о таком применении не думает, а когда увидит, начинает «стесняться». Так что чем больше будет таких проектов как ваш, тем лучше, сменится background. Потому что писать C/C++ байндинги для всего подряд действительно не вариант.
                      0
                      Я о нем и не слышал. Действительно, идея та же. Серьезный продукт, судя по всему.
                      Но работать с ним я бы не стал, потому что использовать gtk под Windows, на мой взгляд, не лучший вариант. И выглядит несколько непривычно, и то, что надо устанавливать сам gtk везде со своей программой — сильно не нравится.
                        0
                        А под мак по вашему ГТК норм? Очень убого будет выглядеть + тянуть гтк либы придется. Так что раз для винды нативный интерфейс сделали, то и для мак стоило бы Cocoa использовать
                          0
                          У меня нет мака под рукой и я никогда не видел, как выглядит там gtk.
                      0
                      А почему Qt не рассматривал? Там и сокеты есть и кросплатформенность уже решена за тебя и конструктор UI. Так же в помощь сигналы слоты, которые писали бы в сокеты события. Ну и зачем 2 соединения? Разве одно все не вытянет?
                        0
                        QT мне не нравится по ряду причин, одна из которых — необходимость тянуть мегатонны dll со своей программой, даже самой маленькой. Для Go, кстати, уже есть фреймворк под qt.
                        Что касается количества соединений — можно, конечно, было бы и одним обойтись, но тогда логика взаимодействия процессов усложнилась бы. Ведь событие от GUI, которое посылает сообщение основной программе, может произойти в любой момент, в том числе во время передачи сообщения от программы к GUI или когда программа ждет ответа на свое сообщение. Разрулить все это можно, но зачем лишняя головная боль…
                          0

                          Для Go, кстати есть и GTK биндинги. Вот тут рассматривали как минимум один из них.
                          Для локального приложения tcp/ip выглядит странно, более логично было бы AF_UNIX (linux/osx) и NamedPipe (win). Но я хоть убей всё ещё не понимаю, зачем может понадобиться связка GuiServer и GuiClient на разных хостах. Как это, вообще будет выглядеть, если GuiServer на win-хосте, а клиент на osx, к примеру?
                          Ах, ну и Клиппер, о боже, Клиппер! :)

                            0
                            Для Go, кстати есть и GTK биндинги.

                            Знаю, но я не использую gtk под Windows. Кроме того, это именно биндинги, т.е., набор функций, практически повторяющий gtk с его местами непростой структурой. В HwGUI же — обертка, которая изолирует программиста от всех деталей gtk и делает его работу проще.
                            Для локального приложения tcp/ip выглядит странно

                            Согласен. Вполне возможно, что в дальнейшем могут быть добавлены альтернативные варианты организации обмена. Реально там всего несколько небольших функций, которые за это отвечают. Через процедуру Init можно будет передавать GuiServer'у информацию, какой вариант использовать.
                            зачем может понадобиться связка GuiServer и GuiClient на разных хостах.
                            Преимущественно в тех случаях, когда на компьютере, где работает программа, нет GUI оболочки или даже монитора — я же перечислил возможные варианты.
                            0
                            даже самой маленькой.

                            Тогда Go с его статической линковкой вам не подойдет.

                              0
                              Уже подошел. Его исполняемые файлы, конечно, великоваты на мой вкус, но все же меньше, чем суммарный вес всех dll от qt. К тому же файл именно один, что само по себе приятно.
                                0
                                Я что-то не понимаю, но у вас сервер написан на Си, т.е. вместо одного файла у вас два. Вам не нравится таскать gtk, но предлагаете таскать сервис.
                                  0
                                  Да, всего 2 файла, основная программа и GuiServer. А теперь посчитайте, сколько у gtk или qt. Кроме того, это не единственная проблема — я уже писал об этом выше, могу повторить:
                                  1)То, как выглядит gtk под Windows, оставляет желать лучшего.
                                  2)Биндинги повторяют модель библиотеки, gtk или qt. Моя обертка над gtk и winapi скрывает низкоуровневые детали реализации, с ней работать проще. Впрочем, может, это потому, что я к ней привык).
                                    0
                                    > Биндинги повторяют модель библиотеки, gtk или qt.

                                    Это ожидаемое поведение, т.к. не нужно переучиваться. Но никто не мешает скрыть все за более удобный интерфейс.

                          0
                          На картинках самые простые компоненты и canvas, а где можно посмотреть список доступных контролов? Интересуют таблицы, treee view, графики, layout-ы

                          И ещё момент — очень часто сталкиваюсь с тем, что админы выключают на локальных компах виндовз службу DNS адресов. Видимо на то есть причины. В этом случае сервер ваш работать не будет, не так ли? по этому логичнее было бы например для windows использовать именованные пайпы, shared memory либо сообщения WM_COPYDATA. Или любой другой рекомендованный способ межпроцессной коммуникации, не зависящий от запущенных служб
                            0
                            Вот список контролов, уже прописанных в Go-пакете: label, edit, button, checkbox, radio, group, combo, bitmap, line, panel, ownbutton, splitter, updown, tree, progress bar, tab, browse. Здесь browse — это то, что вы называете таблицей, panel — то, на что можно, если надо, положить др. контролы — как тулбар, я большей частью именно в этом качестве его использую, ownbutton — кнопка, нарисованная средствами HwGUI.
                            Есть еще группа контролов, которые есть в HwGUI и, следавательно, будут доступны и через GuiServer, когда я напишу для этого код. В принципе, они все доступны и сейчас, если использовать xml формы.
                            По второму вопросу: отсутствие службы DNS никак не может повлиять на вызов локального сервера по адресу 127.0.0.1. Но об альтернативных механизмах связи я все равно буду думать — полезно иметь несколько вариантов.
                              0
                              И еще: список всего, что есть в HwGUI, можно посмотреть в онлайн-документации, например, в списке классов
                              0
                              А вот я бы взялся, пожалуй, за версию для Perl.
                                0
                                Если вы всерьез, готов помочь — не в кодировании на Perl, а в том, что касается описания протокола и логики работы.
                                  0
                                  Вполне серьезно. Протокол бинарный?
                                    0
                                    Нет, текст: "+" <JSON строка> "\n"
                                    Я буду выкладывать информацию вот здесь — по частям, по мере возможности.
                                    По самому протоколу самое лучшее — запустить готовые тесты, включив журналирование, тогда все принятые сервером команды будут записываться в guiserver.log.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое