Introduction
Thumbnail
views
is a nice
feature of
Windows
Explorer.
However,
little is
known about
how to
create
shell
thumbnail
extensions
for custom
documents. I
was
developing a
medical
image
visualisation
software
and wanted
this feature
for the
DICOM
(medical)
images that
the software
can load.
After
searching
the Web i
finally
found a
relevant
article in
the MSDN
magazine:
More Windows
2000 UI
Goodies:
Extending
Explorer
Views by
Customizing
Hypertext
Template
Files.
The
articles
covers this
topic and
includes a
simple image
extractor
for icon
files.
After
creating my
DICOM image
extractor (i
can give it
upon
request) i
created also
an image
extractor
shell
extension
for Scribble
(MFC
tutorial)
documents
and
specifically
for Scribble
Step 5. I
tried to
write the
code in an
objected
oriented way
which
promotes
reuse (i am
a "fun" of
the OO
"guru"
Paul
Dilascia,
author of
the MSDN
mag).
Finally, i
converted
the scribble
image
extractor
project to a
Custom
AppWizard so
that you can
easily
generate the
sceleton
code of an
image
extractor
for your
MFC
documents.
The figure
above shows
the
thumbnail
view
of a
folder
containing a
medical file
and also my
precious
scribbles :)
CScribbleExtractor
COM object
The
MFC-based
thumbnail
extension
for Scribble
(ThumbScb
project) has
been
created
as an
MFC
regular DLL.
After the
AppWizard i
added an ATL
object to
the project.
The object
was coded to
implement
the two
interfaces
needed:
IPersistFile
to know
about the
file
currently
selected in
the shell,
and
IExtractImage2
(derived
from
IExtractImage)
to access
the document
and return a
bitmap that
renders its
content.
Collapse
Copy
Code
class ATL_NO_VTABLE CScribbleExtractor :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CScribbleExtractor, &CLSID_ScribbleExtractor>,
public IPersistFile,
public IExtractImage2
{
public: ...
HRESULT CScribbleExtractor::Extract(HBITMAP* phBmpThumbnail)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
theApp.LoadDoc(m_szFile);
m_hPreview = theApp.CreateThumbnail(m_bmSize);
*phBmpThumbnail = m_hPreview;
return NOERROR;
}
...
CExtractImageApp
generic
application
class
As you can
see, the COM
object
delegates
document
loading and
thumbnail
generation
to the main
application
object. This
is
convenient
because the
CWinApp-derived
class can
determine
the
supported
document
types and
create
corresponding
document
objects via
the document
manager
mechanism.
In order to
reuse as
much code as
possible i
implemented
the
LoadDoc
and
CreateThumbnail
functions in
a generic
CWinApp-derived
class named
CExtractImageApp
.
The first is
implemented
with generic
MFC code.
The helper
function
CanOpenDocument
returns an
appropriate
document
template
that can
create a
document
object to
serve the
file. The
document
object is
dynamically
created and
then the
content of
the file is
loaded from
disk.
Loading code
is mainly
copy-pasted
from the
MFC
OnOpenDocument
function.
Collapse
Copy
Code
BOOL CExtractImageApp::LoadDoc(LPCTSTR lpFileName)
{
ASSERT(lpFileName!=NULL);
CString sFileName = lpFileName;
CDocTemplate* pDocTemplate = CanOpenDocument(sFileName); if (pDocTemplate) {
if(!m_pDoc) {
m_pDoc = pDocTemplate->CreateNewDocument();
m_pDoc->m_bAutoDelete = TRUE;
}
if (m_pDoc)
{
CFileException fe;
CFile* pFile = m_pDoc->GetFile(sFileName, CFile::modeRead, &fe);
if (pFile == NULL)
return FALSE;
m_pDoc->DeleteContents();
CArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete);
loadArchive.m_pDocument = m_pDoc;
loadArchive.m_bForceFlat = FALSE;
try
{
if (pFile->GetLength() != 0)
m_pDoc->Serialize(loadArchive); loadArchive.Close();
m_pDoc->ReleaseFile(pFile, FALSE);
}
catch(CException *e)
{
m_pDoc->ReleaseFile(pFile, TRUE);
m_pDoc->DeleteContents(); e->Delete();
return FALSE;
}
return TRUE;
}
}
return FALSE;
}
The
CreateThumbnail
function
creates
the
thumbnail
bitmap and
draws
the document
content on
it. It is
essentially
a template
function
because it
calls the
GetDocSize
and
OnDraw
functions
that are
pure virtual
(must be
implemented
by the
derived
class).
Drawing
is done in
isotropic
mapping to
match
document
dimensions
with
thumbnail
dimensions.
The minus
sign in
SetViewportExt
is used to
define a
cartesian
coordinate
system
(y-axis
values
increase
from bottom
to top).
Collapse
Copy
Code
HBITMAP CExtractImageApp::CreateThumbnail(const SIZE bmSize)
{
HBITMAP hThumb; CBitmap bmpThumb;
if(!m_pDoc) return NULL;
CSize bmDocSize = GetDocSize(); CDC memdc;
memdc.CreateCompatibleDC(NULL);
bmpThumb.CreateBitmap(bmSize.cx,bmSize.cy,3,8,NULL);
CBitmap* pOldBm = memdc.SelectObject(&bmpThumb);
memdc.PatBlt(0,0,bmSize.cx,bmSize.cy,WHITENESS);
memdc.SetMapMode(MM_ISOTROPIC);
memdc.SetViewportExt(bmSize.cx,-bmSize.cy);
memdc.SetWindowExt(bmDocSize.cx,bmDocSize.cy);
OnDraw(&memdc); memdc.SelectObject(pOldBm);
hThumb = (HBITMAP)bmpThumb.Detach();
return hThumb;
}
Derived
application
class
In the main
application
class
derived from
CExtractImageApp
,the
above
mentioned
pure virtual
functions
are
implemented
by calling
the
respective
functions of
the already
loaded
document.
Before this,
I have to
downcast the
CDocument
m_pDoc
pointer to a
CScribbleDoc
pointer. If
you have
multiple
document
classes you
can use the
CObject::IsKindOf
function to
find the
kind of
document
class
m_pDoc
points to.
In the
InitInstance
function i
add a
template for
the scribble
document
type.
CMDIChildWnd
and
CView
are MFC
classes,
while
CScribbleDoc
is a
simplified
version of
the original
scribble
document
class. The
only
functions
that
CScribbleDoc
is required
to implement
for this
project are
the
Serialize
function
called
during
document
loading and
the
GetDocSize
and
OnDraw
functions
called
during
thumbnail
creation.
Normally,
OnDraw
function
belongs to
the
CScribbleView
class but
here there
is no need
for doc/view
architecture.
To simplify
implementation
i copied
OnDraw
from
CScribbleView.
In the first
line i
replaced
GetDocument()
with
this
.
Collapse
Copy
Code
BOOL CThumbScbApp::InitInstance()
{
if (!InitATL())
return FALSE;
AddDocTemplate(new CMultiDocTemplate(IDR_SCRIBBTYPE,
RUNTIME_CLASS(CScribbleDoc),RUNTIME_CLASS(CMDIChildWnd), RUNTIME_CLASS(CView)));
return CWinApp::InitInstance();
}
void CThumbScbApp::OnDraw(CDC *pDC)
{
CScribbleDoc *mydoc = (CScribbleDoc *)m_pDoc;
mydoc->OnDraw(pDC);
}
CSize CThumbScbApp::GetDocSize()
{
CScribbleDoc *mydoc = (CScribbleDoc *)m_pDoc;
return mydoc->GetDocSize();
}
void CScribbleDoc::OnDraw(CDC* pDC)
{
CScribbleDoc* pDoc = this; ASSERT_VALID(pDoc);
...
How to debug
Debugging
shell
extensions
is
difficult. I
have created
a COM object
(ThumbExtract
project)
that uses
shell
interfaces
to get an
IExtractImage
for a file
and then
creates a
bitmap. In
the same
folder you
'll find
small VB6
EXE project
called
TestThumbExtract
that
references
the
ThumbExtract
object. An
instance of
this test
application
is shown
below. Click
the button
to select a
file and a
large
thumbnail
for that
file will
appear in
the picture
box. You can
debug your
shell
extension by
setting the
TestThumbExtract.exe
as the
'Executable
for debug
session' in
Project/Settings/Debug.
Thumbnail
Project
Wizard
Since code
changes for
a new
thumbnail
shell
extension
project are
few, i made
a custom
AppWizard (ThumbWiz
project)
based on the
ThumbScb
project. The
compiled
wizard DLL
is named
ThumbWiz.awx
and you must
copy it to "C:\Program
Files\Microsoft
Visual
Studio\Common\MSDev98\Template"
folder.
An entry
named "Thumbnail
Project
Wizard"
appears
then in the
New Project
dialog. The
wizard has a
custom step
which ucancode.netes
your
object's
name (e.g.
Scribble)
and the file
extensions
supported
(if you want
to support
many
extensions
separate
them with
commas). The
names of the
application
class, the
document
class and
the COM
class
created, are
based on the
object name
you give.
Also, for
each file
extension a
new document
template is
created in
InitInstance
.
New GUIDs
are
automatically
generated.
The sceleton
code
includes
TODO
comments
indicating
where to add
the needed
details. In
CThumbWizAppWiz::CustomizeProject
i customize
the project
settings
(specifically
the MIDL
settings)
and also
create a
post-build
step to
register the
object.
Registry
entries
under the
file
extension
keys are
created so
that the
system can
determine
what object
to load for
each file
extension.
Be careful
not to
replace the
entries for
well known
types (like
.jpg)
because no
backup of
registry
entries is
taken.
Conclusion
Creating
thumbnail
shell
extensions
for your
MFC document
types should
be very easy
now (i hope
:). With
this
article, my
thumbnail-PhD
comes to an
end !! Visit
my homepage
for other
free tools
and
programs.