Link to home
Start Free TrialLog in
Avatar of salamander
salamander

asked on

indicating progress of an open-file action...

Is there a relatively straight-forward way to show progress (via some form of progress control) of an open-file(CreateFile) action?  If so, how?  Overlapped structure??
Avatar of tma050898
tma050898
Flag of United States of America image

It depends. What are you opening and how are you doing the open? For example, are you using CFile to open a text or binary file? Are you serializing a file in from disk?
Avatar of salamander
salamander

ASKER

Hmmm... I may have asked this question before really thinking about it.  I use a CArchive object and read integers in.  It would certainly appear that all I need to do is know how many integers I must read in.  Oh well, put an answer up and I'll give you the points anyway.  I kind jumped the gun on this one!
ASKER CERTIFIED SOLUTION
Avatar of tma050898
tma050898
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
No prob.
Danr these PAQ's ... you have to pay the points even for a non-answer like this :-(

I was hoping to find a neat solution that didn't involve hooking directly into the Serialize function of the document. As I use the ole document CDocItem list, the serialization is in the MFC document class itself, so I need to copy the code from the MFC implementation into my document class serialize function (which is not a nice thing to do).

Oh well .. I'll keep going the way I have been.

Actually, I may have just thought of a solution .. I'll try it out when I can and post it here (if I can remember where this question is).

RONSLOW,

Actually, I had to do this at work about 2 weeks ago. I didn't post the solution because salamandar said he had it under control. However, I am going to post it anyway because its not fair that someone comes along, sees the paq header, pays points to see an answer and gets zippo. I'll post what I did this evening.

Tom

Thanks (on behalf of other PAQ readers)

I know how frustrating it is not knowing what sort of answer you are going to see.

There have been requests in the suggestions area that the grade a PAQ received should be included in the listing .. then you know beforehand that you are going to see an A grade answer, or a D or whatever.

If one is short on points, then that would save you wasting them on answers that just didn't come up to scratch.

BTW: I use a progress control on the status bar to indicate loading progress (this functionality is part of my CSatusBar derived class).  A simpler alternative would be to show a percentage complete message in the status bar.  Another solution would be a modeless dialog showing the progress control.  I'd be interested to see what method you decided on for your case.

We use the mainframe's status bar and show progress (as opposed to simply a completion msg). However, with this approach you could do any of the above.

I screwed up and forgot to bring the files home I needed. However, I’ll go over the basics of what I did and post the exact code tomorrow.

As you know, the serialization works through a CArchive object that is based on a CFile object. The problem that I ran into while trying to have a progress bar that would indicate progress while serializing our data files was that the CArchive’s file is cached during I/O.  Therefore, by the time our Serialize function was called, the file had already been read in!

The only way I could think of to solve this problem was to derive the class that would be passed to the CArchive c’tor from CFile and override its Read and Write functions. This class (CFxFile) takes as an argument in its c’tor the hwnd of a window. The Read and Write functions keep track of how much of the file has been read and written, respectively. These amounts are then sent via a user-defined message to the window handle (received in its c’tor). In our case, the window handle is the mainframe window handle. Our mainframe class then has message map entries for the messages being sent and updates the status bar accordingly.

This all works great for dialog-based applications or for situations where for some reason the application is doing the serialization “manually”. For example, our application has multiple ole documents whose data is all saved into one physical file. Therefore, using the normal document serialization wouldn’t work for us and we create the CArchive object and do the loading and storing ourselves.

However, how would the idea above work in a document-based application when the document itself does the serializing? I think that this is what you specifically asked about, RONSLOW. I haven’t tested this, but it looks pretty straightforward. If you look at doccore.cpp’s OnOpenDocument function, you’ll see that it calls a virtual function called GetFile and bases its CArchive on it. Since the GetFile is supposed to return a CMemoryFile object, I would make two adjustments to the CFxFile class described above.

1. Derive from CMemoryFile instead of CFile
2. Override the GetFile to instantiate a CFxFile object instead of a CMemoryFile. This object will be returned to the document and the correct file object with its overrides of Read and Write will be used.

Alternatively, in an application w/o a UI, a function pointer could have been passed to the CFxFile’s c’tor instead of a hwnd. Then the CFxFile::Read and CFxFile::Write functions would have called that function instead of sending messages.

That’s about all there is to it. I’ll post the exact Read and Write function tomorrow.

Tom

Oops! I was writing my comment w/o looking at the doccore.cpp file. Actually, it's a CMirrorFile (not a CMemoryFile!). Sorry about that.

Tom

You method looks very good.

I take it then that you just give an indication in bytes (or whatever) of the file i/o done, rather than a percentage complete.  Getting a percentage complete in this case would be possible to calculate when reading, but not when writing.

For my purposes, I am after a percentage read/written.  This relies on the code determining how many items there are in my document and updating a progress dialog as I read/write them fromt/to the archive.

BTW, in my case, the file/acrhive caching doesn't bother me .. I'm more interested in seeing how many items are read/written as it happens .. forgetting about the caching just means that the progress dialog may go 'slow' as the cache is updated.

The technique you propose (as with the one I am using currently, I might add) may still be a bit more intrusive than I'd like to have, when trying to fit into the document framework.  Perhaps I'll find it is simpler than I think it sounds when I see your code.

What I'm hoping to come up with is a way to hook into the serializing process without having to copy and change (much) code from the MFC source.

One possibility I am looking into is to derive a class from CArchive (maybe CArchiveProgress ??) and when serializing my document, I'd construct a CArchiveProgress that was a clone/wrapper for the CArchive passed in, and then use that one in the call to the base COleDocument class serialize method.  

This is similar to what you are doing with your CFile-derived class, but at the archive level.

However, even this way (if possible) would not help with getting percentage figures (which are needed for a progress bar).

I'll let you know if have any luck with this.

I take it then that you just give an indication in bytes (or whatever) of the file i/o done, rather than a percentage complete.  Getting a percentage complete in this case would be possible to calculate when reading, but not when writing.
===========================================================
Actually, I'm almost positive that pctgs are shown for both. I know 100% that pctgs are shown for reading.

Perhaps I'll find it is simpler than I think it sounds when I see your code.
===========================================================
I think I get a little wordy because I'm afraid I'll leave something out. This is why I'm thankful that we have editors for my books ;) !!! It's really something that you can plug into an existing app in minutes. Anyway, its only about 15 lines in each function and I'll post 'em when I get in tomorrow.

I'm not sure what you have planned with the CArchive, but I would definitely be interested in seeing it.

Tom
So, when reading, you get the total file size at the start, and then count the number of bytes read?  That's pretty easy .. but ...

How can you do that on output when, as they say, "it ain't over 'til its over"?

I don't want to fiddle to much with my code at the moment .. I'm in the middle of other changes and don't want to through any unncessary spanners in the works .. but I'll do a little more thinking about it and let you know what I come up with after the sawdust settles in my brain.

Roger

Darn .. there isn't a virtual function in sight in CArchive, so there is no point in deriving from it :-(

I was hoping to override the << etc operators so that I could get my filthy little hands on the guts of the serializing process (now there's a pretty mental picture :-).  But without virtuals this will be tricky.

Now you know why I did it at the file level ;-) I ran into all kinds of problems trying to do it at the carchive level. I was hoping you'd see something that I missed.
Perhaps I can go UP a level rather than DOWN.

As I am using CDocItem's I may be able to hook into the serialize call for that and bump up a counter.

Actually, what I'd do in the serialize for the CDocItem is to get the doc pointer for the archive, cast it to be a pointer to my doc class and call a member function of the doc which updates a counter.

void CMyDocItem::Serialize(CArchive& ar) {
  CMyDocument* pMyDoc = DYNAMIC_DOWNCAST(CMyDocument, ar.m_pDocument);
  if (pMyDoc) {
    pMyDoc->OnItemSerialized();
  }
  CDocItem::Serialize(ar);
  .. other stuff here
}

The only problem then, if I want to work out a percentage, is determine the total number of items. For write this is (fairly) easy.  But for read I'd need to know the internal serialize format and peek at the count
eg.

DWORD CMyDocument::GetArchiveItemCount (CArchive& ar) {
  DWORD dwCount = 0;
  if (ar.IsStoring()) {
    POSITION pos = GetStartPosition();
    while (pos) {
      CDocItem* pDocItem = GetNextItem(pos);
      if (!pDocItem->IsBlank()) ++dwCount;
    }
  } else {
    ar.Flush();
    CFile* pFile = ar.GetFile();
    DWORD here = pFile->GetPosition()
    ar >> dwcount;
    pFile->Seek(herer,CFile::begin);
    ar.Flush();
  }
  return dwCount;
}

Then I can setup the progress bar with the approriate range (based on the number of items) and then step it by one on every call to OnItemSerialzed().

The only bit of sneakyness is peeking at the count .. if the format ever changes (I doubt it would) then this wouldn't work .. but MS would be in BIG trouble if they changed the serialize format !!!.

What do you thinkg of that idea ???

Pretty good! However, I have two questions about it...

1. Aren't you locking yourself into one method of serializing your data, though? IOW, with this you would have to use CDocItem. Or am I missing something? With the CFxFile, I can take that and use it on *any* project.

2. Also, I think this idea would be a lot more work for our situation because we have multiple documents that all serialize to one physical file. Therefore, in our case we need the progress at the file byte count level and not the document object count level.

Lemme know what you think...
Tom

Unfortunately when working with documents all the creation of files and archives happens within MFC internal functions.  These function are not trivial, so just overriding them with an edited copy is not a good solution.  That is why the CFile-derived class is not a suitable alternative.  A CArchive-derived class might have been possible (as one can get a hook in at the Serialize call) - but CArchive is all virtual.  When using a COLEDocument, you would almost always use CDocItem's, because a COleDocument includes a doc item list and the code to keep track of them.

In other situations, the document class for ones app would do all the serialising, and so progress indications can (usually) be easily put into the users code.

It is interesting that you have several docs going to the one file .. is it going to structured storage?  I also have that, and display the status bar progress bar sequentially for each stream.

I don't think there is a good general solution.  But there may be different solutions that are suitable for different situations.  The CDocItem solution I suggested above may be the 'best' for OLE documents that make use of the CDocItem list implemented in MFC.

Your code would be good in cases where one does one's own file open/save (bypass or completely override the CDocument class processing).

That is why the CFile-derived class is not a suitable alternative...
====================================
I'm still not sure why you think this is not a good solution. In no way is there any mfc code copied and pasted. We are deriving a class from the base class that the CArchive expects. Now when CArchive calls the file's virtual read and write functions, our's will be called. That's a very standard example of polymorphism.

In addition, the cdocument class's GetFile function is virtual. That right there tells me that mfc expects people to derived their own classes from cfile and have them used for the serialization process.

Don't get me wrong. I think you're code is *great* and is perfect for certain situations. However, what if you're not using docitems or have multiple docs writing to a single file or you have a dialog-based app and don't have a document at all?

In our case, we have multiple ole docs and definitely don't want a separate progress "run" for each doc. Therefore, in trying to create a class that could be used in *all* serialization cases we decided to go at this problem from the file standpoint. In addition, as you can, we do use percentages in both reads and writes. Now instead of creating a CFile object, we simply create a CFxFile object and pass its pointer to the CArchive c'tor. It meant changing one line of code (besides adding the cfxfile) in our existing application to get a progress bar on opens and saves.

UINT CFxFile::Read( void* lpBuf, UINT nCount )
{
 UINT nStat = 0;
      
 CWnd *pwndFrame = AfxGetMainWnd();
 if (GOOD_PTR(pwndFrame, CWnd))
 {
  nStat = CFile::Read(lpBuf, nCount);
  // TRACE2("pos: %ld, len: %ld\n", GetPosition(), GetLength());
            
  DWORD dwPos = GetPosition();
  DWORD dwLen = GetLength();
  if (dwPos > m_dwPreviousPosition)
  {
   m_dwPreviousPosition = dwPos;
                  
   float fPercent = ((float)dwPos / (float)dwLen);
   int iProgress = (int)(fPercent * 100);
   pwndFrame->SendMessage(WM_FILE_OPEN_STATUS, iProgress, 0);
  }
 }
 return(nStat);
}

void CFxFile::Write(const void* lpBuf, UINT nCount)
{
 UINT nStat = 0;
      
 CWnd *pwndFrame = AfxGetMainWnd();
 if (GOOD_PTR(pwndFrame, CWnd))
 {
  CFile::Write(lpBuf, nCount);
  // TRACE2("pos: %ld, len: %ld\n", GetPosition(), GetLength());
            
  DWORD dwPos = GetPosition();
  DWORD dwLen = GetLength();
  if (dwPos > m_dwPreviousPosition)
  {
   m_dwPreviousPosition = dwPos;
                  
   float fPercent = ((float)dwPos / (float)dwLen);
   int iProgress = (int)(fPercent * 100);
   pwndFrame->SendMessage(WM_FILE_OPEN_STATUS, iProgress, 0);
  }
 }
}      

BTW, we were using structured storage at the beginning and trashed that for a number of reasons. We went back to straight serialization.

Aha .. I was just tracing thru the file opening process for MFC and saw the call to GetFile, which is a trivial overridable function.  So one can get ones hooks in there.  I hadn't come across that one before because I work with OLE documents (this override doesn't apply for them).  I thought you were talking about CArchive::GetFile).

So your hook can easily be made at this point in the MFC document processing .. IF you are not using OLE structured storage docs.

However, if using COleDocument, then you cannot (AFAIK) hook into the file (storage) open step without a big copy/paste/edit.

If not using COleDocument, tho, your method sounds ideal !!

... GetFile, which is a trivial overridable function.
===============================================
Trival? Yes. But ever so important as it provided the hook we needed.

So your hook can easily be made at this point in the MFC document processing
===============================================
Exactly! Sorry. I thought I had said that it was the doc's GetFile. NOW, I understand what your reserverations were before.

Also, in the case of the coledocument, you may be right. As I mentioned, although we use coledocuments we manually handle the serialization. However, I think you're right that if we were using the normal serialization in conjunction with coledocuments, that may be one area we would have some more work to do to make the cfxfile work.