YOU CAN CODE!

 

With The Case Of UCanCode.net  Release The Power OF  Visual C++ !   HomeProducts | PurchaseSupport | Downloads  
Download Evaluation
Pricing & Purchase?
E-XD++Visual C++/ MFC Products
Overview
Features Tour 
Electronic Form Solution
Visualization & HMI Solution
Power system HMI Solution
CAD Drawing and Printing Solution

Bar code labeling Solution
Workflow Solution

Coal industry HMI Solution
Instrumentation Gauge Solution

Report Printing Solution
Graphical modeling Solution
GIS mapping solution

Visio graphics solution
Industrial control SCADA &HMI Solution
BPM business process Solution

Industrial monitoring Solution
Flowchart and diagramming Solution
Organization Diagram Solution

Graphic editor Source Code
UML drawing editor Source Code
Map Diagramming Solution

Architectural Graphic Drawing Solution
Request Evaluation
Purchase
ActiveX COM Products
Overview
Download
Purchase
Technical Support
  General Q & A
Discussion Board
Contact Us

Links

Get Ready to Unleash the Power of UCanCode .NET

 


UCanCode Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!

"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"


VC++ MFC Tutorial: Auto Save files, CDocument - CView

 
By Jesse Ezell. 

How to implement autosave and autorecover features in your application.

Introduction

How to autosave files and then recover these files after a program crashes, is something that is seldom discussed, but adds power and flexibility to a program. This article will describe how to implement an autosave and autorecover method similar to that used by MS Office.

How it works

Autosaving is quite simple. After a specific amount of time, your program must serialize the loaded documents to the disk at a specific location. These serializations must not overwrite the files that are currently in use by the user, because this would eliminate the user's choice of whether or not they would like to save the file on exit from your program. Each time the program loads up, it must search for autosaved files and restore them as needed.

As with Office, this implementation saves these temporary files to a temporary directory inside your Windows directory and searches this directory at load time for autosaved files. If any are found, it will then begin its recovery process.

Assigning an Autosave Directory

First, you must choose where you are going to save the files. This directory must remain constant, or else you would have to search the entire hard disk for autosaved files... and that is out of the question. For practical purposes, your program should place them inside a folder in the most constant directory on the HD, the Windows directory. This directory should be stored in the environment variable WINDIR. If for some reason, the user's system does not contain a WINDIR environment variable, or the environment variable is incorrectly set, your program needs to detect this and choose another directory. You can easily check to see if the stored directory exists, by using the CFileFind class to search for it. The following code will do just that (gm_autosave directory should be a string, either global, as with this example, or accessible through a member function in your application). It is best preformed within the InitInstance function of your application.

::GetEnvironmentVariable("WINDIR",buffer,512);
CFileFind CFF;
if(CFF.FindFile(buffer,0))
{
    base=buffer;
}
else base = "C:\\TEMP";

gm_autosaveDirectory = base+"\\TEMP";

Optionally, you could use GetTempPath(DWORD nBufferLength, LPTSTR lpBuffer); to retrieve a temporary path. However, the above implementation will allow you to specify a specific directory for the files to occupy, such as "Temporary Autosaved Files".

Autosaving files

Now that the path is determined, your program will know where to save its files. The next part is, therefore, implementing the save routine in your application. To avoid soaking up system resources, use the OnTimer function in your Main Frame window. On initialization, your program needs to call the SetTimer(...) function with the appropriate values. SetTimer(0,m_autosavetime * 60 * 1000,NULL) will install the timer for you (assuming m_autosavetime is in minutes). The OnTimer() function of your Main Frame will now be called each time the timer has expired. Within this function, you will need to send autosave messages to all the views contained by your app. Use EnumChildWindows to call a procedure with each child window.

::EnumChildWindows(m_hWnd,AutosaveTimerChildProc,NULL);

AutosaveTimerChildProc needs to verify that the given child is a view for the document before sending the message to the window. DYNAMIC_DOWNCAST the pointer passed to your procedure to verify that it is a view, and then use PostMessage to notify your view of the change:

BOOL CALLBACK AutosaveTimerChildProc( HWND hwnd,  LPARAM lParam)
{
    if(DYNAMIC_DOWNCAST(CMyView,CWnd::FromHandle(hwnd)) != NULL)
    {
        ::PostMessage(hwnd,WM_MYVIEW_AUTOSAVETIMER,0,0);
    }
    return(TRUE);
}

Note: WM_MYVIEW_AUTOSAVETIMER should be defined as an Application Message, which is based of WM_APP.

Now, your view must process the message and save the file in a restorable way. Add a message map such as this:

ON_MESSAGE(WM_MYVIEW_AUTOSAVETIMER,OnAutosaveTimer)

and prototype the function in your view's class definition as a void accepting the standard WPARAM and LPARAM messages. This function can get a little tricky. The problem is that if the user saves the file in another location, then the restore path for the file will change also. Since the autosave name is based off the actual filename (in case the user needs to manually access the file in some strange incident) and you don't want more than one copy of the autosaved file, your program must always store the name of the last autosave backup and delete the autosaved backup, before it writes its copy of the file. This implementation uses a vector of CStrings to store the backup filenames (this vector will contain either the name of the backup or nothing at all). Additionally, your program must make sure that the directory for your autosave has been created before saving to it! If an error occurs during the process, the choice is up to you how you will recover or handle it.

Collapse
void CMyView::OnAutosaveTimer(WPARAM w, LPARAM l)
{
    CFileFind CFF;
    if(CFF.FindFile(gm_autosaveDirectory.GetBuffer(1))==FALSE) 
    {
        if(CreateDirectory(gm_autosaveDirectory.GetBuffer(1), 
                                 NULL) ==0) //create directory
        {
            //an error has occured, process the error here
        }

    }

    // document name and your autosave extension
    CString  fname = (gm_autosaveDirectory + 
          "\\"+GetDocument()->GetTitle()+".MBK"); 
    if(m_autosave_names.size() > 0)  //delete old file
    {
        if(CFF.FindFile((*m_autosave_names.begin()))==TRUE)
        {
            if(::DeleteFile(((*m_autosave_names.begin()))) == 0)
            {
                //an error has occured, process the error here if you want
                //however, if the file simply does not exist, 
                //that is fine, ignore the error and continue
            }
        }
        // remove the old filename from the vector
        m_autosave_names.erase(m_autosave_names.begin());
    }
    m_autosave_names.push_back(fname); //add the new filename to the vector

    //call store function
    GetDocument()->StoreAutoRecoveryInformation(fname);
}

The StoreAutoRecoveryInformation function is implementation dependant. An easy implementation would simply serialize the document's true path to an archive, and then call the Serialize function to save the file after the path. For our example, this will suffice:

CMyDoc::StoreAutoRecoveryInformation(CString path)
{
    CFile f;
    if(f.Open(path,CFile::modeWrite | CFile::modeCreate)!=0)
    {
        CArchive ar(f,CArchive::store);
        ar.WriteString(path);
        try
        { 
            Serialize(ar);
        }
        catch(CException *e)
        {
            //write error occured, process here
        }
        ar.Close();
        f.Close();
    }
    else
    {
        //open error occured, process here
    }
}

Autosave Recovery

Rather than writing an entirely new document and view class for recovered files, you can simply modify your Document's Serialize(...) function to check the file extension on serialization. If the extension is that of the autosave file type, perform the appropriate actions.

Collapse
//helper function to get file extension from a given path
CString MakeExt(CString fname) 
{
    for(int i = fname.GetLength()-1; i >0; i--)
    {
        if(fname.GetAt(i)=='.') return fname.Mid(i);
        if(fname.GetAt(i)=="\\") break;
    }
    return "";
}
.
.
.
CMyDoc::Serialize(CArchive ar)
{
    CString ext=MakeExt(ar.GetFile()->GetFileName());
    ext.MakeLower();
    if(ext == ".mbk" && ar.IsLoading())
    {
        CString s;
        //read path to restore path member variable
        m_restorepath = ar.ReadString(s)
        // read old path to member variable
        m_oldpath = ar.GetFile()->GetFileName();
        CMyBaseDocument::Serialize(); 
          //call the base document's serialize function, 
          // or perform your default serialization here
    }
    // put your default serialization code here
    else CMyBaseDocument::Serialize();
}

Because of the way that the MFC code for document serialization works, the document's view will have to handle changing the document's path to the correct location. (By default, the path will be to the autorestored copy, but you want to restore the original path, so that the user can continue where they left off with as little hassle as possible.) In the OnInitialUpdate function of your view, check to see if the document's restore path has been set. If it has, then you know that the program has just loaded an autorecovered file, and the path and title of the document and view must be changed to the proper location.

CMyDoc *doc=GetDocument();
if(doc->m_restorepath.GetLength() > 0)
{
    doc->SetPathName(doc->m_restorepath,TRUE);
    doc->SetModifiedFlag(TRUE);
    doc->UpdateAllViews(NULL);
    m_autosave_names.push_back(doc->m_oldpath);
}

Your program must autorecover the files each time it loads. This is done by searching the autosave directory for all autosaved files and loading as needed from your InitInstance function. If you allow multiple instances of your program, make sure that the current instance is the only instance before recovering autosaved files.

Collapse
BOOL bFound = FALSE;
HANDLE hMutexOneInstance = NULL;
#ifdef _WIN32
    hMutexOneInstance = 
      CreateMutex(NULL,TRUE,_T("PreventSecondInstanceMutex"));
    if(GetLastError() == ERROR_ALREADY_EXISTS)
    bFound = TRUE;
#else
    if(m_hPrevInstance != NULL)
    bFound = TRUE;
#endif

#ifdef _WIN32
    if(hMutexOneInstance) ReleaseMutex(hMutexOneInstance);
#endif 
    
.
.
.

if(bFound)
{
    if(CFF.FindFile((gm_autosaveDirectory+"\\"+ 
                   "*.MBK").GetBuffer(1),0)==TRUE)
    {
        if(::MessageBox(NULL,
          "Autosaved files found. Would you like to recover them?\n"
          "(WARNING: IF YOU PRESS NO, YOU WILL NOT BE ABLE TO RECOVER"
          " THE FILES IN THE FUTURE).", "MYDOC", MB_YESNO) != IDNO) 
        {
            while(CFF.FindNextFile() !=0)
            {
                CMyDoc *doc= 
                 (CMyDoc *)OpenDocumentFile(CFF.GetFilePath().GetBuffer(1));
            }
            OpenDocumentFile(CFF.GetFilePath().GetBuffer(1));
        }
        else
        {
            while(CFF.FindNextFile() !=0)
            {
                DeleteFile(CFF.GetFilePath().GetBuffer(1));
            }
            DeleteFile(CFF.GetFilePath().GetBuffer(1));
        }
    }
}

Use class wizard to add the PostNcDestroy member to your view class, and then delete the autosaved file there (it will only be deleted if the window closes normally). The following chunk of code will do that for you.

CFileFind CFF;
if(m_autosave_names.size() > 0)
{
    if(CFF.FindFile(((*m_autosave_names.begin())).c_str())==TRUE)
    {
        ::DeleteFile(((*m_autosave_names.begin())).c_str());
    }
    m_autosave_names.erase(m_autosave_names.begin());
}

Finally, at program close, these files should be deleted and the directory removed. In your ExitInstance function, delete the directory.

CFileFind CFF;
if(CFF.FindFile(gm_autosaveDirectory.GetBuffer(1))==TRUE)
   RemoveDirectory(gm_autosaveDirectory.GetBuffer(1));

 

 

Copyright ?1998-2022 UCanCode.Net Software , all rights reserved.
Other product and company names herein may be the trademarks of their respective owners.

Please direct your questions or comments to webmaster@ucancode.net