Lập trình giao diện console: NCurses Menu

Manh

December 17, 2015

C++

1. App căn bản

(Note: các hướng dẫn này được viết cho Linux, NCurses có thể chạy trên môi trường Windows, tuy không có khác biệt về code, nhưng các bước cài đặt thư viện cũng như biên dịch sẽ khác, nếu có thời gian mình sẽ viết thêm bài hướng dẫn dành cho Windows – MinGW)

 

Chào các bạn. Đây là bài viết hướng dẫn đầu tiên về lập trình giao diện console với NCurses. Trước tiên chúng ta sẽ học cách viết một ứng dụng NCurses cơ bản và sau đó sẽ tạo một menu để thêm vào app.

 

Sau khi đã hiểu sơ qua và cài đặt NCurses như trong bài viết mở đầu:

Lập trình giao diện console

Bạn có thể sử dụng IDE/Editor yêu thích của mình để tạo một file/project mới để bắt đầu viết code.

 

Các bước để viết một app sử dụng NCurses (chỉ áp dụng cho C++, nếu ứng dụng của bạn là ứng dụng viết bằng C, bạn nên tham khảo tài liệu đi kèm Ncurses hơn là những bài viết này :v):

– 1 class bạn chọn để viết App phải được thừa kế từ class NCursesApplication.

– tạo ra một biến toàn cục là instance của class bạn viết ở trên để NCurses truy cập và sử dụng.

(thư viện C++ của NCurses sẽ tự thu hồi vùng nhớ mà bạn cấp phát)

 

Hàm main đã được viết sẵn nên bạn chỉ cần làm 2 công việc ở trên. Nếu bạn cần thay đổi hàm main này, bạn có thể sửa file cursesmain.cc trong folder c++ (Bạn nhớ build lại lib trước khi biên dịch app của mình).

 

#include <cursesapp.h>
#include <cstring> // header của C++ để dùng mảng ký tự như là một string (C-String)
 
class FirstNCursesApp : public NCursesApplication {
public:
	// hàm khởi tạo của bạn gọi hàm khởi tạo cha
	// tham số boolean ý nghĩa rằng chương trình của bạn
	// có sử dụng màu hay không
	FirstNCursesApp() : NCursesApplication(true) { }
 
	// implement hàm pure-virtual của class cha
	virtual int run();
 
protected:
	// override hàm xác định độ lớn của tiêu đề
	// tương ứng với số dòng trên console bạn dùng để hiển thị tiêu đề
	virtual int titlesize() const { return 1; }
 
	// override hàm thiết lập tiêu đề
	virtual void title();
};
 
void FirstNCursesApp::title() {
	const char* const titleText = "NCurses Menu Demo";
	const int len = std::strlen(titleText);
 
	// setup background (viết tắt: bkgd)
	// titleWindow là một member có sẵn từ class cha
	// hàm addstr nhận tham số đầu tiên là hàng
	// chúng ta có 1 dòng (titlesize) nên sẽ dùng hàng đầu tiên của console
	// tham số tiếp theo là cột - căn giữa title
	// tham số cuối cùng là string lưu title
	// hàm screen_titles (class cha) trả về thiết lập màu của màn hình tiêu đề
	// noutrefresh cập nhật màn hình như những gì chúng ta cài đặt
	titleWindow->bkgd(screen_titles());
	titleWindow->addstr(0, (titleWindow->cols() - len) / 2, titleText);
	titleWindow->noutrefresh();
}

OK, tiếp theo là code hàm run(). Nhưng trước tiên hãy thử biên dịch xem app của chúng ta hiển thị như thế nào nhé. Vậy nên hàm run() mình sẽ để một vòng lặp vô tận, nếu không app của chúng ta sẽ thoát ngay khi NCurses gọi run().

 

Bước tiếp theo đó là tạo ra app instance:
 

int FirstNCursesApp::run() {
	while (true) {
 
	}
}
 
static FirstNCursesApp* app = new FirstNCursesApp();

Để biên dịch code này bạn cần những cài đặt sau:

– Include folder: folder include của NCurses.

– Library search folder: folder lib của NCurses.

– Linker settings: -lncurses++ và -lncurses

 

Mỗi IDE sẽ có cài đặt khác nhau nên mình không tiện hướng dẫn hết ở đây, thay vào đó có một ví dụ về câu lệnh biên dịch và makefile:

g++ -o menu menu.cpp -I/some_path/ncurses-6.0/include/ -L /some_path/ncurses-6.0/lib/ -lncurses++ -lncurses

Makefile:

menu: menu.cpp (xuống dòng và 1 ký tự Tab)
         g++ -o menu menu.cpp -I/some_path/ncurses-6.0/include/ -L /some_path/ncurses-6.0/lib/ -lncurses++ -lncurses

Bạn có thể gõ lệnh biên dịch lên Terminal hoặc viết makefile với nội dung như trên, sau đó gọi lệnh make để biên dịch.

 

2. Soft Label Key

Sau khi biên dịch xong bạn gọi chương trình từ Terminal. Chương trình đã chạy, nhưng nó không hiển thị gì trên màn hình cả. Vấn đề tiếp theo đó là Soft label key (SLK). Soft label key là những nút điều khiển, bạn có thể đọc thêm ở đây: https://en.wikipedia.org/wiki/Screen-labeled_function_keys

Chúng ta chưa thiết lập Layout cho SLK, vì sau NCurses yêu cầu điều này thì mình cũng … không biết =)) Có thể là thông thường các màn hình đều có những nút điều khiển.

Bạn thêm đoạn code sau vào phần khai báo protected của class:

 

// trả về bố cục của các nút điều khiển
virtual Soft_Label_Key_Set::Label_Layout useSLKs() const {
	return Soft_Label_Key_Set::PC_Style_With_Index;
}

Biên dịch lại code và gọi chương trình, bạn sẽ thấy hiển thị như sau
(Ứng dụng không có màu và có màu)

 

 

 

Các loại SLK layout: (cursslk.h)

 

typedef enum {
    None                = -1, // mặc định khi bạn không override hàm useSLKs()
    Three_Two_Three     = 0,
    Four_Four           = 1,
    PC_Style            = 2, // layout như hình trên nhưng không có tên phím
    PC_Style_With_Index = 3 // kiểu mình dùng ở ảnh trên
  } Label_Layout;

Kiểu Three_Two_Three và Four_Four tương ứng với bố trí nút điều khiển:
ví dụ: 4 nút – 4 nút

 

 

3. Menu Demo

App cơ bản của chúng ta đã xong, giờ đến phần tạo Menu cho app.

Để tạo ra một menu, bạn cần thừa kế class NcursesMenu

 

#include <cursesm.h> // curses menu
 
class SimpleMenu : public NCursesMenu {
private:
	NCursesPanel* panel;
	NCursesMenuItem** items;
 
	static const int itemCount = 6;
 
public:
	// hàm khởi tạo cha nhận 4 tham số
	// tham số đầu tiên là số dòng menu, ít nhất là = số entry +2
	// tham số thứ 2 là số cột, là độ dài nội dung text dài nhất +3
	// "Item 6th" có 8 ký tự, thêm 3 = 11
	// quy tắc này là cách trang trí menu của NCurses
	// 2 tham số tiếp theo là số hàng và số cột mà menu bắt đầu được hiển thị
	SimpleMenu() : NCursesMenu(itemCount+2, 11, (lines()-10)/2, (cols()-10)/2),
				   panel(NULL), items(NULL)
	{
		items = new NCursesMenuItem*[itemCount+1];
 
		items[0] = new NCursesMenuItem("Item 1");
		items[1] = new NCursesMenuItem("Item 2");
		items[2] = new NCursesMenuItem("Item 3");
		items[3] = new NCursesMenuItem("Item 4");
		items[4] = new NCursesMenuItem("Item 5");
		items[5] = new NCursesMenuItem("Item 6th");
 
		// tạo ra một item mặc định để đánh dấu kết thúc menu
		items[6] = new NCursesMenuItem();
 
		// tham số boolean thứ nhất: có sử dụng frame (đóng khung) hay ko
		// tham số thứ 2: tự động xóa item
		InitMenu(items, true, true);
 
		// tạo ra một panel
		panel = new NCursesPanel();
 
		// trang trí menu theo kiểu boldframe
		// 2 tham số là tiêu đề trên và tiêu đề dưới
		// ngoài ra còn có các kiểu trang trí frame và label
		boldframe("Menu", "Demo");
 
		panel->show();
	}
};
 
int FirstNCursesApp::run() {
	SimpleMenu m;
	m();
	return 0;
}

Trong makefile hoặc câu lệnh biên dịch bạn link thêm thư viện menu và panel.
Kết quả chương trình của chúng ta sẽ hiển thị như thế này:

 

 

Trong ví dụ này các item của chúng ta chỉ hiển thị nhưng chưa có hoạt động nào ngoài lựa chọn giữa các item, ở bài sau ta sẽ thêm các action vào menu trong NCurses.

Comments

comments

Related Posts

Hãy dừng việc sử dụng fflush(stdin) để xóa dòng nhập

Manh

March 16, 2016

C++

1. Hiện thực – Lập trình C được giảng dạy ở Việt Nam một cách rất “hỗn tạp” – Vì chỉ được coi là một môn học “nhập môn” nên giảng dạy khá “cẩu thả” – C không xứng đáng là để dạy nhập môn, hãy dạy nó chuyên sâu: Vì sao lại khiến một […]

Read More

Modern C++: Functors

Manh

March 11, 2016

C++

1. Functor là gì? Nếu như là một fan trung thành của C++ STL (Standard Template Library), bạn sẽ bắt gặp functor rất nhiều. Ví dụ trong đoạn code sau: #include <iostream> #include <vector> #include <algorithm>   void increment(int& i) { ++i; std::cout << i << ' '; }   int main() { std::vector<int> v […]

Read More