Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Calculator Interface Design In Rust Language

5.00/5 (4 votes)
15 Dec 2016CPOL2 min read 20.2K   447  
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)