Click here to Skip to main content
15,881,172 members
Articles / Desktop Programming / Win32
Tip/Trick

Calculator Interface Design In Rust Language

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
15 Dec 2016CPOL2 min read 19.1K   430   2   2
This tip describes how we can design application interface using Win32 API in Rust Programming language.

Introduction

Here in this tip, we will design a simple calculator's graphical interface using pure Win32 API functions in Rust programming language.

In Code Project, I wrote an article on Rust called 'Win32 GUI Programming In Rust Language ' where I showed how we can create a simple window using pure Win32 API. Although there were some mistakes, some Code Project members helped me through comments to correct those mistakes. So I want to say many thanks to them.

Using the Code

Designing graphical user interface using pure Win32 API is a little hard. But at the end, it helps a lot to understand the basic concept. So this tip will help you to understand the use of basic graphical elements such as buttons, labels, etc. to design an application's user interface. And also, we will see how we can handle a button's click event and change label control's background color.

First, we have to extern our necessary crates (these crates are available on the internet):

C++
extern crate kernel32;
extern crate user32;
extern crate gdi32;
extern crate winapi;
extern crate libc;

use winapi::windef::HWND;
use winapi::windef::HMENU;
use winapi::windef::HBRUSH;

use winapi::minwindef::HINSTANCE;
use winapi::minwindef::UINT;
use winapi::minwindef::DWORD;
use winapi::minwindef::WPARAM;
use winapi::minwindef::LPARAM;
use winapi::minwindef::LRESULT;

use winapi::winnt::LPCWSTR;

use winapi::winuser::WS_VISIBLE;
use winapi::winuser::WS_CHILD;
use winapi::winuser::WS_EX_CLIENTEDGE;

use winapi::winuser::WNDCLASSW;

use std::os::windows::ffi::OsStrExt;
use std::ffi::OsStr;

static mut op_e1 :i32 = 0;
static mut op_e2 :i32 = 0;

static mut op_operator :i32 = 0;

static mut hwnd_display :HWND = 0 as HWND;

We need a Rust's string to utf16 string converter function to work with Win32 API functions:

C++
fn to_wstring(str : &str) -> Vec<u16> {
    let v : Vec<u16> =
            OsStr::new(str).encode_wide().chain(Some(0).into_iter()).collect();
    v
}

The following functions are button's click event handlers. These functions are being called from the WM_COMMAND message in order to handle our calculator's buttons click event. Though, currenly these functions don't do anything important:

C++
unsafe fn on_clear_click() {
    op_e1 = 0;
    op_e2 = 0;
    op_operator = 0; 

    user32::SetWindowTextW(hwnd_display, to_wstring("0").as_ptr());
}

unsafe fn on_numbers_click(id :u32) {
    // Here our numbers_click event codes
    user32::SetWindowTextW(hwnd_display, to_wstring("Not Implemented!").as_ptr());
}

unsafe fn on_operators_click() {
    op_operator = 1;
    user32::SetWindowTextW(hwnd_display, to_wstring("0").as_ptr());
}

unsafe fn on_equal_click() {
    let result = op_e1 + op_e2;
    let res_str: String = result.to_string();
    let ws = to_wstring(&res_str[..]).as_ptr();
    user32::SetWindowTextW(hwnd_display, ws);
}

This is our window's message handler function. Here, we handle WM_DESTROY, WM_CTLCOLORSTATIC and WM_COMMAND messages:

C++
pub unsafe extern "system" fn window_proc
(h_wnd :HWND, msg :UINT, w_param :WPARAM, l_param :LPARAM) -> LRESULT
{
    if msg == winapi::winuser::WM_DESTROY {
        user32::PostQuitMessage(0);
    }

We handle the WM_CTLCOLORSTATIC in order to change the display label control background color to white:

C++
else if msg == winapi::winuser::WM_CTLCOLORSTATIC
{
     if hwnd_display == (l_param as HWND)
     {
         let h_brush = gdi32::CreateSolidBrush((255 | (255 << 8)) | (255 | (255 << 16)));
         return h_brush as LRESULT;
     }
}

And we also handle WM_COMMAND to detect buttons click event. Though, it doesn't do any important job here but surely helps you to understand the button click event handling.

C++
else if msg == winapi::winuser::WM_COMMAND
{

I know that this isn't the good way, but I found it easier:

C++
        if w_param > 100 && w_param < 111 // Range of number buttons id
        {
            on_numbers_click(w_param as u32);
        }
        else if w_param > 110 && w_param < 117 // Range of operator buttons id
        {
            if w_param == 115 {
                on_clear_click();
            }
            else {
                on_operators_click();
            }
        }
        else if w_param == 117 // Id of '=' operator button
        {
            on_equal_click();
        }
    }
    return user32::DefWindowProcW(h_wnd, msg, w_param, l_param);
}

Since we are going to create a calculator application's interface, we will need a lot of buttons and a label control. To create our calculator's button controls and other graphical elements, we will use the CreateWindowExW function. As calculator's display control, we will use a static/label control. We will create all the controls inside a function called init_interface:

C++
unsafe fn init_interface(h_wnd :HWND)
{
    hwnd_display = user32::CreateWindowExW(WS_EX_CLIENTEDGE, to_wstring("static").as_ptr(), 
                     to_wstring("0"), WS_CHILD | WS_VISIBLE | 2 /*SS_RIGHT*/, 
                       46, 20, 256, 60, h_wnd, 340 as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    let mut i = 1;
    let mut x = 46;
    let mut y = 210;
    let mut btn_num_id = 101;

Now we create all the number buttons using a loop statament. It will make thing a lot more easier:

C++
loop
{
    if i >= 10 {
        break;
    }

    let txt = format!("{}", i);

    user32::CreateWindowExA(0, "button".as_ptr() as *mut _,
                txt.as_ptr() as *mut _, WS_CHILD | WS_VISIBLE,
                   x, y, 40, 32, h_wnd, btn_num_id as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    x = x + 54;

    if i % 3 == 0 {
        x = 46;
        y = y - 54;
    }

    btn_num_id = btn_num_id + 1;

    i = i + 1;
}

Let's ceate all the arithmetical calculation buttons such as ‘+’, ‘-’ etc.

C++
    x = 46;
    y = 264;

    user32::CreateWindowExA(0, "Button".as_ptr() as *mut _, 
                    "0".as_ptr() as *mut _, WS_CHILD | WS_VISIBLE, 
                       x, y, 148, 32, h_wnd, btn_num_id as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    btn_num_id = btn_num_id + 1;

    x = 208;
    y = 102;

    user32::CreateWindowExA(0, "Button".as_ptr() as *mut _, 
                    "+".as_ptr() as *mut _, WS_CHILD | WS_VISIBLE, 
                       x, y, 40, 32, h_wnd, btn_num_id as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    y = y + 54;
    btn_num_id = btn_num_id + 1;

    user32::CreateWindowExA(0, "Button".as_ptr() as *mut _, 
                    "-".as_ptr() as *mut _, WS_CHILD | WS_VISIBLE, 
                       x, y, 40, 32, h_wnd, btn_num_id as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    y = y + 54;
    btn_num_id = btn_num_id + 1;

    user32::CreateWindowExA(0, "Button".as_ptr() as *mut _, 
                    "x".as_ptr() as *mut _, WS_CHILD | WS_VISIBLE, 
                       x, y, 40, 32, h_wnd, btn_num_id as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    y = y + 54;
    btn_num_id = btn_num_id + 1;

    user32::CreateWindowExA(0, "Button".as_ptr() as *mut _, 
                    "/".as_ptr() as *mut _, WS_CHILD | WS_VISIBLE, 
                       x, y, 40, 32, h_wnd, btn_num_id as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    btn_num_id = btn_num_id + 1;

    x = 262;
    y = 102;

    user32::CreateWindowExA(0, "Button".as_ptr() as *mut _, 
                    "C".as_ptr() as *mut _, WS_CHILD | WS_VISIBLE, 
                       x, y, 40, 32, h_wnd, btn_num_id as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    y = y + 54;
    btn_num_id = btn_num_id + 1;

    user32::CreateWindowExA(0, "Button".as_ptr() as *mut _, 
                    "%".as_ptr() as *mut _, WS_CHILD | WS_VISIBLE, 
                       x, y, 40, 32, h_wnd, btn_num_id as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    y = y + 54;
    btn_num_id = btn_num_id + 1;

    user32::CreateWindowExA(0, "Button".as_ptr() as *mut _, 
                    "=".as_ptr() as *mut _, WS_CHILD | WS_VISIBLE, 
                       x, y, 40, 86, h_wnd, btn_num_id as HMENU, 0 as HINSTANCE, std::ptr::null_mut());
}

This is the entry point function of our program. Here, we register and create our main window. Then, we call the init_interface function to create our calculator's interface (static and buttons).

C++
fn main()
{
  unsafe
  {
    let class_name = to_wstring("my_window");

    let wnd = WNDCLASSW {

        style: 0,
        lpfnWndProc: Some(window_proc), 
        cbClsExtra: 0,
        cbWndExtra: 0,
        hInstance: 0 as HINSTANCE,
        hIcon: user32::LoadIconW(0 as HINSTANCE, winapi::winuser::IDI_APPLICATION),
        hCursor: user32::LoadCursorW(0 as HINSTANCE, winapi::winuser::IDI_APPLICATION),
        hbrBackground: 16 as HBRUSH,
        lpszMenuName: 0 as LPCWSTR,
        lpszClassName: class_name.as_ptr(),
    };

    user32::RegisterClassW(&wnd);

    let h_wnd_main = user32::CreateWindowExW(0, class_name.as_ptr(), 
                    to_wstring("Simple Calculator Interface In Rust").as_ptr(), 
                     winapi::winuser::WS_OVERLAPPED | winapi::winuser::WS_CAPTION | 
                      winapi::winuser::WS_SYSMENU | winapi::winuser::WS_MINIMIZEBOX | WS_VISIBLE, 
                       0, 0, 366, 400, 0 as HWND, 0 as HMENU, 0 as HINSTANCE, std::ptr::null_mut());

    init_interface(h_wnd_main); 

    user32::ShowWindow(h_wnd_main, winapi::SW_SHOW);

    let mut msg = winapi::winuser::MSG {
        hwnd : 0 as HWND,
        message : 0 as UINT,
        wParam : 0 as WPARAM,
        lParam : 0 as LPARAM,
        time : 0 as DWORD,
        pt : winapi::windef::POINT { x: 0, y: 0, }, 
    };

    // Finally we run the standard application loop -
    loop {
        let pm = user32::GetMessageW(&mut msg, 0 as HWND, 0, 0);
        if pm == 0 {
            break;
        }
        if msg.message == winapi::winuser::WM_QUIT {
            break;
        }
        user32::TranslateMessage(&mut msg);
        user32::DispatchMessageW(&mut msg);
    }
  }
}

Our Cargo.toml file's contents will look like this (name, version and authors is your choice):

C++
[package]

name = "simple_calculator_interface"
version = "0.0.1"
authors = [ "Your name <you@example.com>" ]

[dependencies]
libc = "0.1.10"
winapi = "0.2.4"
user32-sys = "0.1.2"
kernel32-sys = "0.1.4"
gdi32-sys = "0.1.1"

Compile the program using the following commands (many thanks to the Code Project member who helped me remove the black console window via command line arguments):

C++
cargo rustc -- -C link-args=-mwindows

Now run your ‘simple_calculator_interface.exe’.

Conclusion

I hope this tip has helped programmers to understand the basic GUI programming in Rust language. Although I know that using the Win API directly isn't the best idea. However, it does help to understand the basic idea.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Bangladesh Bangladesh
Hi, I'm Shah Farhad Reza. I'm a desktop and web software developer.

Recently I've developed an web based ERP (Enterprise Resource Planning) Software for a manufacturing company. The software is in use and working effectively fulfilling its goal (Alhamdulillah) - [February 10, 2023]

The areas of my expertise are the followings:

- OS Kernel developing.
- Programming language's compiler design and implement.
- Expert in C, C++ and Visual Basic and have basic knowledge on C#, D, Java.
- A few times used the Microsoft's new language F#.
- SQL Database programming.
- I've basic knowledge on lowest level programming language like assembly.
- Learning Mozilla’s Rust & Google’s GO programming language for software development.
- Code optimization for performance.
- Multi-threaded programming in C/C++ and Java.
- Know various advanced computer algorithm and have used them to develop graphics and simulation programs. Also working with Linear Algebra and keen to learn Quadratic Algebra in future.
- Graphics and Game programming (Both 2D and 3D).

Currently, I'm doing research on programming language and its compiler development. I've made various kind of software and now I want to share my experiences with people.


Comments and Discussions

 
QuestionNot using the wide character versions? Pin
Jeremy Falcon5-Jan-23 12:54
professionalJeremy Falcon5-Jan-23 12:54 
QuestionThis is a nice idea for getting started Pin
Member 79573371-Nov-16 6:36
Member 79573371-Nov-16 6:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.