Creating a notepad
http://www.dreamincode.net/forums/index.php?act=Attach&type=post&id=8602
In this tutorial, we're going to take quite a big step~ but one that I think is manageable. It's possible to make a notepad application in just 74 lines, with plenty of line spacing. We're going to look at:
- Event handling
- Creating a menu bar
- The wxMenuBar class
- The wxMenu class
- The wxMenuBar class
- The wxTextCtrl class
Before I start, I'd like to say that I know it's best to split classes across seperate files. But I'm putting all of this in one file so that it's more concise, and easier to read.
So, let's get started on our program!
First off, let's include the main wx header:
#include <wx/wx.h>
Designing the class
Next, we need to start writing our Notepad class~ It's going to inherit a wxFrame. I've commented it plenty so that you can see what's going on:
class Notepad : public wxFrame {
public:
Notepad(); // our default constructor
private:
wxMenuBar *menu; // the menu bar
wxMenu *file; // the file menu (keep this simple
wxTextCtrl *text; // the main text area
void OnSave(wxCommandEvent &event); // the click event for "save"
void OnOpen(wxCommandEvent &event); // the click event for "open"
void OnExit(wxCommandEvent &event); // the click event for "close"
// declare some ID values for our menu items
// these can be anything, but I personally tend to start from 1,000
enum MenuControls {
idSave = 1000,
idOpen, idExit
};
// this bit's important~
// it's a macro, which is basically saying we want to define some events
// (events such as clicking menu items in the menu)
DECLARE_EVENT_TABLE()
};
Clear? It should be. Basically, we've only got one thing which is public, and that's the constructor for our notepad. Next, we have some private objects, the menu bar, the menu, and the text area. This should be pretty simple at the minute, there's no new syntax up until that last line... what's this? DECLARE_EVENT_TABLE()
A macro. By doing this, we're basically saying that our application should have some event handlers, which we define in something called an event table in a moment.
Defining an event table
This bit's new. It's a great way to assign event handlers when you get used to it. An event table is basically where we assign functions to specific events using those IDs we created earlier. Basically, by passing a number and a method name, we assign that method name a value, or a key. This key is then assigned to items later (in this case, menu items) as the event handler. Don't worry if this doesn't make sense right now, it will become clear later on.
BEGIN_EVENT_TABLE(Notepad, wxFrame) // begin the event table for our Notepad class, which inherits wxFrame
EVT_MENU(idSave, Notepad::OnSave) // set an event for our idSave, and the function OnSave
EVT_MENU(idOpen, Notepad::OnOpen) // set an event for open
EVT_MENU(idExit, Notepad::OnExit) // set an event for exit
END_EVENT_TABLE() // end the event table
The constructor
As with any OOP program that you've written in the past, we need to set up our object in the constructor. In the constructor for our notepad application, we need to add some menu items to the menu, add the menu to the menu bar, and add the text area to the main frame. Sounds like a lot, but it's actually quite simple.
Our constructor is actually going to do a default operation to the wxFrame constructor. We do this like so:
Notepad::Notepad() : wxFrame(NULL, wxID_ANY, wxT("wxNotepad"), wxDefaultPosition, wxSize(650,500)) {
I know, I know~ it's long. But once this class is done, we don't have to do anything to it, other than show it.Next, we need to instantiate our menu and our menu bar:
this->menu = new wxMenuBar(); // instantiate our menu bar
this->file = new wxMenu(); // instantiate our file menu for our menu bar
That was simple!
Now it's time to Append() an item to our menu. Here, we're going to pass two parameters:
- The ID number of our event handler~ this is where that enum comes in handy. Remember how we did this in the event table:
EVT_MENU(idSave, Notepad::OnSave) // set an event for our idSave, and the function OnSave
So, we're passing that idSave index to Append() as if to say "we're going to associate Notepad::OnSave as the event handler. - A wxString, formatted in a specific way: The text (with an ampersand (&) to signify which key shortcut to use, a tab (\t) and then the keyboard shortcut, which is assigned automatically according to the string we pass now. Great, it's done automatically!
this->file->Append(idSave, wxT("&Save File\tCtrl-S")); // add a save option to the menu bar
**Remember that we use wxT("") to format a string as a wxString.Next, we're going to add an "Open" option, using a similar process to the last line:
this->file->Append(idOpen, wxT("&Open File\tCtrl-O"));
And now it's time to add a separator, before the exit option. We can do this very simply, by calling one function (which doesn't need parameters)
this->file->AppendSeparator();
Lastly, let's add an exit option:
this->file->Append(idExit, wxT("E&xit\tCtrl-F4"));
Next, we're going to append that file menu to our menu bar. This is also as simple as calling the Append function. It takes two parameters, the actual menu to add, and the name of the menu (what to display in the menu bar)
this->menu->Append(file, wxT("&File"));
The last thing we need to do for the menu bar is actually apply it to the application, which can be done very simply:
this->SetMenuBar(menu); // set our menu bar to be visible on the application
Now it's time for the last line of our constructor! Wahey!
Basically, all we need to do now is add the text area itself, which is quite simple~ the wxTextCtrl constructor takes quite a lot of arguments, each of which I'm going to explain now:
- The first parameter is the parent, so we pass this (this is the owner)
- wxID_ANY ~ the ID of the control. Since it doesn't matter, we can just pass wxID_ANY
- The third, wxt("") is the text to put in the text area straight away. We pass an empty string here
- wxDefaultPosition: the position of the control
- wxDefaultSize: the size of the control
- And lastly, we have the style~ we want two styles here, wxTE_MULTILINE and wxTE_PROCESS_ENTER. This way, we enable multi-line editing, and allow the user to press enter to move on to a new line. (Otherwise, the enter keypress is processed internally)
this->text = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER | wxTE_MULTILINE);
} // end of the constructor!
Defining those events~ what to do, what to do?
First off, the save event. We need to create a save dialog, show it, and check if the user clicked OK. If they did, then we want to save the contents to a file (which is very simple)
So, let's open up that function!
void Notepad::OnSave(wxCommandEvent &event) {
First off, we need to create a FileDialog. If you're coming from developing in .NET, you'll probably be thinking that you can use a SaveFileDialog. WRONG! We use a wxFileDialog, and specify that we want it to be a save dialog within the parameters.
wxFileDialog *saveDialog = new wxFileDialog(this, wxT("Save File~"), wxT(""), wxT(""),
wxT("Text Files (*.txt)|*.txt|C++ Files (*.cpp)|*.cpp"), wxSAVE);
This is another one which takes a lot of parameters, so I'll explain them all now:
- this ~ the parent of the object
- wxT("Save File~") ~ the title of the dialog
- The next two, I've passed empty strings. These are the default directory, and the default file. (You can define them if you wish, but you don't have to)
- Next, we have the filter. The filter accepts a specific format: "the text to display|*.filetype|another type|*.otherfiletype" (if you're familiar with .NET, you should be familiar with this)
- And lastly, the style of the dialog: wxSAVE, since we want a save dialog
Next, we actually need to display the dialog box, and determine what the user pressed (OK or Cancel) After that, we save the contents of the text area to the file specified:
int response = saveDialog->ShowModal();
if (response == wxID_OK) { // if the user clicked OK, we should save the text
this->text->SaveFile(saveDialog->GetPath()); // this is pretty cool. we can save it with just one line!
// wxTextCtrl has a member function called SaveFile, which you simple pass the path to and it will save
// our dialog has a member function called GetPath, which will return the entire path and file name
}
} // end of the event handler
Since the open file event is almost identical, I'll post the code now~ the only difference is that we specify wxOPEN instead of wxSAVE:
void Notepad::OnOpen(wxCommandEvent &event) {
wxFileDialog *openDialog = new wxFileDialog(this, wxT("Open File~"), wxT(""), wxT(""),
wxT("Text Files (*.txt)|*.txt|C++ Files (*.cpp)|*.cpp"), wxOPEN);
int response = openDialog->ShowModal();
if (response == wxID_OK) {
this->text->LoadFile(openDialog->GetPath());
}
}
And the last event is the close event. This one is simple, it's only one line long. We call Destroy() to basically dispose of the window and any resources used:
void Notepad::OnExit(wxCommandEvent &event) {
this->Destroy(); // close the window, and get clear any resources used (eg, memory)
}
Almost there... Let's implement it in our app!
And now for the bit that you've been waiting for. Implementing the app, so we can actually compile it! w00t! This shouldn't be anything new, provided that you've read Part II.
class MainApp : public wxApp {
public: // remember this from part 2? very simple from here on in, we're almost done
virtual bool OnInit();
};
bool MainApp::OnInit() {
// create a new Notepad (we used a wxFrame in part 2)
Notepad *main = new Notepad();
main->Show(true); // show it
return true;
}
// and lastly, we just have to implement our application!
IMPLEMENT_APP(MainApp)
Now, compile and run~ you should have a basic notepad application! Note that you don't have the keyboard shortcuts to copy, paste, etc~ but if you highlight some text, and right click, you get a pretty big context menu, with plenty of options. And if you're feeling up to the task, you could try adding them as a new menu to your menu bar!
And finally, here is the entire program, just in case you got lost along the way:
#include <wx/wx.h>
class Notepad : public wxFrame {
public:
Notepad(); // our default constructor
private:
wxMenuBar *menu; // the menu bar
wxMenu *file; // the file menu (keep this simple
wxTextCtrl *text; // the main text area
void OnSave(wxCommandEvent &event); // the click event for "save"
void OnOpen(wxCommandEvent &event); // the click event for "open"
void OnExit(wxCommandEvent &event); // the click event for "close"
// declare some ID values for our menu items
// these can be anything, but I personally tend to start from 1,000
enum MenuControls {
idSave = 1000,
idOpen, idExit
};
// this bit's important~
// it's a macro, which is basically saying we want to define some events
// (events such as clicking menu items in the menu)
DECLARE_EVENT_TABLE()
};
// this is our event table, where we assign functions to specific events
// using those IDs we created earlier
// basically, by passing a number and a method name, we assign that method name a value, or a key
// this key is then assigned to items later (in this case, menu items) as the event handler
// don't worry if this doesn't make sense right now, it will become clear later on
BEGIN_EVENT_TABLE(Notepad, wxFrame) // begin the event table for our Notepad class, which inherits wxFrame
EVT_MENU(idSave, Notepad::OnSave) // set an event for our idSave, and the function OnSave
EVT_MENU(idOpen, Notepad::OnOpen) // set an event for open
EVT_MENU(idExit, Notepad::OnExit) // set an event for exit
END_EVENT_TABLE() // end the event table
// our constructor, which does all this stuff for the wxFrame constructor
// this makes it easier to simply create our notepad object later~
Notepad::Notepad() : wxFrame(NULL, wxID_ANY, wxT("wxNotepad"), wxDefaultPosition, wxSize(650,500)) {
this->menu = new wxMenuBar(); // instantiate our menu bar
this->file = new wxMenu(); // instantiate our file menu for our menu bar
this->file->Append(idSave, wxT("&Save File\tCtrl-S")); // add a save option to the menu bar
// note how we passed "idSave" as the first argument? This means that we're assigning OnSave
// as an event handler when the user clicks the save menu item
// now we do the same for our "open" option, assigning the OnOpen event to it
this->file->Append(idOpen, wxT("&Open File\tCtrl-O"));
this->file->AppendSeparator(); // add a separator (between our file I/O options and our exit option
this->file->Append(idExit, wxT("E&xit\tCtrl-F4")); // add an exit option to the file menu
this->menu->Append(file, wxT("&File")); // add the file menu to the menubar, and give it the title "File"
this->SetMenuBar(menu); // set our menu bar to be visible on the application
// lastly in our constructor, we're going to add the text area~ we need to pass quite a few parameters here
// the first parameter is the parent, so we pass this (this is the owner)
// wxID_ANY ~ the ID of the control. Since it doesn't matter, we can just pass wxID_ANY
// the third, wxt("") is the text to put in the text area straight away. We pass an empty string here
// wxDefaultPosition: the position of the control
// wxDefaultSize: the size of the control
// and lastly, we have the style~ we want two styles here, wxTE_MULTILINE and wxTE_PROCESS_ENTER
// this way, we enable multi-line editing, and allow the user to press enter to move on to a new line
this->text = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER | wxTE_MULTILINE);
}
// now it's time to define our save event (for when the user clicks "Save"
void Notepad::OnSave(wxCommandEvent &event) {
// first, we create a wxFileDialog, which takes a number of parameters
// this ~ the parent of the object
// wxt("Save File~") ~ the title of the dialog
// the next two, I've passed empty strings. These are the default directory, and the default file
// (you can define them if you wish, but you don't have to)
// next, we have the filter. The filter accepts a specific format:
// "the text to display|*.filetype|another type|*.otherfiletype
// and lastly, the style of the dialog: wxSAVE, since we want a save dialog
wxFileDialog *saveDialog = new wxFileDialog(this, wxT("Save File~"), wxT(""), wxT(""),
wxT("Text Files (*.txt)|*.txt|C++ Files (*.cpp)|*.cpp"), wxSAVE);
// next, we display the dialog box
// it will return one of 2 values: wxID_OK, or wxID_CANCEL (whichever button the user clicked)
int response = saveDialog->ShowModal();
if (response == wxID_OK) { // if the user clicked OK, we should save the text
this->text->SaveFile(saveDialog->GetPath()); // this is pretty cool. we can save it with just one line!
// wxTextCtrl has a member function called SaveFile, which you simple pass the path to and it will save
// our dialog has a member function called GetPath, which will return the entire path and file name
}
} // end of the event handler
// next, we have our open dialog, which is practically the same as our save dialog, so I won't go into detail
// note how we pass wxOPEN instead of wxSAVE as the style though~
void Notepad::OnOpen(wxCommandEvent &event) {
wxFileDialog *openDialog = new wxFileDialog(this, wxT("Open File~"), wxT(""), wxT(""),
wxT("Text Files (*.txt)|*.txt|C++ Files (*.cpp)|*.cpp"), wxOPEN);
int response = openDialog->ShowModal();
if (response == wxID_OK) {
this->text->LoadFile(openDialog->GetPath());
}
}
// if the user clicks exit (from the menu) then we should close the window
void Notepad::OnExit(wxCommandEvent &event) {
this->Destroy(); // close the window, and get clear any resources used (eg, memory)
}
// now all that's left is the implementation! we need to create our MainApp class
class MainApp : public wxApp {
public: // remember this from part 2? very simple from here on in, we're almost done
virtual bool OnInit();
};
bool MainApp::OnInit() {
// create a new Notepad (we used a wxFrame in part 2)
Notepad *main = new Notepad();
main->Show(true); // show it
return true;
}
// and lastly, we just have to implement our application!
IMPLEMENT_APP(MainApp)
Where do I go from here?
Well, that's it for this series of tutorials (for now, at least)
If there's anything that you're still not sure of, please feel free to leave a comment on this tutorial, requesting one~ hopefully, I'll be able to get it done within 24-48 hours of you requesting it.
Remember that you can check out the wxWidgets documentation if you get stuck along the way. Also, keep an eye on my contributions~ you never know when a tutorial or snippet might crop up.
Happy coding!









MultiQuote









|