本示例展示了基于 FLTK 和 SQLITE 制作用户管理程序。代码比较长,但是不难看懂。
main.rs
mod store {
use super::Userdata;
use anyhow::Result;
use rusqlite::Connection;
pub struct Database {
conn: Connection,
}
impl Database {
pub fn create_connection() -> Result<Self> {
let conn = Connection::open(":memory:")?;
conn.execute(
r#"CREATE TABLE IF NOT EXISTS userdata (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
age INTEGER NOT NULL
);"#,
(),
)?;
Ok(Self { conn })
}
pub fn insert_userdata(&self, username: &str, age: u64) -> Result<()> {
self.conn.execute(
r#"INSERT INTO userdata (username, age) VALUES (?, ?);"#,
(username, age),
)?;
Ok(())
}
pub fn insert_many_userdata(&mut self, count: u64, username: &str, age: u64) -> Result<()> {
let tx = self.conn.transaction()?;
for _ in 0..count {
tx.execute(
r#"INSERT INTO userdata (username, age) VALUES (?, ?);"#,
(username, age),
)?;
}
tx.commit()?;
Ok(())
}
pub fn update_userdata(&self, id: u64, username: &str, age: u64) -> Result<()> {
self.conn.execute(
r#"UPDATE userdata
SET username = ?, age = ?
WHERE id=?;"#,
(username, age, id),
)?;
Ok(())
}
pub fn delete_userdata(&self, id: u64) -> Result<()> {
self.conn
.execute(r#"DELETE FROM userdata WHERE id=?;"#, (id,))?;
Ok(())
}
pub fn get_all_userdata(&self, limit: u64, offset: u64) -> Result<Vec<Userdata>> {
let mut stmt = self
.conn
.prepare("SELECT id, username, age FROM userdata limit ? offset ?")?;
let person_iter = stmt.query_map([limit, offset], |row| {
Ok(Userdata {
id: row.get(0)?,
username: row.get(1)?,
age: row.get(2)?,
})
})?;
let mut result = Vec::new();
for person in person_iter {
result.push(person?);
}
Ok(result)
}
pub fn get_count(&self) -> Result<u64> {
let mut stmt = self.conn.prepare("SELECT COUNT(*) FROM userdata;")?;
let rows = stmt.query([])?.and_then(|row| {
let count: u64 = row.get(0)?;
anyhow::Ok(count)
});
for row in rows {
return Ok(row?);
}
Ok(0)
}
}
}
use fltk::{
app::{channel, App},
dialog::{alert_default, input_default},
};
#[derive(Clone, Debug, Default)]
pub struct Userdata {
pub id: u64,
pub username: String,
pub age: u64,
}
mod ui {
use super::Userdata;
use fltk::app::Sender;
// use fltk::prelude::WidgetExt;
fl2rust_macro::include_ui!("ui/main.fl");
#[derive(Clone)]
pub enum Message {
Insert,
QueryAll,
SelectUser,
UpdateUser,
DeleteUser,
SetRows,
Before,
Next,
Refresh,
InsertMany,
SetPage
}
trait SenderForAppUi {
fn bind<W>(&self, widget: &mut W, message: Message)
where
W: WidgetExt + Sized;
}
impl SenderForAppUi for Sender<Message> {
fn bind<W>(&self, widget: &mut W, message: Message)
where
W: WidgetExt + Sized,
{
widget.emit(self.clone(), message);
}
}
macro_rules! emit {
($sender:expr, {$($widget:expr => $message:expr),+}) => {
$(
let sender = $sender.clone();
$widget.set_callback(move |_| sender.send($message));
)+
};
}
impl UserInterface {
pub fn emit(&mut self, sender: Sender<Message>) {
emit!(sender, {
self.btn_insert_new => Message::Insert,
self.btn_insert_to_select => Message::UpdateUser,
self.btn_delete_select => Message::DeleteUser,
self.btn_refresh => Message::Refresh,
self.btn_set_rows => Message::SetRows,
self.btn_go_before => Message::Before,
self.btn_go_next => Message::Next,
self.btn_many => Message::InsertMany,
self.sql_browser => Message::SelectUser
});
}
pub fn get_edit_userdata(&self) -> Userdata {
Userdata {
id: self.input_id.value().parse::<u64>().unwrap_or(0),
username: self.input_username.value(),
age: self.input_age.value().parse::<u64>().unwrap_or(0),
}
}
pub fn set_edit_userdata(&mut self, userdata: &Userdata) {
self.input_username.set_value(&userdata.username);
self.input_age.set_value(userdata.age.to_string().as_str());
self.input_id.set_value(userdata.id.to_string().as_str());
}
fn sql_browser_prefare(&mut self) {
self.sql_browser.clear();
self.sql_browser.set_text_size(16);
let widths = &[50, 100, 50];
self.sql_browser.set_column_widths(widths);
self.sql_browser.set_column_char('\t');
self.sql_browser.add("ID\tUSERNAME\tAGE");
}
fn sql_browser_add_data(&mut self, userdata: &Userdata) {
self.sql_browser.add_with_data(
&format!("{}\t{}\t{}", userdata.id, userdata.username, userdata.age),
userdata.clone(),
);
}
pub fn load_query_data(&mut self, userdata: &[Userdata]) {
self.sql_browser_prefare();
for user in userdata {
self.sql_browser_add_data(&user);
}
}
pub fn get_select_userdata(&self) -> Option<Userdata> {
let index = self.sql_browser.selected_items();
if index.len() > 0 {
let data: Option<Userdata> = unsafe { self.sql_browser.data(index[0]) };
return data;
}
None
}
pub fn set_page(&mut self, page: u64) {
self.op_page.set_value(page.to_string().as_str());
}
}
}
use anyhow::Result;
use store::Database;
use ui::{Message, UserInterface};
fn manage_error_in_alert<T>(result: Result<T>) {
if let Err(error) = result {
alert_default(error.to_string().as_str())
}
}
fn main() {
let app = App::default();
let (s, r) = channel::<Message>();
let mut app_ui = UserInterface::make_window();
let mut database = Database::create_connection().unwrap();
app_ui.emit(s.clone());
s.send(Message::QueryAll);
let mut limit = 10;
let mut page = 0;
while app.wait() {
if let Some(message) = r.recv() {
match message {
Message::Insert => {
let userdata = app_ui.get_edit_userdata();
manage_error_in_alert(
database.insert_userdata(&userdata.username, userdata.age),
);
s.send(Message::QueryAll)
}
Message::QueryAll => match database.get_all_userdata(limit, page * limit) {
Ok(users) => {
app_ui.load_query_data(&users);
app_ui.set_page(page as u64);
}
Err(error) => alert_default(error.to_string().as_str()),
},
Message::SelectUser => {
if let Some(userdata) = app_ui.get_select_userdata() {
app_ui.set_edit_userdata(&userdata)
}
}
Message::UpdateUser => {
if let Some(s_user) = app_ui.get_select_userdata() {
let j = app_ui.get_edit_userdata();
database
.update_userdata(s_user.id, &j.username, j.age)
.unwrap();
s.send(Message::QueryAll)
}
}
Message::DeleteUser => {
if let Some(s_user) = app_ui.get_select_userdata() {
database.delete_userdata(s_user.id).unwrap();
s.send(Message::QueryAll);
}
}
Message::SetRows => {
if let Some(rows) =
input_default("Set display rows:", limit.to_string().as_str())
{
if let Ok(rows) = rows.parse::<u64>() {
limit = rows
}
}
page = 0;
s.send(Message::QueryAll)
}
Message::Before => {
if page > 0 {
page = page - 1;
s.send(Message::QueryAll)
}
}
Message::Next => {
let count = database.get_count().unwrap();
if (page + 1) * limit < count {
page = page + 1;
s.send(Message::QueryAll)
}
}
Message::Refresh => {
page = 0;
s.send(Message::QueryAll);
}
Message::InsertMany => {
if let Some(count) = input_default("Insert count:", "1") {
if let Ok(count) = count.parse::<u64>() {
if count > 10000 {
alert_default("Count is over the limit 10000");
} else {
let userdata = app_ui.get_edit_userdata();
let _ = database.insert_many_userdata(
count,
&userdata.username,
userdata.age,
);
}
}
}
}
Message::SetPage => {
}
}
}
}
}
# data file for the Fltk User Interface Designer (fluid)
version 1.0400
header_name {.h}
code_name {.cxx}
class UserInterface {open
} {
Function {make_window()} {open
} {
Fl_Window {} {
label {Sqlite 3 Sample} open
xywh {364 207 786 409} type Double visible
} {
Fl_Input input_username {
label {Username: }
xywh {90 52 160 27}
}
Fl_Input input_age {
label {Age: }
xywh {90 88 160 27} type Float
}
Fl_Input input_id {
label {ID:}
xywh {90 18 160 27} deactivate
}
Fl_Button btn_insert_new {
label {Insert New}
xywh {25 129 100 31}
}
Fl_Button btn_many {
label {Insert Many}
comment BtnInsertMany
xywh {25 169 100 31}
}
Fl_Button btn_insert_to_select {
label {Update to select}
xywh {135 130 115 30}
}
Fl_Button btn_delete_select {
label {Delect select}
xywh {135 170 115 30}
}
Fl_Button btn_set_rows {
label {Set rows}
xywh {392 18 100 30}
}
Fl_Button btn_refresh {
label Refresh
xywh {277 18 100 30}
}
Fl_Button btn_go_before {
label Before
xywh {280 305 60 30}
}
Fl_Button btn_go_next {
label Next
xywh {350 305 55 30}
}
Fl_Output op_page {
label {Page: }
xywh {670 307 50 28}
}
Fl_Browser sql_browser {selected
xywh {277 63 445 225} type Multi
}
}
}
}
[dependencies]
anyhow = "1.0.75"
fl2rust-macro = "0.5.17"
fltk = { version = "1.4.19", features = ["fltk-bundled"] }
rusqlite = { version = "0.30.0", features = ["bundled"] }