Beech:Using common dialogs

From WxWiki
Jump to: navigation, search

Introduction

Many operations in a GUI are repetitive, for example opening files, printing, changing directories. It helps the user if the tools they use for these operations are consistent in the way they look and behave and most APIs (application programming interfaces) provide for the common operations. This saves the programmer much time and also ensures a consistent interface. The user can make a selection from a menu and expect to see a dialog that looks familiar and behaves consistently no matter what application they are running.

wxWidgets provides for common dialog usage through a number of classes:

  • wxColourDialog
  • wxFontDialog
  • wxPrintDialog
  • wxFileDialog
  • wxDirDialog
  • wxTextEntryDialog
  • wxMessageDialog
  • wxSingleChoiceDialog
  • wxMultipleChoiceDialog

During the rest of this session we will use a number of these and will start by using the wxFileDialog class to extend the example from the earlier sessions. Before we start though keep in mind what we are doing: we are going to generate events from menu selections and launch common dialogs and use them.

Using the File Dialog - wxFileDialog

File Open Dialog

When we use wxFileDialog as an "open file" dialog we see the familiar dialog box as shown here. This is the file dialog for the Windows platform and would look different on Linux or Mac. Since wxWidgets is a cross-platform framework it will present whatever the common dialog is that is availabe on the particular platform or, if the common dialog is not available, wxWidgets will substitute a generic dialog.

void BasicFrame::OnOpenFile( wxCommandEvent & event )
{
	wxFileDialog* openFileDialog =
		new wxFileDialog( this, _("Open file"), "", "", FILETYPES,
		                  wxOPEN, wxDefaultPosition);

	if ( openFileDialog->ShowModal() == wxID_OK )
	{
		SetCurrentFilename(openFileDialog->GetFilename());
		theText->LoadFile(openFileDialog->GetFilename());
		SetStatusText(GetCurrentFilename(), 0);
		SetStatusText(openFileDialog->GetDirectory(),1);
	}
}

User Submited: Or if it doesn't work, Like it didn't for me, you can try this:

#include <wx/filename.h>

void BasicFrame::OnOpenFile( wxCommandEvent & event )
{
	wxFileDialog* openFileDialog =
		new wxFileDialog( this, _("Open file"), "", "", FILETYPES,
		                  wxOPEN, wxDefaultPosition);

	if ( openFileDialog->ShowModal() == wxID_OK )
	{
		wxString path;
		path.append( openFileDialog->GetDirectory() );
		path.append( wxFileName::GetPathSeparator() );
		path.append( openFileDialog->GetFilename() );
		theText->LoadFile(path);
		SetStatusText(path, 0);
		SetStatusText(openFileDialog->GetDirectory(),1);
	}
}

END of user submitted

User note: In addition to the change above, I also added an include statement for <wx/filedlg.h> and replaced "wxOPEN" with "wxFD_OPEN". (Using wxWidgets 2.8.3)

Here is an example event handler which presents an open file dialog to the user. To use it we:

  • Construct the dialog, setting the arguments to the constructor as appropriate
  • Display the dialog
  • Get a return status when to user dismisses the dialog
  • Use the values held by the dialog if we need to

Construction involves declaring a variable openFileDialog which is a pointer to a type wxFileDialog. The arguments to the constructor are:

  • this - the parent window
  • "Open file" - a caption
  • "" - the default directory
  • "" - the default filename
  • FILETYPES - a list of file filters, in this case defined in the string FILETYPES
static const wxChar *FILETYPES = _T(
	"Text files|*.txt|"
	"C/C++ source files|*.cpp;*.cc;*.c|"
	"C/C++ header files|*.hpp;*.h|"
	"Make files|Mak*;mak*|"
	"Java files|*java|"
	"Hypertext markup files|*html;*htm;*HTML;*HTM|"
	"All files|*.*"
	);
  • wxOPEN - a file dialog style
    • wxOPEN - Open dialog.
    • wxSAVE - Save dialog.
    • wxHIDE_READONLY - Hide read-only files.
    • wxOVERWRITE_PROMPT - Prompt for a confirmation if a file will be overwritten.
    • wxMULTIPLE - For open dialog only: allows selecting multiple files.
  • wxDefaultPosition - not yet implemented

We could just have "*.*" as the file types string but the file dialog will accept a string that is structured like this example. It represents the list of file types that will appear in file dialog's "Files of type" combo-box.

The member function ShowModal() displays the dialog. A dialog box can be either modal or modeless. If it is modal then no other window of the application can get the focus until the dialog box is closed. The user will dismiss the dialog box by selecting either the OK or Cancel buttons. ShowModal() returns an integer and if this is the same as wxID_OK we process the values held in the dialog box data structures by calling the various file dialog member functions.

In our case we get the filename with openFileDialog->GetFilename() and use this as an argument to SetCurrentFilename() which is a method of the parent frame. We also use openFileDialog->GetFilename() to load the file contents into theText. We also call SetStatusText() to set some values in the status bar and finally exit from the event handler at which point the file dialog instance is destroyed.

The Save file and Save as file dialogs are virtually the same as the Open file file dialog. The difference is a matter of how you use them. If you would like to see an example which uses all three then download wxbasic4a.zip and read the source code. I will continue this session with the font dialog.

Using the Font Dialog - wxFontDialog

Font Chooser Dialog

The font dialog enables us to change the font of a parent window, in our case the text control theText. It is a little bit trickier to use than the file dialogs since we (should) determine what the current font settings of the parent window are before constructing and using the font dialog.

To do this we use three other classes

  • wxFontData - holds data for the font dialog selections, we transfer font data between the world and the dialog via this,
  • wxFont - holds data for the font of a window, we transfer font data between the world and the window via this,
  • wxColour - holds colour data for an object.
void BasicFrame::OnChooseFont( wxCommandEvent & event )
{
	wxFontData fontData;
	wxFont     theFont;
	wxColour   colour;

	theFont = theText->GetFont();
	fontData.SetInitialFont(theFont);
	colour = theText->GetForegroundColour();
	fontData.SetColour(colour);
	fontData.SetShowHelp(true);

	wxFontDialog *dialog = new wxFontDialog( this, &fontData );
	if ( dialog->ShowModal() == wxID_OK )
	{
		fontData = dialog->GetFontData();
		theFont = 	fontData.GetChosenFont();
		theText->SetFont(theFont);
		theText->SetForegroundColour(fontData.GetColour());
	}
}

Here is an example which uses the dialog and associated classes. There are a number of steps before constructing and after dismissing the font dialog instance:

  • Get the current font data of the window (our BasicFrame) to a wxFont instance via the GetFont() method of wxTextCtrl and,
  • set this data in the wxFontData instance ready for transfer to the font dialog.
  • Get the current foreground colour of the window via the GetForegroundColour() method and,
  • set this colour in a wxFontData instance ready for transfer to the font dialog.
  • Construct the font dialog.
  • Show the dialog and if it returns wxID_OK we can now reverse the process:
    • Extract the font data from the dialog using wxFontDialog's member function GetFontData() and,
    • transfer this data to a wxFont instance via the wxFontData method GetChosenFont()
    • Extract the foreground colour from font dialog data and,
    • transfer this to the target window.

A little reminder: In presenting the two examples I have shown only the salient details. You should remember that the examples also contain other code that is relevant, for example the window identifiers and the event table. I have left the bulk of the example programs out of the presentation in order to save space and minimize distraction.

Using the Directory Dialog - wxDirDialog

Directory Chooser Dialog

Whenever we have used the open file dialog we have found ourselves peering at some default directory, in my case "My Documents" (who would call a directory "My Documents"? - If it's your computer the directory isn't likely to be called "Someone Else's Documents"), it can be tedious.

To relieve the tedium and place us in a more useful location in the file system structure we can use a dialog and some useful functions. The dialog is the familiar directory chooser shown here and the useful functions are:

  • wxGetCwd() - (Get Current Working Directory) which returns a string containing the current path and,
  • wxSetWorkingDirectory(wxString) - which will set the CWD to our choice.

If we add a data member to store the CWD to our BasicFrame instance then we can retain the CWD during the execution of the program and never see "My Documents" again, unless we choose to.

void BasicFrame::OnChooseDir( wxCommandEvent & event )
{
	wxDirDialog *d =
		new wxDirDialog( this, _("Choose a directory"),
		                 GetCurrentPath(), 0, wxDefaultPosition );

	if ( d->ShowModal() ==  wxID_OK )
	{ SetCurrentPath( d->GetPath() ); }
}

Here is the event handler which produces the change directory dialog. When you compare it with the other dialogs it has a reassuringly familiar look. The arguments to the constructor are:

  • this - the parent window,
  • "Choose a directory" - a caption,
  • GetCurrentPath() - a BasicFrame method which returns the CWD,
  • 0 - a style which isn't used,
  • wxDefaultPosition - a position which isn't yet implemented in wxWidgets version 2.

There is also the familiar if statement:

if ( d->ShowModal() ==  wxID_OK )

And after the directory dialog has been dismissed we can use one of its methods to get the selected directory:

SetCurrentPath( d->GetPath() )

SetCurrentPath() is a simple method we supply to update the BasicFrame data member we have already declared.

Summary

enum {
	BASIC_EXIT = wxID_HIGHEST + 1,
	BASIC_OPEN,
	BASIC_SAVE,
	BASIC_SAVE_AS,
	BASIC_FONT,
	BASIC_DIR,
	BASIC_ABOUT,
	BASIC_HELP
};

If you look through the examples you will see I have made a slight change to the enumerated range that we use for our window ID's.

wxWidgets uses a number of "standard" ID's and the authors recommend that users of the framework avoid simply plugging in any value for a window ID since it may conflict with one of those already allocated. We can avoid conflict by using wxID_HIGHEST + 1 when enumerating our own ID's.

A little reminder: In presenting these I have show only the salient details. You should remember that the examples also contain other code that is relevant, for example the window identifiers and the event table. I have left the bulk of the example programs out of the presentation in order to save space and minimize distraction.