Как связать интерфейс с кодом

Обновлено: 26.04.2024

Интерфейс представляет некое описание типа, набор компонентов, который должен иметь тип данных. И, собственно, мы не можем создавать объекты интерфейса напрямую с помощью конструктора, как например, в классах:

В конечном счете интерфейс предназначен для реализации в классах и структурах. Например, реализуем выше определенный интерфейс IMovable:

При применении интерфейса, как и при наследовании после имени класса или структуры указывается двоеточие и затем идут названия применяемых интерфейсов. При этом класс должен реализовать все методы и свойства применяемых интерфейсов, если эти методы и свойства не имеют реализации по умолчанию.

Если методы и свойства интерфейса не имеют модификатора доступа, то по умолчанию они являются публичными, при реализации этих методов и свойств в классе и структуре к ним можно применять только модификатор public .

Применение интерфейса в программе:

В данной программе определен метод DoAction() , который в качестве параметра принимает объект интерфейса IMovable. На момент написания кода мы можем не знать, что это будет за объект - какой-то класс или структура. Единственное, в чем мы можем быть уверены, что этот объект обязательно реализует метод Move и мы можем вызвать этот метод.

Иными словами, интерфейс - это контракт, что какой-то определенный тип обязательно реализует некоторый функционал.

Консольный вывод данной программы:

Реализация интерфейсов по умолчанию

В данном случае интерфейс IMovable определяет реализацию по умолчанию для метода Move . Класс Person не реализует этот метод, поэтому он применяет реализацию по умолчанию в отличие от класса Car , который определяет свою реализацию для метода Move.

Стоит отметить, что хотя для объекта класса Person мы можем вызвать метод Move - ведь класс Person применяет интерфейс IMovable , тем не менее мы не можем написать так:

Множественная реализация интерфейсов

Рассмотрим на примере:

Класс Message реализует оба интерфейса и затем применяется в программе.

Интерфейсы в преобразованиях типов

Все сказанное в отношении преобразования типов характерно и для интерфейсов. Поскольку класс Message реализует интерфейс IMessage, то переменная типа IMessage может хранить ссылку на объект типа Message:

Преобразование от класса к его интерфейсу, как и преобразование от производного типа к базовому, выполняется автоматически. Так как любой объект Message реализует интерфейс IMessage.

Обратное преобразование - от интерфейса к реализующему его классу будет аналогично преобразованию от базового класса к производному. Так как не каждый объект IMessage является объектом Message (ведь интерфейс IMessage могут реализовать и другие классы), то для подобного преобразования необходима операция приведения типов. И если мы хотим обратиться к методам класса Message, которые не определены в интерфейсе IMessage, но являются частью класса Message, то нам надо явным образом выполнить преобразование типов:

В этой статья я хочу показать на небольшом примере, как же все таки создать простое и красивое десктопное приложение на HTML, CSS, JS и Python. В качестве библиотеки для связи всех компонентов будем использовать EEL.

Установка

Оговорюсь, что я работаю на windows 10 x64.

Для начала установим саму библиотеку выполнив простую команду:
pip install eel
После установки одной лишь библиотеки можно начинать наш путь.

Наше приложение будет выглядеть таким образом:


Логика приложения очень простая: при нажатии кнопки “рассчитать” считываются значения в поле для ввода рублей, полученные данные отправляются в Python, где происходит расчет курса валют. Python возвращает нам данные и мы выводим из через JS

Раскидываем по папкам

Разобьем теперь наш проект на модули. В папку “front” будем класть всё, что связано с графической частью нашего приложения. В папку “back” будем помещать все наши исходники на python. Еще создадим папку middle, в которую будем помещать методы, вызываемые из JS (об этом чуть позже). В Главной директории создадим файл main.py, он будет запускать наше приложение.


Логика на Python

В качестве теста реализуем простое приложение конвертер валют (да-да, банально, никому не надо, на гитхабе миллион проектов). Для этого на python установим библиотеку CurrencyConverter:
pip install currencyconverter

Теперь приступим к написанию логики на python. Реализуем метод, который на вход будет принимать сумму, валюту с которой нам надо перевести и валюту, на которую нам надо перевести. Создадим “convert.py” в директории “back” и запишем в него следующий код:

Теперь в директории “middle” создадим файл “mid_convert.py” и пропишем следующий код:

Что здесь вообще происходит? Файл “mid_convert.py” будет служит связующим звеном между JS и Python. Декоратор @eel.expose дает нашей программе понять, что функция, описанная ниже, может быть вызвана из JS. Таким образом, любая функция, которую мы в будущем захотим вызывать из JS, должна быть обернута декоратором @eel.expose. Очевидно, что не имеет смысла делать лишних модулей, если у вас простой проект, можно было обернуть декоратором функцию “convert_value” в модуле “convert.py”. В больших проектах разбиение программы на такие слои позволит легче расширять и оптимизировать код.

Подключаем Python к JS

Теперь подключим нашу логику на Python к самому JS. Для этого примера я использую простую страничку, сверстанную чисто при помощи HTML, CSS (или вы можете использовать популярные фреймворки, которые нужны вам для работы с графикой). Первым делом необходимо подключить библиотеку eel к самому JS:

Далее реализуем функционал кнопки “рассчитать”. По нажатию кнопки первым делом необходимо считать значение рублей и пойти по каждой валюте, чтобы перевести значение.

Обратите внимание на строку “let value_cur = await eel.convert_value_py(value_rub, "RUB", name_cur)();”. Важно, что вызов функции из питона происходит в асинхронном режиме, поэтому обязательно необходимо сделать асинхронной ту функцию, в которой есть вызов Python кода. При вызове функции из eel необходимо ставить пару (), где в первые круглые скобки будут передаваться наши аргументы.

Проверим, работает ли.


Как мы видим, все работает.

Запуск программы

Для запуска кода используем файл “main.py”. В нем импортируем библиотеку eel и всё, что мы писали в файле “min_convert.py” для того, чтобы при запуске проекта подтянуть все функции, которые мы оборачивали в eel.expose:

Теперь необходимо проинициализировать через eel ту директорию, где лежит front-end нашего приложения. При помощи команды eel.init(args). Далее для запуска самого приложения вызывает метод eel.start(args):

Метод eel.start принимает на вход несколько параметров (подробнее о них см. официальную документацию по eel). Самым интересным параметром является “mode”. При помощи него вы можете запустить свой проект, как в браузере, так и в виде отдельного приложения. Например, вы можете указать параметр “default”, в таком случае приложению будет запускать в браузере по умолчанию. Если у вас стоит google chrome, то указав параметр “mode=chrome” ваша программа откроется в виде отдельного приложения.

Также можно использовать chromium. Для этого скачиваем его с официального сайта. Разархивируем его в какую-нибудь папку и прописываем следующий код в нашем “main.py”:

Вывод

Как мы видим, то создавать красивые десктопные приложения с использованием Python в качестве языка для реализации основной логики не так сложно. Вы можете устанавливать какие угодно библиотеки, реализовать нужный код с использованием Python, а писать красивые интерфейсы на HTML,CSS и JS.

P.S. это моя первая статья на Habr, пишите Ваши пожелания, замечания и отзывы, буду рад увидеть любой комментарий.


Мне кажется, не оправданно много полезных статей не только не посвящают и двух слов самому животрепещущему вопросу при начале работы с JavaFX, но и посвятив, все равно не раскрывают его полностью. А вопрос возникает следующий: как наладить связь ваших node по их fx:id и вашего кода. Как использовать их в разных частях кода, чтобы ссылаться на ваш программный интерфейс? Вот на это, я и постараюсь ответить под катом

Что такое fx:id и с чем его едят

По какой-то причине, для авторов статей и комментаторов на StackOverFlow не очевидно, что попросту не понятно для читателя, как происходит связь fx:id ссылок с вашим кодом. Потому что это не очевидно. А некоторые моменты, вообще больше похожи на магию, этот момент я отдельно упомяну ниже.

Вообще, многие статьи полезны, информативны (особенно на английском), так же, есть ответы почти на все на StackOverFlow. Но, практически нигде развернуто не сказано, как именно работать с fx:id, которые мы определяем в FXML файле. Лишь краткие упоминания, которые приводят к еще большей путанице в голове. Обычно пишут "задайте вашей node нужный вам fx:id и будем вам счастье".

К сожалению, счастья не будет. Будет NullPointerException. По причине некорректного использования, которое идет из непонимания области видимости, скажем так, этих id. И я хочу рассказать, по какой причине получается exception, а главное, как корректно связать ваш FXML с классом-контроллером и java-кодом. Надеюсь, это поможет людям избежать тех мучений, которые прошел я в поисках ответа на данный вопрос.

Я бы наверно и пришел к ответу сам, рано или поздно, но спустя многие часы дни поиска, я наткнулся на статью. В ней рассказано про MVC модель, которую и следует использовать при разработке на JavaFX. Не стану повторять написанное в статье, там достаточное описание этой модели. Если вы о ней не слышали, то к прочтению обязательно, поскольку можно обойтись работая с javaFX вообще без FXML, на чистом java коде, но не стоит. Так же, там есть некоторое пояснение, как заставить ожить fx:id, но я все же хочу дополнить эту статью своими наблюдениями и знаниями в оригинальной статье.

Модель эта, кстати, не так очевидна, даже если смотреть статьи на Oracle. По крайней мере, я не нашел у Oracle, как мне работать с fx:id. Зато очень много экспериментировал, что и привело к пониманию, как с ними работать.

Итак, с предисловием пора заканчивать, к делу.

Что конкретно не очевидно из примеров в статьях? Примеры в интернетах, предлагают в FXML оформить дизайн приложения, после создать к нему класс-контроллер, который наследуется от Application, в нем определить метод start() (или сделать это в классе Main, разные есть примеры) и там пользоваться вашими fx:id. И это работает. Казалось бы, чудненько, все так просто, что даже хочется сплясать.

Вот даже банальный пример, естественно "hello world":

Если его запустить и ткнуть в кнопку, то в консоли получим результат, у кнопки изменится ее название. Лихо? А вот и нет. Это не вносит ясности, как с этим работать. И такими примерами пестрит интернет. А если не делать MVC модель или каким-либо иным способом не разделять и властвовать, получится каша, на которой подскользнешься, ну и как говорят во всех американских фильмах, "упадешь и сломаешь бедро". Причем, бедренная кость — одна из самых крепких…

Следующим, логичным казалось бы шагом, было бы взять в этом же коде выше, убрать из него метод click и поработать напрямую, из кода. Например из метода start(), чтобы далеко не ходить, добавить в него обработку клика мышки на кнопку fxButton (панель/иную часть интерфейса, не имеет значения, это простой пример fx:id):

Вас обматерит ваша IDE еще при запуске

Process finished with exit code 1

В первый миг, захочется материться в ответ, ведь не ясно, почему не инициализирована переменная. Ведь это контроллер, он знает про FXML файл, в него с успехом обращаются элементы интерфейса и обрабатываются, ведь в коде с методом click мы так и делали, обращались по fx:id.
Но это работает только в методах, в которые они жестко закодированы в FXML. А при прямом обращении из кода java в файл FXML, а не наоборот, получается, что объявленная в классе переменная Button fxButton, к которой мы в методе успешно обратились — не инициализирована. Вот казалось бы, незадача. И волки вроде сыты, да и овцы целы, но вот пастух слегка лукавит.

Ваша IDE, например, IDEA, вполне успешно генерирует пример выше, модель в нем верная(генерируются FXML и классы Controller, Main и немного кода в нем). Этого достаточно для начала работы. Я немного дописал кода, для наглядности, но сделал это в Main классе, вместо контроллера, для экономии места.

Итак, выходит что для взаимодействия с вашими node в в FXML файле, вам требуется создать метод, в котором действие будет обработано. Но обратите внимание, именно действие по элементу интерфейса. Внутри метода, можно так же использовать fx:id.

Но, стоит задуматься о использовании кнопки из примера выше в других частях кода (точнее выразиться, использовании fx:id этих элементов), даже в этом же классе (а это — контроллер), вас ждет NullPointerException и отсутствие понимания, собственно, WHY? А вдруг я хочу в коде ссылаться на какую-либо панель, сделав ей отдельный fx:id, менять текст или внешний вид форм, при этом не взаимодействуя физически с кнопками и прочими элементами, делая это из кода, согласно какой-то логике? Какое верное решение?

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

Вернувшись и проковыряв дыру в интернете и в голове, перепробовав разные варианты, я докопался до истины. Местами в интернете, видел предложения провести инициализацию. Собственно, это и оказалось решением, но нигде опять же, нет примеров. По такому случаю, сейчас примеры с разъяснениями будут у меня. Как должна выглядеть программа на javaFX, прилагаю код.

FXML. Внешний вид. Необходимо определить в нем контроллер

Main. Единственная задача, запустить приложение, загрузить сцену из FXML

Controller. Адаптер FXML и java кода.

View. Отвечает за то, что отображает приложение.

В контроллере и View я показал, как можно взаимодействовать разными способами с fx:id.

Прямо закодировать в FXML вызов метода в контроллере.

Инициализировать используемые id и иже с ними node и передавать в необходимое место в коде, вплоть до присваивания локальным переменным этих node по их fx:id.


Разберу работу кода, предложенного выше. Запускается вот такого вида окно:

В FXML файле определен текст labelFx, но в initialize() он при запуске, получает значение, которое определено в методе.


При нажатии на кнопку:

Происходит вывод на консоль "Hello World", у кнопки меняется текст на "Hey!", управление переходит в метод labelLocalInitialize(), в нем локально определенной переменной назначается объект labelFx. Следом, labelFx назначается новый текст.

Резюмируя:

  • При инициализации через initialize(), достаточно просто обратиться к любой node по ее fx:id. Это инициализирует все node, которые определены в FXML. И они будут доступны и в Controller и во View по своим fx:id
  • onAction (или любое иное действие) из FXML, которое, например как в коде выше, вызывает метод click, так же инициализирует все, аналогично методу initialize
  • Чтобы передать управление по fx:id классу, который не является контроллером или View, нужно создать локальную переменную в классе, где вам необходимо управление и присвоить ей значение в контроллере или View.
  • Минимальная программа на JavaFX должна содержать FXML и классы Controller, Main. Если она не однокнопочная, само собой. Можно написать на чистом коде, без FXML. Но это потребует много строк кода. На мой взгляд, FXML нагляднее, просто есть нюансы, которые я выше и раскрыл.
  • Main.java — класс, отвечающий лишь за запуск приложения и загрузку вашего FXML. Никаких более действий, он не выполняет и не должен.
  • Controller.java — класс, являющийся прослойкой между кодом на java и FXML файлом. Адаптер, если угодно. Через него происходит связь всех ссылок fx:id, в его методах обрабатываются вызовы интерактивных элементов интерфейса, тут же происходит инициализация.

Где-то выше я говорил, что есть магия в javaFX. Она начинается в классе View. Вроде, очевидно бы было, если бы от контроллера можно было наследовать классы, для работы с id, но происходит обратное… Почему так сделано, я в подробности не вдавался, хотя и любопытно, почему так. Если в комментариях знающие люди приоткроют завесу тайны, буду весьма благодарен.

Реализация в классе:

Но если я просто удалю интерфейс и сделаю вот так

На этот вопрос отвечали много раз, используйте поиск. Если вы захотите составить список всех объектов, которые могут двигаться: машин, людей, галактик. какой у него будет тип?

2 ответа 2

Есть как минимум 4 причины для использования интерфейсов. Их может и больше, но я текст пишу из головы, потому остальные причины, если такие есть, загуглите или додумаете сами.

Давайте начнем с того, как строятся большие и сложные системы. Основной механизм построения чего то большого - это разделение этого большого на малые части, определения того, как эти части взаимодействуют, и после этого программирование каждой из частей.

Ключевой момент здесь в том, что вы не можете сразу продумать все эти мелкие части и сазу написать все классы для них.

Давайте возьмем типичный пример. Вы пишете свою игру и вам надо предусмотреть сохранение вашей игры. Вы точно знаете, что именно вам надо сохранить, но вы ещё не знаете, куда вы будете сохранять, в файл или в БД или передавать данные на сервер. Что делать в такой ситуации?

В такой ситуации вам поможет интерфейс. Например, у вас есть класс, который вам надо сохранить.

Напишем для него интерфейс сохранения

Что тут произошло? Вашего сохранения ещё в природе нет, кода для него нет, какой это будет класс, какая у класса-хранилища будет иерария наследования, ничего не известно, но вы уже определили, как ваша игра будет взаимодействовать с этим, ещё не существующим классом. Вы можете этот класс написать позже, или его может написать любой другой программист и все будет работать, так как контракт вашего будушего класса уже известен - вы уже определили с помощью интерфейса все нужные для класса методы.

Другими словами, интерфейс помогает определять взаимодействие между модулями вашей программы, даже если эти модули ещё не существуют.

Но тут вы можете возразить мне и сказать - что вы с таким же успехом можете определить класс для сохранения, например

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

Теперь посмотрите что произошло. Мы не только заставляем классы для сохранения игры наследоваться от нашего абстрактного класса, но ещё и накладываем на них определенные огреничения. Интересны ли эти ограничения остальной части игры? Думаю, нет. В таком случае, зачем остальной части игры знать об этих огреничениях? Нет никаких причин для этого. Таким образом, Используя интерфейс, вы декларируете желаемое поведение. Используя класс - вы декларируете детали реализации. То есть, когда у вас был интерфейс в игре, вы декларировали, что "не важно что тут будет за объект, не важно как он будет написан, но мне надо чтобы он мог сохранять и загружать состояние игры" - то есть вам было важно, что объект может делать, при этом не важно, как он это делает. В этом суть инкапсуляции. Когда же вы используете класс, особенно если класс содержит какие либо посторонние детали, вы уже покажываете, что вам важно не только что объект может делать, но и также кем объект является и какие у объекта есть детали реализации.

А теперь давайте посмотрим на это с точки зрения игры. Игре, на самом деле, не интересно знать, как и куда будет происходить сохранение. Игре не интересны детали сохранения. Игре не интересен даже факт - сущестует ли уже написанный класс сохранения. Все, что игре надо - это получить объект, который содержит нужные игре методы. Всё. При таком подходе связь между игрой и реальным классом сохранения лежит только через интерфейс, что является связью гораздо более слабой. Сам класс может быть написан как угодно, содержать какие угодно огранчения или детали реализации, эти ограничения и детали могут меняться и быть переписаны любое количество раз и все будет работать, пока класс сохранения реализует интерфейс. Таким образом, связь классов через интерфейс является более слабой связью, чем связь классов через абстрактный класс или напрямую, откуда следует выводы:

Привет, я из Болгарии.
Теперь я начинаю изучать Python и у меня есть задание написать небольшую программу, для извлечения размера дверей фурнитуры. Написал код, но я не могу подключиться к GUI. Использую QT дизайнер и сделал это.


Мой вопрос заключается в том, как я могу сделать так, чтобы моя программа работает?

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


Замирание графического интерфейса
Доброго времени суток. Во время работы на компьютере происходит замирание изображения на экране.


Создание графического интерфейса
Всем привет! Подскажите, как создаются приложения с графическим интерфейсом? Например, та же Visual.

Организация графического интерфейса
Реализовать графический интерфейс, используя заданный framework WPF для задачи из файла.

По вашему .ui-файлу надо сгенерировать py c помощью программы pyuic4.
Затем его подключаете, примерно так:

Вот код преобразованного файла. Можете ли вы показать мне, где поставить что?

Я вроде понятно написал. '<класс из сгенерированного py>' - это ваш Ui_MainWindow.

И не надо писать ваш код в этом файле. Напишите в другом, Ui_MainWindow импортируйте. Для вас там сверху есть предупреждение: WARNING! All changes made in this file will be lost!

Создание графического интерфейса
Дали задание написать матричный калькулятор.Я вроде с этим справился теперь нужен интерфейс для неё.

Построение графического интерфейса
Здравствуйте, Вот само задание Необходимо построить имитационную модель одноканальной СМО с.

Реализация графического интерфейса
Добрый день.Долгое время уже изучаю c++,сейчас надоели консольные приложения,решил научиться делать.

Создание графического интерфейса в С?
Доброго времени суток.Так как я еще новичок в этой теме, то у меня родился вопрос:Как можно сделать.

Читайте также: