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

Manh

December 22, 2015

C++

Trong bài hôm nay chúng ta sẽ add thêm hành động vào Menu, khi được lựa chọn, chương trình sẽ tạo ra một form nhập dữ liệu.
 

1. Color Attributes

Chúng ta đã biết rằng một ứng dụng NCurses có thể sử dụng màu hoặc không. Vậy có thể thay đổi các màu hiển thị không? Tất nhiên là được.
Chúng ta cùng tham khảo file cursesapp.cc trong folder c++:
 

Root_Window->setcolor(1);
Root_Window->setpalette(COLOR_YELLOW,COLOR_BLUE);
Root_Window->setcolor(2);
Root_Window->setpalette(COLOR_CYAN,COLOR_BLUE);
Root_Window->setcolor(3);
Root_Window->setpalette(COLOR_BLACK,COLOR_BLUE);
Root_Window->setcolor(4);
Root_Window->setpalette(COLOR_BLACK,COLOR_CYAN);
Root_Window->setcolor(5);
Root_Window->setpalette(COLOR_BLUE,COLOR_YELLOW);
Root_Window->setcolor(6);
Root_Window->setpalette(COLOR_BLACK,COLOR_GREEN);

Để cài đặt thuộc tính Color, bạn sử dụng một Color Pair trong NCurses, tức một thuộc tính màu gồm 2 màu đi với nhau đó là foreground color và background color.
 
Chương trình của chúng ta có 6 thuộc tính màu (6 color pair). Để biết được 6 color pair này được dùng như thế nào thì chúng ta có thể đọc ở file cursesapp.h:
 

// Attributes to use for menu and forms foregrounds
virtual chtype foregrounds() const {
  return b_Colors ? static_cast<chtype>(COLOR_PAIR(1)) : A_BOLD;
}
 
// Attributes to use for menu and forms backgrounds
virtual chtype backgrounds() const {
  return b_Colors ? static_cast<chtype>(COLOR_PAIR(2)) : A_NORMAL;
}
 
// Attributes to use for inactive (menu) elements
virtual chtype inactives() const {
  return b_Colors ? static_cast<chtype>(COLOR_PAIR(3)|A_DIM) : A_DIM;
}
 
// Attributes to use for (form) labels and SLKs
virtual chtype labels() const {
  return b_Colors ? static_cast<chtype>(COLOR_PAIR(4)) : A_NORMAL;
}
 
// Attributes to use for form backgrounds
virtual chtype dialog_backgrounds() const {
  return b_Colors ? static_cast<chtype>(COLOR_PAIR(4)) : A_NORMAL;
}
 
// Attributes to use as default for (form) window backgrounds
virtual chtype window_backgrounds() const {
  return b_Colors ? static_cast<chtype>(COLOR_PAIR(5)) : A_NORMAL;
}
 
// Attributes to use for the title window
virtual chtype screen_titles() const {
  return b_Colors ? static_cast<chtype>(COLOR_PAIR(6)) : A_BOLD;
}

2. Menu Action

Để thực thi một hành động khi một Menu Item được người dùng chọn, đầu tiên chúng ta sẽ tạo ra một class thừa kế NcursesMenuItem và implement hàm action().
 
Ở đây ta sẽ thử một action đơn giản: Quit
 

class QuitItem : public NCursesMenuItem {
public:
	QuitItem() : NCursesMenuItem("Quit") { }
 
	// giá trị trả về của hàm action:
	// trả về true để thoát menu
	// trả về false sẽ mở lại menu
	virtual bool action() {
		return true;
	}
};

Và trong hàm khởi tạo Menu chúng ta sửa Item cuối cùng:
 

items[5] = new QuitItem();

Bạn hãy biên dịch và chạy thử, khi lựa chọn Quit, menu duy nhất của chương trình được đóng, nên chương trình của chúng ta sẽ kết thúc.
 

3. Form Basics

Trong phần này, chúng ta sẽ tạo ra một form nhập dữ liệu, và các dữ liệu sẽ dược validate khi nhập, ví dụ như bạn sẽ đặt giới hạn cho một số nguyên, NCurses sẽ kiểm tra dữ liệu bạn vừa nhập vào có nằm trong giới hạn không thì mới cho phép nhập sang Field khác, hoặc kiểu Enumeration sẽ chỉ cho phép người dùng nhập một trong các giá trị bạn đã định sẵn.   Đầu tiên chúng ta sẽ thử tạo một Form cơ bản: Tạo ra một class NCursesFormLabel làm tiêu đề cho field, thực chất đây là một NcursesFormField bị tắt đi các thuộc tính Edit và Active.
 

#include <cursesf.h> // curses form
 
class NCursesFormLabel : public NCursesFormField {
public:
	NCursesFormLabel(const char* title, int row, int col) :
	  // 2 thuộc tính đầu là size, số dòng và số cột mà title này chiếm
	  // 2 tham số sau là vị trí
	  NCursesFormField(1, std::strlen(title), row, col) {
		set_value(title);
		// tắt tính năng edit và thuộc tính active
		options_off(O_EDIT | O_ACTIVE);
	}
};

Chúng ta bắt đầu tạo một form.
 

class MyForm : public NCursesForm {
private:
	NCursesFormField** fields;
 
public:
	MyForm() : NCursesForm(5, 50, (lines()-15)/2, (cols()-50)/2), fields(NULL) {
		fields = new NCursesFormField*[2];
 
		fields[0] = new NCursesFormLabel("NCurses Form Demo", 0, 15);
 
		fields[1] = new NCursesFormField();
 
		InitForm(fields, true, true);
		boldframe();
	}
};

Tiếp theo là FormAction.
 

class FormAction : public NCursesMenuItem {
public:
	FormAction(const char* title) : NCursesMenuItem(title) { }
 
	virtual bool action() {
		MyForm form;
		form();
		return false;
	}
};

Và thay đổi menu item của chúng ta.
 

items[0] = new FormAction("Form Demo");
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 QuitItem();
 
items[6] = new NcursesMenuItem();

Khi khởi chạy, chương trình hiển thị như sau:
 

 

 
Nếu bấm Ctrl X để thoát khỏi Form, chương trình sẽ trở lại menu chính.
 

4. Adding fields

Ở phần trên form của chúng ta chỉ gồm có tiêu đề, giờ ta sẽ add thêm field để cho người dùng nhập.
 
Bạn tạo ra một field.
 

MyForm() : NCursesForm(7, 50, (lines()-15)/2, (cols()-50)/2),
                    fields(NULL), intField(NULL) {
		fields = new NCursesFormField*[4];
 
		fields[0] = new NCursesFormLabel("NCurses Form Demo", 0, 15);
 
		// cùng nằm trên dòng 2, fields[2] lùi lại phia sau
		fields[1] = new NCursesFormLabel("Input a number", 2, 3);
		fields[2] = new NCursesFormField(1, 10, 2, 30);
 
		fields[3] = new NCursesFormField();
 
		InitForm(fields, true, true);
		boldframe();
}


 
Giờ chúng ta sẽ thêm type vào cho field để giới hạn nội dung nhập.
Mình sẽ tạo thêm 1 field không có limit để dễ so sánh.
 

class MyForm : public NCursesForm {
private:
	NCursesFormField** fields;
	Integer_Field* intField;
 
public:
	MyForm() : NCursesForm(10, 50, (lines()-15)/2, (cols()-50)/2),
			   fields(NULL), intField(NULL) {
		fields = new NCursesFormField*[6];
 
		fields[0] = new NCursesFormLabel("NCurses Form Demo", 0, 15);
 
		// cùng nằm trên dòng 2, fields[2] lùi lại phia sau
		fields[1] = new NCursesFormLabel("Input a number between 1-10", 2, 3);
		fields[2] = new NCursesFormField(1, 10, 2, 32);
 
		fields[3] = new NCursesFormLabel("Input a number", 4, 3);
		fields[4] = new NCursesFormField(1, 10, 4, 32);
 
		fields[5] = new NCursesFormField();
 
		intField = new Integer_Field(0, 1, 10);
		fields[2]->set_fieldtype(*intField);
 
		InitForm(fields, true, true);
		boldframe();
	}
 
	~MyForm() {
		delete intField;
	}
};

Nếu bạn không nhập đúng ở Field đầu tiên, bạn sẽ không thể bấm Enter để chuyển sang Field tiếp theo, hãy thử nhập vài lần để chắc chắn Field Type đã hoạt động.
(Xóa ký tự bằng Ctrl + H, có thể code để sửa thành Backspace cho tiện – vấn đề này sẽ ở một bài khác)
 

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