Rust 애플리케이션용 GUI 만들기
36209 단어 devjournalrusttutorialbeginners
코드 내에서 직접 작업하지 않고 시각적 인터페이스를 생성하기 위해 Glade이라는 애플리케이션을 사용합니다. 구성 요소를 드래그 앤 드롭하고 인터페이스에 대한 구성 요소, 위치, 정보 등으로 XML을 생성하기만 하면 GTK UI를 쉽게 구축할 수 있습니다.
필수 크레이트는 GTK 및 GIO입니다. 프로젝트에서 Cargo.toml 파일이나 Cargo.toml 파일에 이것을 추가하기만 하면 됩니다.
[dependencies.gtk]
version = "0.9.0"
features = ["v3_16"]
[dependencies.gio]
version = ""
features = ["v2_44"]
우리가 만들 예제 응용 프로그램은 다음과 같습니다. 이 색상의 이름을 지정합니다. 이를 통해 사용자는 색상을 선택하고 원하는 이름을 지정할 수 있습니다. 간단하지만 설명 가능합니다.
따라서 NTC 인터페이스가 있습니다. 나에게는 XML이 너무 많은 것 같습니다... 인간이 보아야 할 것처럼 봅시다.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="main_window">
<property name="width_request">450</property>
<property name="height_request">300</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Name this color</property>
<property name="resizable">False</property>
<property name="window_position">center</property>
<child type="titlebar">
<placeholder/>
</child>
<child>
<object class="GtkFixed">
<property name="width_request">450</property>
<property name="height_request">300</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="color_name_entry">
<property name="name">color_name_entry</property>
<property name="width_request">166</property>
<property name="height_request">40</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="x">145</property>
<property name="y">170</property>
</packing>
</child>
<child>
<object class="GtkColorButton" id="color_selection">
<property name="name">color_selection</property>
<property name="width_request">100</property>
<property name="height_request">80</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="x">175</property>
<property name="y">45</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="select_color_label">
<property name="name">select_color_label</property>
<property name="width_request">100</property>
<property name="height_request">34</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Select a color</property>
</object>
<packing>
<property name="x">175</property>
<property name="y">10</property>
</packing>
</child>
<child>
<object class="GtkButton" id="save_button">
<property name="label" translatable="yes">Save</property>
<property name="name">save_button</property>
<property name="width_request">100</property>
<property name="height_request">40</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="x">175</property>
<property name="y">250</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="name_color_label">
<property name="name">name_color_label</property>
<property name="width_request">100</property>
<property name="height_request">41</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Name this color</property>
</object>
<packing>
<property name="x">175</property>
<property name="y">135</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="registered_color_label">
<property name="name">registered_color_label</property>
<property name="width_request">120</property>
<property name="height_request">25</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="x">165</property>
<property name="y">215</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
따라서 인간에게는 다음과 같이 보입니다.
그것은 무엇입니까?
너무 간단합니다. 선택한 RGBA의 색상과 이름을 가져와 이름과 색상의 두 벡터가 있는 구조에 저장합니다. 이 구조는 빨강, 녹색, 파랑 및 알파 필드로 정의된 또 다른 구조입니다. 그 후 캡쳐한 이름과 색상을 스트럭처에 푸시합니다.
코드를 보여주는 것이 항상 더 나은 설명입니다.
// src/ntc.rs
#[derive(Debug, PartialEq)]
pub struct Color {
pub red: f64,
pub green: f64,
pub blue: f64,
pub alpha: f64
}
pub struct NTC {
pub names: Vec<String>,
pub colors: Vec<Color>
}
impl NTC {
pub fn new() -> Self {
NTC {
names: vec![],
colors: vec![]
}
}
pub fn save_color(&mut self, color: Color, name: String) -> Result<(), String> {
if self.colors.contains(&color) || self.names.contains(&name) {
Err("The color was already saved!".to_string())
} else {
self.colors.push(color);
self.names.push(name);
Ok(())
}
}
}
기본 코드는 자체적으로 설명됩니다.
// src/main.rs
use std::{cell::RefCell, path::Path, rc::Rc};
// gtk needs
use gtk::prelude::*;
use gio::prelude::*;
use ntc::Color;
mod ntc; // importing the ntc module
fn main() {
gtk::init() // This function will initialize the gtk
.expect("Could not init the GTK");
// and if something goes wrong, it will send this message
/*
The documentation says about gtk::init and gtk::Application::new:
"When using Application, it is not necessary to call gtk_init manually.
It is called as soon as the application gets registered as the
primary instance".
It worth to check it.
*/
// Here it defined a gtk application, the minimum to init an application
// There are some caveats about this
/*
To build this interface, I have used a component GtkWindow as father of from
all others components, hence, it needed to create Gtk::Application inside
de code.
If a GtkApplicationWindow had been to choose, it would not be necessary,
because it alraedy had a Gtk::Applicaiton "inside".
*/
let application = gtk::Application::new(
Some("dev.henrybarreto.name-this-color"), // Application id
Default::default() // Using default flags
).expect("Could not create the gtk aplication");
// The magic happens in this line
// The ntc.glade is pushed into our code through a builder.
// With this builder it is possible to get all components inside the XML from Glade
let builder: gtk::Builder = gtk::Builder::from_file(Path::new("ntc.glade"));
// ----------------------------------------------------------|
let colors_saved = Rc::new(RefCell::new(ntc::NTC::new()));// |
// ----------------------------------------------------------|
// when the signal connect_activate was sent, the application will get our
// components for work
application.connect_activate(move |_| {
// All components from the ntc.glade are imported, until the one has not used to
// for didactic propouses
// the "method" get_object gets from the id.
let main_window: gtk::Window = builder.get_object("main_window").expect("Could not get the object main_window");
let save_button: gtk::Button = builder.get_object("save_button").expect("Could not get the save_button");
let color_selection: gtk::ColorButton = builder.get_object("color_selection").expect("Could not get the color_selection");
let color_name_entry: gtk::Entry = builder.get_object("color_name_entry").expect("Could not get the color_name_entry");
//let _select_color_label: gtk::Label = builder.get_object("select_color_label").expect("Could not get the select_color_label");
//let _name_color_label: gtk::Label = builder.get_object("name_color_label").expect("Could not get the name_color_label");
let registered_color_label: gtk::Label = builder.get_object("registered_color_label").expect("Could not get the registeredd_color_label");
let colors_saved = colors_saved.clone();
// When the button was clicked...
// The "main" logic happen here
save_button.connect_clicked(move |_| {
let color_rgba = color_selection.get_rgba(); // getting the color from the button
let color: Color = Color { // setting manually color by color for didactic.
red: color_rgba.red,
green: color_rgba.green,
blue: color_rgba.blue,
alpha: color_rgba.alpha
};
let name = color_name_entry.get_text().to_string(); // getting name from the entry
registered_color_label.set_visible(true); // Letting the label visible
if let Ok(()) = colors_saved.borrow_mut().save_color(color, name) { // if the color is saved correctly
registered_color_label.set_text("Registered!");
} else { // when does it not
registered_color_label.set_text("Already Registered!");
}
});
// "event" when the close button is clicked
main_window.connect_destroy(move |_| {
// the gtk application is closed
gtk::main_quit();
});
main_window.show(); // showing all components inside the main_window
});
application.run(&[]); // initializing the application
gtk::main(); // initializing the gtk looping
}
이 코드의 한 가지 세부 사항은 Rc 및 RefCell의 사용에 대해 설명하는 것이 중요합니다. Rust의 작동 방식과 메모리 관리 시스템을 알면 Fn 특성 함수를 통해 변수 정의를 이동하는 것이 좋은 생각이 아니며 컴파일러에서 허용하지 않는다는 것을 알 수 있습니다.
나는 이 블로그가 단지 Rc와 RefCell에 대해 이야기하기 위한 것임을 인정할 수 있지만, 나는 진실을 말하지 않는 것을 선호합니다.
Rc 및 RefCell
이전 블로그에서 이미 Rc에 대해 언급했지만 학습 제안을 위해 요약하겠습니다. Rc는 Rust에서 단일 값이 여러 소유자를 가질 수 있도록 하는 데 사용됩니다. 여기서 핵심 단어는 "단일 가치"와 "다중 소유자"입니다.
Rc와 달리 RefCell은 하나의 소유자 데이터에 단일 값을 보유합니다. 여기서 핵심 단어는 "단일 값"과 "하나의 소유자 데이터"입니다. 물론 간단하게 설명하려고 하면 문제가 될 수 있으므로 자세한 설명이 포함된 글꼴을 맛보는 것이 좋지만 등록을 위해서도 시도해 보겠습니다.
나는 이것이 일부 차용 규칙에 대해 컴파일러를 속이는 방법이라고 생각하고 이 규칙을 런타임에 확인하도록 합니다. 이 차용 규칙은 이것에 대한 참조가 불변인 경우에도 데이터의 변이와 관련이 있습니다. 이를 위해 내부적으로 안전하지 않은 코드를 사용하지만 "안전하지 않은"주제는 이에 대한 전체 블로그가 될 것입니다.
우리 애플리케이션에서는 이 개념을 사용하여 외부 NTC 구조체를 만들고 클로저 내부의 색상과 이름을 가져오고 이 외부 구조에 대한 가변 참조를 통해 저장했습니다. 다른 프로그래밍 언어에서는 사소한 일이겠지만 Rust는 자신만의 방식으로 작업을 수행합니다... 저를 기쁘게 합니다.
프로젝트는 이렇게 끝납니다.
.
├── Cargo.lock
├── Cargo.toml
├── ntc.glade
└── src
├── main.rs
└── ntc.rs
간단하지 않습니까?
응용 프로그램은 어떻게 생겼습니까?
레지스터가 추가되면...
색상 또는 이름이 "데이터베이스" 내에 이미 존재하는 경우.
이것은 좀 더 개인적인 블로그였습니다. 이 블로그에서 저는 제가 하려고 했던 것과는 반대로 더 "i"가 되려고 노력했지만, 여전히 학습한 내용에 대한 저널과 같고 미래 검토자에게 초록이 됩니다.
읽어 주셔서 감사합니다. 자유롭게 댓글을 달거나 정정하거나 인사만 해주세요. 나는 그것이 누군가를 돕기를 바랍니다.
유용한 링크:
Reference
이 문제에 관하여(Rust 애플리케이션용 GUI 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/henrybarreto/creating-a-gui-for-a-rust-application-2h1g텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)