John554
John554

Reputation: 303

gtk-rs getting user input from button press

I am have a gtk-rs application where a Gtk::Entry is used to get user input when the user hits a submit button. Everything compiles and runs however I can not get the user input outside of the method that handles the button press.

I can check that the input is collected properly from inside the button click handler however getting the string outside of the method is not working at the moment.

I have found similar questions and spent a lot of time googling this issue but so far I have not found a solution to this problem. I actually haven't even found anyone asking the same question which makes me think either I am asking the wrong question or I am trying to do this in a very bad way.

So the code is here. I want to get the user_input variable outside of the button.connect_clicked method.

Code:

use gtk::prelude::*;
use gtk::{Button, Orientation};
use std::cell::RefCell;
use std::rc::Rc;

pub fn login_view() -> (gtk::Box, String) {
    let button = Button::builder()
        .label("Submit")
        .margin_top(6)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();

    let input = gtk::Entry::builder()
        .placeholder_text("input")
        .margin_top(12)
        .margin_bottom(6)
        .margin_start(12)
        .margin_end(12)
        .build();

    let login_box = gtk::Box::builder()
        .orientation(Orientation::Vertical)
        .build();
    login_box.append(&input);
    login_box.append(&button);

    let mut user_input = Rc::new(RefCell::new(String::new()));
    let user_clone = user_input.clone();

    button.connect_clicked(move |_| {
        user_input.replace((format!("{}", input.text())));
        println!("user input = {}", user_input.borrow().to_string());
        input.set_text("");
    });

    println!("user_clone: {}", user_clone.borrow().to_string());
    return (login_box, user_clone.borrow().to_string());
}

output:

user_clone: 
user input = dsfdsfds // this line repeats as many times as you hit the button.

This could potentially be an async problem where println!("user_clone: {}", user_clone.borrow().to_string()); is called before the user input is collected and then never runs again but if that is the case I also don't know how to handle that situation because the connect_clicked method doesn't really complete, it just runs every time the button is clicked.

The end goal is to get the input value and store it in a struct. So anyway I can get this input value into a struct that has already been declared would be ideal.

Upvotes: 0

Views: 309

Answers (1)

cafce25
cafce25

Reputation: 27249

You already started to go down the right path, you used a Rc<RefCell<String>> to share ownership, and retain mutability. So instead of returning just the contents at the end of the function you could return the whole user_clone, but you'll quickly realize that graphical applications are usually multithreaded for a reason and thus the Rc<...> would need to be Send or Sync or both. So I suggest to instead switch to Arc<Mutex<String>> (or replace Mutex for RwLock)

use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Button, Orientation};
use std::sync::{Arc, Mutex};

pub fn login_view() -> (gtk::Box, Arc<Mutex<String>>) {
    // ...

    let user_input = Arc::new(Mutex::new(String::new()));

    button.connect_clicked({
        let user_input = Arc::clone(&user_input);
        move |_| {
            let mut user_input = user_input.lock().unwrap();
            *user_input = input.to_string();
            println!("user input = {}", user_input);
            input.set_text("");
        }
    });

    println!("user_clone: {}", user_input.lock().unwrap().to_string());
    return (login_box, user_input);
}

fn run_ui(app: &Application) {
    let (view, string) = login_view();
    let window = ApplicationWindow::builder()
        .application(app)
        .title("Hello Gtk")
        .child(&view)
        .build();

    // example use from another thread
    std::thread::spawn(move || loop {
        std::thread::sleep_ms(1000);
        println!("user_input: {}", string.lock().unwrap());
    });
    window.present();
}

You can then put that returned Arc<Mutex<String>> in the struct where you wanted the original String and whenever you read from it you will have the latest version.

Upvotes: 1

Related Questions