Introduction

With the "success" of the first project and some spare time, I start a completely new version hosted on the same CodePlex project.

I decided to rewrite it to explore a bit more about the MVVM pattern and extend it to other ebook
formats. I am also interested in 7 phone development,
so I took the idea of dynamic books that I have seen on an iPhone application. The roadmap includes:

Dynamic books format: CBZD, a complete zip format with additional XML files associated to pages that contain frame descriptions. CBR contains a designer
to "edit" books to add frames on the different page zone with order and timing. This allows automatic reading usable on desktop, but mainly for phone application.

Screenshots

The Code : Schemas and Principles

Has the project becomes a bit more complicated than the original and as it is always in beta stage, I split the article into several "high level"
parts: core classes, MVVM pattern classes and coding tips... until the architecture becomes stable to give a more complete description.

Model Core Classes

The model core classes illustrated below contain a Catalog class that represent a library. It is mapped to the ExplorerViewModel
and managed by a corresponding CatalogService. All books are represented by a Book entity, that extend (when possible) to Pages.
Zone class is dedicated to my new format for dynamic books. When extra data is needed, it is stored in the Tag property
of the Book (like for ePUB format).

Diagram: Model core classes

Service Core Classes

Below are the main service classes. FileService and FileExtension manage the data we need to associate a file (based on the extension) to data
like the dialog filter, but also the associated model, service and viewmodel. This will be used by the BookServiceFactory to create the corresponding
ViewModel that will also create the corresponding service using reflection. BookInfoService is a separated class that only manages the load/save
operation on Book binary internal structure. CatalogService is the class that manages the library.

Update... !

Fileservice and FileInfo classes has been
replaced by DocumentFactory, DocumentInfo and
DocumentType that link to the Core model.

Note: About the internal file structure...C.B.R. does not use any database. It is a single binary file that you can store where you want which
centralizes the catalog information (path to the book files). Underneath, in the current application folder, I store book information like rating or cover into a separate
bin file, so that they can be shared among several libraries. You can lose you library, if the book binary is there, then nothing lost!

Diagram: Service core classes

Workspace Core Classes

These classes manage all data for the working environment. The singleton class WorkspaceService stores the settings in a WorkspaceInfo
class that contains program options (that the user can change in the backstage), two lists of RecentFileInfo for book and library recent file list and
the supported device list as a DeviceInfo collection. The settings class is serialized through the program properties. It is loaded a program start
and automatically saved on closing.

Update... !

ExtendedInfo contains the data for the extended
options. FeedInfo and FeedItemInfo the data for
the backstage view FeedInfoView. ProxyInfo class
has been added to manage any special internet settings.

Diagram: Workspace core classes

Convert Core Classes

My wish was to not include any "third party" that is not pure WPF... so I had to find a way to convert from unsupported format (like PDF) to the one I choose to support.

Based on the convert panel in the backstage, we have a ContractParameter class that groups all chosen options. It is given
to the BookFileConverter class that supports threading through a BackgroundWorker (as it uses a report progress in the user interface).
Then the conversion process goes through two interfaces that are based on input and output formats: IReaderContract and IWriterContract.
Note that the conversion process does not manage multiple input/output formats.

The BookFileConverter is first calling the reader to extract the useful data that goes through the writer. The package includes ImageFileReader,
RARImageReader, PDFImageReader, XPSImageReader for reading images in folder, Rar/Zip, PDF or XPS files (will be soon extended to ePUB).
We can write to Image files, a XPS or CBZ/ZIP file through ImageFileWriter, XPSImageWriter and ZIPWriter classes.

Diagram: Converter core classes

The standard way to transfer data from reader to writer is an array of bytes that represent images, but I take a shortcut for conversion like RAR to images
where the reader is extracting images from the RAR directly to a folder without the need of a writer. Below is the transfer table mode used for the conversion:

Source

Reader

Transfer mode

Writer

Destination

Images

x

x

x

Images

x

direct

compress folder

CBZ/ZIP

extract to mem

mem

write mem

XPS

PDF

extract to mem

mem

write files

Images

extract to mem

mem

write filescompress folder

CBZ/ZIP

extract to mem

mem

write mem

XPS

CBR/RAR

extract to folder

direct

x

Images

extract to folder

to temp

compress folder

CBZ/ZIP

extract to mem

mem

write mem

XPS

CBZ/ZIP

extract to folder

direct

x

Images

x

x

x

CBZ/ZIP

extract to mem

mem

write mem

XPS

XPS

extract to mem

mem

write files

Images

extract to mem

mem

write filescompress folder

CBZ/ZIP

x

x

x

XPS

ePUB

x

x

x

Images

x

x

x

CBZ/ZIP

extract to mem

mem

write mem

XPS

MVVM Related Application Classes

This is the most complicated part... below are the classes from the application that participate in the pattern. I am not going to describe the helper
classes like the ViewModelBase, the Mediator or the Messenger. It is cut in 5 parts. The views: main user interface,
the backstage and out of the pattern classes. The viewmodels: main user interface, the backstage and some additional classes. Let's go through them.

Diagram: MVVM application classes

View Layer

The main user interface is composed by a MainView (the whole window), an ExplorerView (the library explorer on the left) and some ribbon
content (different book format and device views) that are hosted by a TabConbtrol (with no user interface displayed) binded to a collection of ViewModel.

InfoView, OptionsView, RecentFileView and DeviceConfigView are panels displayed in the ribbon backstage.

BookView (for comics), XpsBookView (for XPS documents) and ePUBBookView (for web like viewer) are hosted in a TabControl in the main window part

USBDeviceView and later PhoneDeviceView are also hosted in a TabControl in the main window part and but displayed in a contextual manner by
the ribbon group "Devices"

The ConvertView and SimulateView are actually out of the pattern because of the BackgroundWorder/thread or a beta stage code.

ViewModel Layer

We have exactly the same for the view model layer, plus a BookViewModelBase that centralizes all common book functions and a DeviceViewModelBase that do
the same for devices. Note that they are all inherited from ViewModelBase (that host a Model in a Data property) which comes from
a BindableObject that implements the INotifyPropertyChanged interface for binding and MVVM support.

On the right side, we also have TreeviewItemViewModel class derivated into SysElementViewModel (that represents a file system element)
and becomes specialized with SysDriveViewModel, SysDirectoryViewModel and SysFileViewModel. They are used in the
USBDeviceViewModel which contains a treeview and a listview control to display the device content.

Update... !

The model has been completed with new classes like FeedView/Model
to display OPDS feeds....

The main changes are due to the AvalonDock integration that brings
PaneViewModel derivated into ToolViewModel (that represents
documents and toolbox view models).

Exchange: How to Communicate between Layers

I will try to publish a graph about the exchange later. The way is MVVM based: Views call Commands on ViewModel,
ViewModel communicate together with Mediator, ViewModel communicate with Views through Messages.
I avoid a maximum of control event handling but sometimes it is not possible or too complicated.

Localization engine

This new part is based on the very nice article from CodeProject, WPF Localization using resx - (so I am not
going to explain it...). But the solution was not satisfying me, because I don't like resx (too much work for the
developer
when it comes to update/copy/paste and rebuild new resources) and I consider C.B.R. being too small for having so many locale assemblies...There is also a
big need for an editor so that any user can manage localization or open the resource storage to database for example.

Here under are the core classes to support the localization in the XAML. I just made some small changes to existing code, like a singleton pattern
on the CultureManager or the LocalizationExtension renamed has got a ResModul property that can identify a resx file or a
xml/bin file. To complete the core model represented by the CultureManager (single public class) I have added some management
functions like GetAvailableCultures, GetAvailableModules, GetModuleResource,
SaveResources or CreateCulture: this will answer the need for an editor. Note that finally, the resx method is very restrictive and cannot cover all this overload...

I also add a CultureItem class that extend the CultureInfo. This will be mapped to a view class to fill the language gallery in the ribbon.

Diagram: Localization core classes

To extend the resx model, I choose to add a provider pattern to the CultureManager. IResourceProvider is implemented
directly by the ResxProvider class (existing one that does not manage cultures) and FileProviderBase that derive into
BinProvider and XmlProvider (the solution I choose because file are human readable). The data model is very
simple and will be serialize or deserialized directly to the resource files. It is composed of LocalizationFile that group
all LocalizationDictionnary by culture code, each dictionary contains a collection of LocalizationItem that represent a resource
set.
See the programming extract to get more details about it's implementation and usage.

Diagram: Localization provider classes

ePUB and OPDS models

I had a complete rework on the previous model (that cannot extend to
conversion or writing functionnalities). ePubManager is the
parsing class and below are the model classes. This one is much better, even
if ePUB has got a lot of specification versions...it must fit for nearly all
reading. Note that the viewer is based on IE (need to register some key for
better emulation mode - done by the setup - you will find them in the
solution). In case of bad encoding detection, you can use the view context
menu.

OPDS is a special feed format dedicated to electronical publication. CBR
include a feed management view and a browser view that allow to navigate the
Rss and download books. Below is the model that suit to my needs (not the
OPDS specification) and the OpdsManager is the main class that
parse the feed to create the objects for View and ViewModels

The Code: Programming Extracts

In the next chapter, I would like to point out the problems that I faced and the solutions as well the nice part of code that seems interesting to share.

Conversion: Extract PDF Images

I dig into a lots of solutions - read and parse PDF, image extraction tools - before hazard brings me to a code file on the internet.
From some test file, I found that iTextSharp has a listener that you can plug onto the parser to get dedicated events and data during the parsing.
Implement a class with the IRenderListener interface and the method RenderImage will be called on each image so you can get the bytes back.

Give it to the ProcessContent method while processing PDF pages through the PdFReader and PdfReaderContentParser classes.
Note you can get text events too.

New: Merge images by page (for PDF)

I have noticed in the past with some tests that PDF image extraction
sometimes results in hundred of images because each page is cutted in
several parts. I don't know where it comes from, but the solution is there. I
have added a checkbox option in the conversion panel to merge images in case
of a number difference with the page count.

As my conversion engine is based on a reader (or extractor), a pipe with
the image bytes, then a writer to finalize the choosen destination format, i
put in the middle a ImageJoiner class that combine the original
bytes into real pages.

I first modify the ImageListener to be sure that page index
is allways included in the image file name. When I detect a difference in
the Read method of PDFImageReader class, I give
the array to the ImageJoiner class and take his array as the
result

The ImageJoiner is quite simple and the trick is in the
second Merge method : it takes indexes to process as
parameters. I first create a list of the corresponding BitmapImage
and compute the size of the destination bitmap. Then create a
RenderTargetBitmap that match the destination. With a
DrawingVisual, I open is context and draw all the bitmap in it. Then
I ask the
RenderTargetBitmap to draw the visual and after that extract
the bytes to the new array...here it is !

XPS: Fit Image on Fixed Page

How to fit images on XPS document pages? Seems like quite an easy question, but I had to test a lot before finding my Achilles heel... Do not mix pixel image size and WPF units!

Have a look at the class XPSHelper in CBR.Core. The method WriteDocument has all the mechanics to write the array
of images into a fixed document. It calls a WritePageContent method used to write the XAML page structure. Here, I specify the viewbox to fit the image
into a A4 formatted page.

Dynamic Properties

CBR has no database to store information about the books, so I had to find a way to add properties dynamically to my objects so that they can be extended.
Let's says you manage all your books by series... it is not an existing property of my Book model object. So you are not able to define the data,
nor to group or to sort them. Another requisite was that they need to be properties to work and take advantage of GroupDescriptions and SortDescription
on an ObservableCollection of book. See below in "context menu" chapter.

.NET 4 brings a fantastic but not well known feature: dynamic or Expando objects. First, add a new property to our model like below based on them.
What you will add to it will be seen like a property by WPF and reflection, but in code we generally manipulate it like a Dictionary.

After defining the backstage panel that allows to manage the dynamic property reference list, I am able to use it and refresh
the Book model with a synchronized method. I am looping on both dictionaries to update the book properties.

Filling the backstage file information panel was hard, because WPF sees it as a dictionary when binding as a list of PropertyName/PropertyValue.
So I had to convert it to a collection of KeyValueProperty view-model class (derived from BindableObject) that has PropertyChanged event
so I can get the value back in the dynamic property.

I let you go through the code in InfoViewModel and KeyValueProperty but also in ExplorerViewModel and PropertyViewModel
which uses dynamic to fill the sort and group dropdown button menus.

Context Menu Binding

Everywhere on internet, it is pointed out that the context menu does not share the same logical tree view... and it's easy to get the parent context... But in my case,
for the context menus in the explorer, I had extra need:

Use the commands from the MainViewModel because commands like Read/Bookmark or Delete were already implemented

Fill sort and group dropdown menus with properties from fixed and dynamic and all calling the same command

Context menus: Sort and book commands

The Book Contextual Menu

As it was not easy to get up in the logical tree view to get the parent main window and to bind to these commands, I choose to implement an intermediary command in
my ExplorerViewModel that will forward it to the MainViewModel. Menu items are defined in the XAML like below:

It was then easy to implement a command in the ExplorerViewModel that uses the Mediator to inform other views that a command occurs through
the CommandContext class that contains the CommandName as a string and the Book item as a parameter.

Sort and Group Menus

I faced the same problems with sort and group menu, plus I had to find a way to populate them with Book model class properties, some were dynamic ones.
To fill it up, I define two properties in the ExplorerViewModel like below that gives me the menu item lists. I also use reflection to get the properties
out of the book... but I quickly found that I need a view model for menu items because I need more than just a property name to continue.

Note that in the ExplorerViewModel, we subscribe to both messages and after that, we can modify the SortDescription
and GroupDescription on our collection. Group command was a bit special, because it needs to forward the changes to the View to remove the GroupStyle
if needed, that's why I call the Messenger.

Update... !

Based on the same principe, i have implemented an automatic property
discovering based on an Attribute class that allow me to define
which property can be sorted, grouped along with the localize code...below
is the schema : The PropertyHelper class provides me directly
with the PropertyViewModel collection needed to fill up
the menus.

I extend my book model like below to define that this property is not
groupable, but sortable and that the localize key is
Core.Properties.FilePath. Advantage is that my menus are now automatically
updated if i changes properties or the language !

Implement Drag and Drop

On the application, from outside

You can drop a comic file or library to the application to open it directly. Easy to implement, add the drop event handler, check if we support the file extension
and execute open command in the MainViewModel. Do not forget to allow drop on you main window through the AllowDrop property.

Explorer and Device View

When drag & drop comes from the internal controls, so we have to manage MouseDown, MouseMove events and initiate a drag & drop operation
on each possible source control. To make it easier, I develop a DragHelper that you plug on each control that needs to be a source - so you don't care anymore
about mouse handling and item preview. The drop destination needs to be managed as usual depending your needs. The DragHelper attaches itself
to the control given in the constructor.

If we meet the drag distances, we capture the mouse in the application, and send a StartDragEvent to the attached control - so it can prepare data and callback
DoDragDrop below. Shortly, it allows drop, attaches itself to the main window DragOver event, creates an adorner that contains the source element
preview, and then calls the real DoDragDrop function. When it is released, we clean up all.

How to Use It

Then, create the StartDragEventHandler that will be called by the helper when a drag & drop operation is validated.
Find your control item and create the data you need to manage the drop. Call the DragHelper.DoDragDrop function to validate and continue the operation.

Implement USB and Device Detection

WMI Event Watcher

WMI as Windows Management Instrumentation is a nice (but complicated feature) that I discover when a friend of mine says "what about dragging my book directly
to my reader ?"...ha ha so simple it is? and what about e-book reader supported formats? This drives me to two new features: DeviceConfigView in the backstage
that holds devices/manufacturers and supported formats as a referential, and the new USBDeviceView to display USB drives that can be associated to devices.

The idea comes to simply detect USB peripherals for most e-book reader (and later 7 phones). After hours of internet digging and test, no way to get it straight because WMI
provides a lot of information but none in the same structure. Here is the solution I implement by writing a helper class that first search for existing devices at the connection
time and then watch over usb events.

WMI provides helpful classes and support a query language called WQL: ObjectQuery - to define a query, ManagementObjectSearcher - like an Execute
and return collection of ManagementBaseObject - the result that contains asked information.

First, I need two ManagementEventWatcher to get WMI events onto the Win32_USBControllerDevice structure for __InstanceCreationEvent
and __InstanceDeletionEvent. Note that the WITHIN clause is like a timer and allow pooling events.

To handle the device removal, I implemented this method: get the instance fired by event, extract the PNP device id and if we remove the device from our collection,
fire an internal event targeting the application.

For existing devices, I go from Win32_DiskDrive through Win32_DiskDriveToDiskPartition through Win32_LogicalDiskToPartition
to reach Win32_LogicalDisk. This makes a cascade of query and loop like below:

In the event handler, as it is called on another thread, we use the application dispatcher to call the commands to add or remove a device. This will update/create
the device view. Do not forget to close the watcher in the OnClose event of the main window.

Localization engine

How it works?

To complete the core model explanation, everything happens in the GetValue method of the LocalizationExtension. I extend
this existing method to ask the configured provider. ConvertValue is the same, GetObject has been overridden in all
provider to search the asked key. More changes occurs in the GetDefaultValue, because the provider will create the file, dictionary and resource item.

Use it in the Application

Configure the provider you want through settings LocalizeProvider and LocalizeFolder, then just replace the actual content with something like
below: ResModul is the destination file, Key is the unique resource identifier in the file and DefaultValue is the displayed text.
There is no need to add an extra namespace because the resource extension is associated with standard XAML namespace.

Note that a localize version will be created and displayed in the designer and the program until you work on it. This helps identifying
quickly what you have missed. At the runtime, all resources for the running culture a going to be added automatically to the files, no need to create one!

Implement Language Choice and Editor

I design a dropdown button associated with a Gallery that display the CultureInfoItem provided by the GetAvailableCulture method of
CultureManager. They are mapped to a LanguageMenuItemViewModel which derive from MenuItemViewModel
that is the generic class for all CBR menus items in the MVVM pattern.

The editor is a very simple tool window. In the source pane, you choose the language and module you want to work on. Click the Select button to use
the language in the application. Note that the window is amodal, so you can continue to use C.B.R. to check your work. The grid display the selected
resources: Key, Default is the original text from development, Translated is the one you need to work on and that will be displayed.

In the New pane, you can select a culture that does not exist actually, add an icon file name and then click Create button to add a new language in
"memory". Use the Save button to commit your changes to the files.

Update 1... !

This bloomy engine was not thinked for localizing from code ! Before the
tabs for multi-document support, i had no resource out of XAML. But when it
comes to messages or tab titles like "home" and "devices"...MarkupExtension
fails because it is for XAML only ! So i found a
turn arround solution. For messages to be displayed in message boxes, just
ask the resource. Tabs need to be updated via the ViewModel when the
language is changed.

I wrote a new function in CultureManager to ask a
localization based on the same principle...GetLocalization(string
modul, string key, string defaultValue) will go through the provider to find
if an existing LocalizationExtension or LocalizationItem
exist. If not it will manage everything like the original engine do.

To put it in a ViewModel like my "Home" tab, I add to
subscribe to the CultureManager event when culture change...and
unsubscribe in the Dispose method. That it's....hours and hours
to find an elegant solution !

Update 2... !

Cultures are now identified by the IETF code from the net class
CultureInfo. It combines language and country code like fr-FR :
french in FRANCE. fr-CA is french in CANADA. CultureItem
class has been removed and the icon is now named based on IETF code too.

Update 3... !

The localize editor has been improved and display now an additionnal
column about unused resources...Then it has been completed with WPF
spellcheck. Note that you will have to install net language packs ! and by
the way, fluent gallery are now displaying correctly with the manage button
at the bottom.

Extend the engine

If you want to extend it with a database provider for example, write your model and provider and feel free to provide us feedback ! I need it for another project " src="http://www.codeproject.com/script/Forums/Images/smiley_smile.gif" />

Note that CBR is not fully localized on beta items and tooltips...

3D and 2 pages flip viewer

For a long time, I had a look to Mistu Futura "2 pages book", but I was
not sure about the performance of an ItemControl binded to more than 50
bitmap pages...and recently I discover after short tests that binding only
occurs when turning the pages! So I decided to go further and adapt this
control to my needs.
Original code is not very well documented and
content is based on "full fit" and xaml user controls, so no scaling
troubles...so I had to found a solution where bitmaps allways fit the pages
the best they can. I also need it to be in a scrollview and answers my
actual book commands.

How it works ?

First, take all original code into a folder, change the class names to be
more clear, group, and transform the user control into a control. This lead us
to LinearGradiantHelper, PageParameters,
CornerOrigin and PageStatus (no changes) - and
TwoPageBook (the new main control) with TripleSheet control
that represent the 3 page sides on the right or left part of the book. When
animating a page we can see the side in front of us, the one behind (when it
rolls the page) and the front side on the page before (or after).

I modify the control to have his default style and wrap it into a
scrollviewer like the template below. I remove a few things I do not need
like zoom on current page...

Then, I complete the class OnApplyTemplate method to grap my
parts to be sure _Content will be scaled and the
_ScrollContainer will be used later to be able to fit into the document view.
I also add Scale, FitMode and a
CurrentPageIndex because original code manage a sheet index (index
divided by 2). Then I change the TripleSheet template to have
the content to fit and colors to fill the gap when bitmap cannot shrink
correctly. Note that the Fit method make an entorse to the
genericity of the control by using the Page model class...that
was the only way to find the content size ! - src="http://www.codeproject.com/script/Forums/Images/smiley_frown.gif" /> /p>

For performance reasons, I split my book view into two separate classes :
simple page view and 2 pages view. OOppps ! I have to manage the swap
between the 2 modes and passing parameters ! As the book is loaded, I just
need the current page index. The book views communicate through the main
view that receive a SwapTwoPageView message and convert the
view into the desired one like illustrated below.

Note that many of the books I found are not really compliant with this
view because of page ratio that are never the same or contains double scan.
Don't be surprised, but as I do not process the images and read them in
memory, I can't make any adjustments.

Contributors

Many thanks to SevenZip (which allows me to uncompress in memory), Fluent Ribbon
for this excellent control library and also for all the internet contributors I read along this project.

Your feedback

As the last version was pretty long to come out and 0.6 was downloaded around 60 000
times, I think you are perhaps interested to be able to give some
more precise feed back, I put all planned features in the "Issue tracker" page of the project.
So now you are able to give me feedback about your needs priority by voting or create new items. This will help me to plan next version content.

Deliverables - Installation notes

With version 0.7 and installer, always remove the previous version or some files are not going to be replaced and big troubles will occurs. I will work on a better solution.

Hi,
No, version 0.7 is the same, from 25 sept. but the article has been updated because of broken links in the summary...which does not work either with all navigators. Next release is on the way, but i have a big bug which is not solved on beta code...
bye.