Single Instance or Multiple Instances?

team bbl


Depending on the nature of your application, you might either want to allow users to keep invoking multiple instances of the application, or reuse the existing instance when the user tries to relaunch the application or open more than one document from the system's file manager. It's quite common to give users the option of running the application in single-instance or multiple-instance mode, depending on their working habits. One problem with allowing multiple instances is that the order in which the instances write their configuration data (usually when the application exits) is undefined, so user settings might be lost. It might also confuse novice users who don't realize they are launching new instances of the application. The behavior of allowing multiple applications to be launched is the default on all platforms (except when launching applications and opening documents via the Finder on Mac OS), so you will need to write additional code if you want to disable multiple instances.

On Mac OS (only), opening multiple documents using the same instance is easy. Override the MacOpenFile function, taking a wxString file name argument, which will be called when a document associated with this application is opened from the Finder. If the application is not already running, Mac OS will run it first before calling MacOpenFile (unlike on other platforms, it does not pass the file name in the command-line arguments). If you are using document/view, you might not need to provide this function because the default implementation on Mac OS X is as follows:

 void wxApp::MacOpenFile(const wxString& fileName) {     wxDocManager* dm = wxDocManager::GetDocumentManager() ;     if ( dm )         dm->CreateDocument(fileName, wxDOC_SILENT) ; } 

However, even on Mac OS, this will not prevent the user from running the application multiple times if launching the executable directly, as opposed to opening an associated document. One way in which you can detect and forbid more than one instance from being run is by using the wxSingleInstanceChecker class. Create an object of this class that persists for the lifetime of the instance, and in your OnInit, call IsAnotherRunning. If this returns TRue, you can quit immediately, perhaps after alerting the user. For example:

 bool MyApp::OnInit() {     const wxString name = wxString::Format(wxT("MyApp-%s"),                               wxGetUserId().c_str());     m_checker = new wxSingleInstanceChecker(name);     if ( m_checker->IsAnotherRunning() )     {         wxLogError(_("Program already running, aborting."));         return false;     }     ... more initializations ...     return true; } int MyApp::OnExit() {     delete m_checker;     return 0; } 

But what if you want to bring the existing instance to the front or open a file that was passed to the second instance in the first instance? In general, this requires the instances to be able to communicate with each other. We can use wxWidgets' high-level interprocess communication classes to do this.

In the following example, we'll set up communication between instances of the sample application and allow secondary instances to ask the first instance to either open a file or raise itself to indicate that the user has requested the application to open. The following declares a connection class for use by both instances, a server class for the original instance to listen for connections from other instances, and a client class that is used by subsequent instances for communicating with the original instance.

 #include "wx/ipc.h" // Server class, for listening to connection requests class stServer: public wxServer { public:     wxConnectionBase *OnAcceptConnection(const wxString& topic); }; // Client class, to be used by subsequent instances in OnInit class stClient: public wxClient { public:     stClient() {};     wxConnectionBase *OnMakeConnection() { return new stConnection; } }; // Connection class, for use by both communicating instances class stConnection : public wxConnection { public:     stConnection() {}     ~stConnection() {}     bool OnExecute(const wxString& topic, wxChar*data, int size,                    wxIPCFormat format); }; 

OnAcceptConnection is called within the original (server) instance when the client tries to make the connection. We need to check that there are no modal dialogs running in this instance because when a modal dialog is open, there should be no other activity in the application that might require a user's attention.

 // Accepts a connection from another instance wxConnectionBase *stServer::OnAcceptConnection(const wxString& topic) {     if (topic.Lower() == wxT("myapp"))     {         // Check that there are no modal dialogs active         wxWindowList::Node* node = wxTopLevelWindows.GetFirst();         while (node)         {             wxDialog* dialog = wxDynamicCast(node->GetData(), wxDialog);             if (dialog && dialog->IsModal())             {                 return false;             }             node = node->GetNext();         }         return new stConnection();     }     else         return NULL; } 

OnExecute is called when the client application calls Execute on its connection object. OnExecute can have an empty data argument, in which case the application should just raise its main window. Otherwise, the application should determine whether the file is already open and raise the associated window if so, or open the document if not.

 // Opens a file passed from another instance bool stConnection::OnExecute(const wxString& WXUNUSED(topic),                              wxChar *data,                              int WXUNUSED(size),                              wxIPCFormat WXUNUSED(format)) {     stMainFrame* frame = wxDynamicCast(wxGetApp().GetTopWindow(), stMainFrame);     wxString filename(data);     if (filename.IsEmpty())     {         // Just raise the main window         if (frame)             frame->Raise();     }     else     {         // Check if the filename is already open,         // and raise that instead.         wxNode* node = wxGetApp().GetDocManager()->GetDocuments().GetFirst();         while (node)         {             MyDocument* doc = wxDynamicCast(node->GetData(),MyDocument);             if (doc && doc->GetFilename() == filename)             {                 if (doc->GetFrame())                     doc->GetFrame()->Raise();                 return true;             }             node = node->GetNext();         }         wxGetApp().GetDocManager()->CreateDocument(                                          filename, wxDOC_SILENT);     }     return true; } 

In OnInit, the application will check for multiple instances using wxSingleInstanceChecker as usual. If no other instances are found, the instance can set itself up as a server, waiting for requests from future instances. If another instance was found, a connection is made to the other instance, and the second instance asks the first to open a file or raise its main window before exiting. Here's the code to do this:

 bool MyApp::OnInit() {     wxString cmdFilename; // code to initialize this omitted     ...     m_singleInstanceChecker = new wxSingleInstanceChecker(wxT("MyApp"));     // If using a single instance, use IPC to     // communicate with the other instance     if (!m_singleInstanceChecker->IsAnotherRunning())     {         // Create a new server         m_server = new stServer;         if ( !m_server->Create(wxT("myapp") )         {             wxLogDebug(wxT("Failed to create an IPC service."));         }     }     else     {         wxLogNull logNull;         // OK, there IS another one running, so try to connect to it         // and send it any filename before exiting.         stClient* client = new stClient;         // ignored under DDE, host name in TCP/IP based classes         wxString hostName = wxT("localhost");         // Create the connection         wxConnectionBase* connection =                      client->MakeConnection(hostName,                                      wxT("myapp"), wxT("MyApp"));         if (connection)         {             // Ask the other instance to open a file or raise itself             connection->Execute(cmdFilename);             connection->Disconnect();             delete connection;         }         else         {             wxMessageBox(wxT("Sorry, the existing instance may be             too busy too respond.\nPlease close any open dialogs and retry."),                 wxT("My application"), wxICON_INFORMATION|wxOK);         }         delete client;         return false;     }     ...     return true; } 

If you want to find out more about the interprocess communication classes used here, another example is provided by the standalone wxWidgets Help Viewer, which you can find in utils/helpview/src in your wxWidgets distribution. This application responds to requests by an application to view a help file. See also samples/ipc and the wxWidgets class reference for wxServer, wxClient, and wxConnection.

    team bbl



    Cross-Platform GUI Programming with wxWidgets
    Cross-Platform GUI Programming with wxWidgets
    ISBN: 0131473816
    EAN: 2147483647
    Year: 2005
    Pages: 262

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net