INTRO
Итак, в предыдущем посте мы сделали чистую GTK-клавиатуру с GTK-кнопками. Возможно, это хорошая идея сравнить два способа разработки небольшой клавиатуры.
Теперь мы сделаем то же самое, используя внешний файл разметки, назовем его grid.ui, он слишком большой, чтобы публиковать его здесь. Вы можете просто взять его с моего github.
Если вы такой же новичок, как и я, вы можете спросить: «Зачем нам использовать дополнительный файл, если мы можем использовать только GTK?». Думаю, у нас может быть как минимум две причины:
1) Это помогает перенести часть GUI в markdown файл, чтобы сделать наш код легко читаемым.
2) Некоторые функции работают по-разному в разных случаях, и интересно, как мы можем это использовать.
Кстати: чистые кнопки GTK не масштабируются по умолчанию, но эти масштабируются.
Некоторые вещи я уже объяснял в предыдущем посте, поэтому просто покажу, что нам нужно в нашем проекте.
Откройте Cargo.toml и добавьте список ящиков
[dependencies]
gtk = "0.15.5"
glib = "0.15.10"
chrono = "0.4.19"
Мы не будем изменять файл main.rs
use gtk::prelude::*;
mod buttons;
fn main() {
let application =
gtk::Application::new(Some("com.github.gtk-rs.examples.grid"), Default::default());
application.connect_activate(buttons::build_ui);
application.run();
}
Мы запускаем файл buttons.rs всего с несколькими строками кода.
use gtk::prelude::*;
use gtk::{ApplicationWindow, Builder};
pub fn build_ui(application: >k::Application) {
let glade_src = include_str!("grid.ui");
let builder = Builder::from_string(glade_src);
let window: ApplicationWindow = builder.object("window").expect("Couldn't get window");
window.set_application(Some(application));
// We are gonna add our buttons and functions here
window.show_all();
}
Если вы cargo run
его запустите, то увидите все элементы, присоединенные к сетке, потому что все они созданы в файле markdown src/grid.ui
. Все, что нам нужно — просто подключить этот файл
let glade_src = include_str!("grid.ui");
Затем отправьте glade_src в Builder как строку slice &str.
let builder = Builder::from_string(glade_src);
И подключитесь к ApplicationWindow
let window: ApplicationWindow = builder.object("window").expect("Couldn't get window");
Еще раз:
1) Создайте файл markdown со всеми необходимыми элементами
2) Привяжите его к переменной
3) Отправьте эту переменную в GTK builder
4) Подключите его к ApplicationWindow
Давайте снова посмотрим на чистый GTK (не добавляйте этот код в наш текущий проект)
let window = gtk::ApplicationWindow::new(application);
window.set_title("Studying GTK");
window.set_default_size(200, 120);
let grid = gtk::Grid::builder()
.margin_start(7)
.margin_end(7)
.margin_top(7)
.margin_bottom(7)
.halign(gtk::Align::Center)
.valign(gtk::Align::Center)
.row_spacing(7)
.column_spacing(7)
.build();
window.set_child(Some(&grid));
Видите разницу? Мы должны создать окно, затем сделать сетку и подключиться к нашему окну. Давайте вернемся к нашему текущему проекту.
Мы используем файл markdown, и он должен выглядеть так, как показано на скриншоте ниже. Это куча бесполезных кнопок, потому что Rust не знает об их существовании. Как я уже говорил — это просто строковый срез содержимого нашего файла markdown. Мы должны объяснить Rust, как его использовать. И здесь нам нужен идентификатор класса.
<object class="GtkButton" id="button0">
Давайте рассмотрим это подробнее в следующей главе.
1. Скользящая кнопка
Обновите зависимости и давайте подключим сетку.
use gtk::glib;
use gtk::prelude::*;
use gtk::{ApplicationWindow, Builder};
use gtk::{Button, Grid};
Сначала нам нужна переменная типа Grid. Она вызывает gtk::Builder
и метод object(), который получает некоторую "grid"
. Что это такое?
let grid: Grid = builder.object("grid").expect("Couldn't get grid");
Это класс и id. Класс говорит о том, что это за элемент. Id — это уникальный идентификатор именно этого элемента. Например, это может быть элемент типа "GtkGrid"
с id "grid"
.
<object class="GtkGrid" id="grid">
Как вы помните, мы говорили Builder, что наш GUI хранится в файле grid.ui.
let glade_src = include_str!("grid.ui");
let builder = Builder::from_string(glade_src);
Теперь gtk::Builder
может найти элемент по его id внутри markdown файла и привязать к нему функцию. И мы сделаем это прямо сейчас.
Давайте создадим кнопку button0 и вызовем ее по id "button0"
.
let button0: Button = builder.object("button0").expect("Couldn't get button0");
Теперь мы можем добавить к ней функцию, вызвав connect_clicked()
.
button0.connect_clicked(glib::clone!(@weak grid => move |button| {
let left_attach = grid.cell_left_attach(button);
let new_left_attach = if left_attach == 2 { 0 } else { left_attach + 1 };
grid.set_cell_left_attach(button, new_left_attach);
}));
window.show_all();
}
Мы используем glib::clone!
для создания клона и @weak или @strong (похоже, оба делают одно и то же в данном случае), чтобы сообщить Rust, какой тип клона мы хотим использовать. Затем мы перемещаем кнопку типа в закрытие и возвращаем ее в конце с новой позицией в сетке.
Здесь мы объявляем две переменные: left_attach и new_left_attach. После запуска приложения первая переменная содержит 1.
Значение new_left_attach зависит от условия if else. Каждый щелчок мыши увеличивает left_attach на 1. Как только оно становится равным 2, условие else делает его равным 0. Попробуйте изменить условия,
<property name="left_attach">1</property>
и ширину кнопки 0.
Хорошо, теперь кнопка 0 сдвигается вправо каждый раз, когда мы нажимаем на нее.
2. Числовые кнопки
Я говорил вам, что gtk::Builder
может привязывать функцию к id элемента, но он также может отправлять данные. Давайте добавим числовые кнопки и заставим их менять свои собственные метки.
Соедините кнопки, используя их id
let button1: Button = builder.object("button1").expect("Couldn't get button1");
let button2: Button = builder.object("button2").expect("Couldn't get button2");
let button3: Button = builder.object("button3").expect("Couldn't get button3");
let button4: Button = builder.object("button4").expect("Couldn't get button4");
let button5: Button = builder.object("button5").expect("Couldn't get button5");
let button6: Button = builder.object("button6").expect("Couldn't get button6");
let button7: Button = builder.object("button7").expect("Couldn't get button7");
let button8: Button = builder.object("button8").expect("Couldn't get button8");
let button9: Button = builder.object("button9").expect("Couldn't get button9");
Добавьте функцию, которая после нажатия на кнопку установит ее метку в числовую букву. Обратите внимание, что в этом случае нам не нужно использовать фигурные скобки. Также нам не нужен glib::clone! потому что мы не перемещаем никаких данных внутри закрытия. Метод set_label() устанавливает новую метку.
button1.connect_clicked( move |button| button.set_label("I") );
button2.connect_clicked( move |button| button.set_label("II") );
button3.connect_clicked( move |button| button.set_label("III") );
button4.connect_clicked( move |button| button.set_label("IV") );
button5.connect_clicked( move |button| button.set_label("V") );
button6.connect_clicked( move |button| button.set_label("VI") );
button7.connect_clicked( move |button| button.set_label("VII") );
button8.connect_clicked( move |button| button.set_label("VIII") );
button9.connect_clicked( move |button| button.set_label("IX") );
Запустите и посмотрите! Цифровые кнопки 1-9 меняют свои метки после первого нажатия.
3. Кнопка «Выход
Другая зависимость
use glib::clone;
В этом случае @weak и @strong — одно и то же. Возможно. Все еще не уверен, как это работает.
Объявляем quit_button с типом Button. Обратите внимание на имя object()
! Здесь мы вызываем объект по его id. В предыдущей главе |button|
был типом, а не данными. Но теперь нам нужен glib::clone!
, потому что мы перемещаем окно внутрь закрытия, чтобы уничтожить его.
let quit_button: Button = builder.object("quit_button").expect("Couldn't get quit_button");
quit_button.connect_clicked(clone!(@weak window => move |_|
unsafe {
window.destroy()
}
));
Давайте сравним это с проектом без markdown-файла из моего предыдущего сообщения (не добавляйте этот код в наш текущий проект). Как вы можете видеть, здесь мы создаем сами кнопки.
let quit_button = gtk::Button::with_label("Quit");
Функция подключения, которая полностью аналогична предыдущей…
quit_button.connect_clicked(clone!(@weak window => move |_|
unsafe {
window.destroy()
}
));
и прикрепление его к сетке.
grid.attach(&quit_button, 3, 1, 1, 4);
Разница: 1) декларирование 2) прикрепление
4. Обновление метки
Теперь щелкните на кнопке, чтобы сделать метку, принимающую данные.
Надеюсь, вы заметили, что когда мы используем внешний файл разметки, необходимы аннотации типов. Мы должны сказать компилятору — это GTK-объект типа Label.
let counter_label: gtk::Label = builder.object("GtkLabel_1").expect("Couldn't get GtkLabel_1");
Объявление minus_button и plus_button такое же, как и раньше, ничего особенного.
let minus_button: Button = builder.object("minus").expect("Couldn't get minus");
let plus_button: Button = builder.object("plus").expect("Couldn't get plus");
Функции будут на сто процентов такими же, как мы сделали в чистом проекте GTK.
plus_button.connect_clicked(glib::clone!(@weak counter_label => move |_| {
let nb = counter_label.text()
.parse()
.unwrap_or(0.0);
counter_label.set_text(&format!("{}", nb + 1.1));
}));
minus_button.connect_clicked(glib::clone!(@weak counter_label => move |_| {
let nb = counter_label.text()
.parse()
.unwrap_or(0.0);
counter_label.set_text(&format!("{}", nb - 1.2));
}));
Теперь кнопки работают и отправляют данные на ярлык.
5. Таймер
Введите новый путь в область видимости.
use chrono::Local;
В конце список путей выглядит следующим образом.
use gtk::glib;
use glib::clone;
use gtk::prelude::*;
use gtk::{ApplicationWindow, Builder};
use gtk::{Button, Grid};
use chrono::Local;
Объявим переменную времени, как мы делали это в предыдущем проекте.
let time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"));
Но эта часть отличается!
let label_time: gtk::Label = builder.object("GtkLabel_2").expect("Couldn't get GtkLabel_2");
label_time.set_text(&time);
Просто сравните с аналогичной частью в предыдущем проекте (не добавляйте этот код в наш текущий проект).
let label_time = gtk::Label::new(None);
label_time.set_text(&time);
grid.attach(&label_time, 0, 6, 4, 1);
Это то же самое, что и в случае с чистой сеткой GTK.
let tick = move || {
let time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"));
label_time.set_text(&time);
// What is this?
glib::Continue(true)
};
glib::timeout_add_seconds_local(1, tick);
Теперь запустите его и вы увидите следующее. Все кнопки работают нормально. Осталась только одна маленькая деталь…
6. Вход не работает!
Согласно документации Entry должен выглядеть так, но, к сожалению, мне не удалось заставить его работать.
<object class="GtkEntry">
<attributes>
<attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
<attribute name="background" value="red" start="5" end="10"/>
</attributes>
</object>
Я потратил некоторое время, пытаясь решить эту загадку. Возможно, я вернусь к этому вопросу позже.
ЗАКЛЮЧЕНИЕ
Здесь еще есть несколько вещей, которые я пока не совсем понимаю. А также есть некоторые идеи, которые я не могу реализовать прямо сейчас. Например, встроенный дисплей или кнопка сохранения файла. Но я думаю, что постепенно разберусь и с этим.
Надеюсь, этот пост был полезен и познавателен.
Берегите себя