Firstly, apologies for not updating this more frequently.  I haven't wanted to post about things until they're bug-free, just to be certain I'm not talking about something that won't work for you.

So far, I've shown transactional writers.  In these cases, modifications to files and directories aren't visible outside of a transaction until that transaction commits.  There is also a second class of transactions, transactional readers, that isolate changes made outside of a particular transaction from being visible.  This form of isolation is performed at the handle level: within a transaction, a handle opened for read will typically be isolated for the duration of that handle.

To illustrate, let's start with some code:
#include <windows.h>
#include <ktmw32.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <strsafe.h>

LPTSTR szKeywords[10]={_T("cat"), _T("dog"), _T("rabbit"), _T("horse"), _T("cow"), _T("chicken"),
_T("bird"), _T("mouse"), _T("donkey"), _T("camel")};

#define NUM_WORDS 10

LPTSTR szFileName=_T("testfile");

void WriteRandomWord(HANDLE hFile)
{
BOOL bRet;
DWORD dwWord, dwWrote;
dwWord = rand()%NUM_WORDS;


SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
bRet = WriteFile(hFile, szKeywords[dwWord], (_tcslen(szKeywords[dwWord])+1) * sizeof(TCHAR),
&dwWrote, NULL);
SetEndOfFile(hFile);

if (bRet)
_tprintf(_T("Wrote \"%s\" to file\n"), szKeywords[dwWord]);
}

void DisplayFileContents(HANDLE hFile, LPTSTR szDesc)
{
TCHAR szReadIn[20];
DWORD dwLen;
DWORD dwRead;
BOOL bRet;

SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
dwLen = GetFileSize(hFile, NULL);
bRet = ReadFile(hFile, szReadIn, dwLen, &dwRead,
NULL);

if (bRet)
_tprintf(_T("Read \"%s\" from file (%s)\n"), szReadIn, szDesc);
}

int __cdecl _tmain(int argc, TCHAR ** argv)
{
HANDLE hWriterFile;
HANDLE hReaderFile;
HANDLE hNonTransReaderFile;
HANDLE hWriterTrans;
HANDLE hReaderTrans;
DWORD dwLoop;

srand(GetTickCount());


hReaderTrans = CreateTransaction(NULL, NULL, 0, 0, 0, 0, NULL);
if (hReaderTrans == NULL) {
_tprintf(_T("Could not create transaction\n"));
return EXIT_FAILURE;
}


hWriterFile = CreateFile(szFileName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hWriterFile == INVALID_HANDLE_VALUE) {
_tprintf(_T("Could not create test file\n"));
return EXIT_FAILURE;
}

WriteRandomWord(hWriterFile);

CloseHandle(hWriterFile);

hNonTransReaderFile = CreateFile(szFileName,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

SetCurrentTransaction(hReaderTrans);

hReaderFile = CreateFile(szFileName,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

for (dwLoop=0;dwLoop<10;dwLoop++) {
hWriterTrans = CreateTransaction(NULL, NULL, 0, 0, 0, 0, NULL);
if (hWriterTrans == NULL) {
_tprintf(_T("Could not create transaction\n"));
return EXIT_FAILURE;
}

SetCurrentTransaction(hWriterTrans);

hWriterFile = CreateFile(szFileName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

WriteRandomWord(hWriterFile);
CloseHandle(hWriterFile);
CommitTransaction(hWriterTrans);
CloseHandle(hWriterTrans);
SetCurrentTransaction(NULL);
DisplayFileContents(hReaderFile, _T("transacted"));
DisplayFileContents(hNonTransReaderFile, _T("non-transacted"));
}
CloseHandle(hReaderFile);
CloseHandle(hNonTransReaderFile);
RollbackTransaction(hReaderTrans);
CloseHandle(hReaderTrans);
SetCurrentTransaction(NULL);
DeleteFile(szFileName);

return EXIT_SUCCESS;
}

Take note of a few things in this code: firstly, it has no error checking.  That's due to space constraints, not the absence of failure conditions.  I'm not advocating zero error checking in code.

More to the point, note that the reader handle is opened once, and closed once.  It remains open the entire time.  Also note that the writing transaction commits, to make its changes visible to other users.  To make the illustration clearer, I've added a traditional, non-transacted reader handle into the mix.  Both readers, and sometimes a writer, are open at the same time.

Here is some sample output from this application:
Wrote "cat" to file
Wrote "rabbit" to file
Read "cat" from file (transacted)
Read "rabbit" from file (non-transacted)
Wrote "mouse" to file
Read "cat" from file (transacted)
Read "mouse" from file (non-transacted)
Wrote "cat" to file
Read "cat" from file (transacted)
Read "cat" from file (non-transacted)
Wrote "cat" to file
Read "cat" from file (transacted)
Read "cat" from file (non-transacted)
Wrote "bird" to file
Read "cat" from file (transacted)
Read "bird" from file (non-transacted)
Wrote "bird" to file
Read "cat" from file (transacted)
Read "bird" from file (non-transacted)
Wrote "donkey" to file
Read "cat" from file (transacted)
Read "donkey" from file (non-transacted)
Wrote "cat" to file
Read "cat" from file (transacted)
Read "cat" from file (non-transacted)
Wrote "dog" to file
Read "cat" from file (transacted)
Read "dog" from file (non-transacted)
Wrote "chicken" to file
Read "cat" from file (transacted)
Read "chicken" from file (non-transacted)

While the view from the non-transacted reader handle changes with each modification, the transacted reader handle remains frozen at that point in time.  To get an updated view, a transacted reader simply needs to close the existing handle and open the file again.