Click here to Skip to main content
Licence GPL3
First Posted 30 Jun 2009
Views 37,825
Downloads 1,737
Bookmarked 88 times

WPF Book Reader

By Guillaume Waser | 4 Dec 2011
A WPF book reader for cbz/cbr files

1

2
3 votes, 25.0%
3

4
9 votes, 75.0%
5
4.17/5 - 12 votes
μ 4.17, σa 1.58 [?]

New!

With the "success" of the first project and some spare time, I started a complete 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 another ebook format. 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:

  • Better user interface and design: Ribbon...
  • Multiple format support and conversion: Images, PDF, XPS, CBR/RAR, CBZ/ZIP, ePUB...
  • Dynamic books format: CBZD, a completed 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
  • External devices support and a Windows Phone 7 application

Check out the new publication: CodeProject article

CBR

Introduction

Why this project? There are so many e-book readers already available, but none of them suited my requirements. They unzip pages on the disk, or hang between pages, or do not manage the library, etc.

Background

I decided to use this project to experiment more on WPF, and its graphic capacity fits the needs and helps fulfill the missing functionalities. It's more an experiment than anything else; the solution is clean but not perfect.

Functionalities

The application (in my opinion) has to fulfill the following:

  • Manage the book library, by parsing a specified folder, displaying the cover pages.
  • Reading a book, placing the bookmark on a current page, and restoring it.
  • Saving all for further reading.
  • Being quick because of zipped content access, implement image cache management.
  • Once a book is open, display the pages inside and the folder structure.

BookReader1_new.jpg

Example: The main interface

BookReader2_new.jpg

Example: The options dialog

The Code

This project is very simple. As you can see in the class diagram below, it contains three forms and a few classes.

The Class Diagram

Click to enlarge image

The main class is Catalog. It holds a collection of IBooks (mapped to RarBook) which contains an IBookItem (mapped to RarPage). The catalog is in charge of parsing the book directory for all cbz or cbr files, and load/save itself (book name, bookmark, cover) in two separate binary files.

The loading algorithm is the following, and the main point is the usage of a thread to load the covers and parse the folder. The first load will be a bit long, but the generation of the covers will be threaded too. We will see the details later.

private void Load()
{
    try
    {
        string bin = 
          System.Reflection.Assembly.GetExecutingAssembly().
            Location.Replace(".exe", ".bin");
        if (File.Exists(bin))
        {
            //load the book name and bookmark
            if (LoadBooks(bin))
            {
                bin = 
                  System.Reflection.Assembly.GetExecutingAssembly().
                    Location.Replace(".exe", ".bin2");
                if (File.Exists(bin))
                {
                    // load the cover images
                    Thread t = new Thread(
                      new ParameterizedThreadStart(LoadCovers));
                    t.IsBackground = true;
                    t.Priority = ThreadPriority.BelowNormal;
                    t.Start(bin);
                }
            }

            //then refresh the book
            // ????
        }
        else //binary files does not exist, parse the directory
        {
            Thread t = new Thread(new 
                ParameterizedThreadStart(ParseDirectory));
            t.IsBackground = true;
            t.Priority = ThreadPriority.BelowNormal;
            t.Start(_bookPath);
        }
    }
    catch (Exception err)
    {
        ExceptionManagement.Manage(err, true);
    }
}

WPF brings the big advantage of advanced binding, and I use the ObservableCollection so I can notify later that some properties have changed, like the cover images which are loaded after the collection and the binding.

private ObservableCollection<ibook> _Books = 
        new ObservableCollection<ibook>();
public ObservableCollection<ibook> Books
{
  get { return _Books; }
  set { _Books = value; }
}

Once I load the book, I start a new thread to get the covers, and the UI will be updated with each Dispatcher=>Delegate - obligation, because the bitmap created here will be used on the UI thread.

public void LoadCovers(object fileName)
{
    IFormatter formatter = new BinaryFormatter();
    Stream streamBin = new FileStream((string)fileName,
        FileMode.Open,
        FileAccess.Read,
        FileShare.None);

    try
    {
        //book count
        int count = (int)formatter.Deserialize(streamBin);

        for (int i = 0; i < count; i++)
        {
            string filePath = (string)formatter.Deserialize(streamBin);

			MemoryStream coverStream = 
			  (MemoryStream)formatter.Deserialize(streamBin);

            foreach (IBook book in this._Books)
            {
                if (book.FilePath == filePath)
                {
                    MemoryStream stream2 = new MemoryStream();
                    coverStream.WriteTo(stream2);
                    coverStream.Flush();
                    coverStream.Close();

                    stream2.Position = 0;

                    Application.Current.Dispatcher.Invoke(
                       DispatcherPriority.Normal, (ThreadStart)delegate
                    {
                        BitmapImage myImage = new BitmapImage();
                        myImage.BeginInit();
                        myImage.StreamSource = stream2;
                        myImage.DecodePixelWidth = 70;
                        myImage.EndInit();

                        book.Cover = myImage;
                    });
                    coverStream = null;
                    stream2 = null;
                }
            }
        }
    }

When it's the first loading, I parse the directories, and construct the book with the second parameter set to true to generate its cover in a thread. See the constructor and the GenerateCover function.

foreach (FileInfo file in directory.GetFiles("*.*"))
                {
                    if (Properties.Settings.Default.BookFilter.Contains
						(file.Extension.ToUpper()))
                    {
                        if( !BookExist( file.FullName ) )
                            Application.Current.Dispatcher.Invoke
			    (DispatcherPriority.Background, (ThreadStart)delegate
                            {
                                IBook bk = (IBook)new RarBook(file.FullName, true);
                                bk.Size = file.Length;
                                Books.Add(bk);
                                this.IsChanged = true;
                            });
                    }
                }

The common functions of books are in the BaseBook class. The RarBook only contain specific code due to the wrapper used to read the files. Another important class is ImageCache which holds a collection of Bitmaps as specified in the options dialog (number and time). It's woken up by a timer in the main window.

private DispatcherTimer _TimerClock;

Debug Form

It will be displayed only if you activate the "debug checkbox" in the Options dialog. Otherwise, exceptions are logged into a ".log" file in the application directory. Always remember that everything that has something to do with the UI must be called on the same thread.

catch (Exception err)
{
    Application.Current.Dispatcher.Invoke(
       DispatcherPriority.Normal, (ThreadStart)delegate
    {
        ExceptionManagement.Manage(err, true);
    });
}

[or without thread]

catch (Exception err)
{
    ExceptionManagement.Manage(err, true);
}

Points of interest

Performances vs. Design

WPF is used as the rendering surface, but no special effects are used because it a pain in terms of performance... even more into a development VPC. I just use a simple theme from the WPF toolkit provided on CodePlex to skin the application. Then, I add threads for cover generation and binary reading.

The Problems

Serializing the Covers

I was unable to reload the saved catalog covers into the binary file. I don't know why but BitmapImage is not serializable... I found a solution by going through two MemoryStreams before creating the BitmapImage. This was the same with the rar stream...I got the message: "header corrupted".

Getting the Thread Working

That was the other major point because I put myself in a hurry when I saw the performance :-) but it works.

Contributors

Many thanks to SevenZip which allows me to uncompress in memory.

Conclusion

I hope you will enjoy it as much as I do. If you get any solution for the problems I faced, please forward...

History

  • v1.0
    • First version
  • v2.0
    • Implements refreshing of the catalog. It's done automatically on loading and from command
    • Removed SharpZipLibrary because it left a lock on the files after the cover generation. It makes ZipBook and ZipPage useless. So, all the file reading is done through SevenZip
    • Removed the rar/zip format options in the dialog and settings
    • Extended the file parsing filter in settings to support all 7Zip formats: ARJ, CAB, CHM, CPIO, DEB, DMG, HFS, ISO, LZH, LZMA, MSI, NSIS, RAR, RPM, UDF, WIM, XAR and Z
    • Added in settings the extraction filter to read only images
  • v2.8
    • Uses the latest version of SevenZipSharp
    • Improves the generation of the covers (tested up to 400 books)
    • Zooms with slider or Left Ctrl + Wheel
    • Loops with Left Shift, Mouse move and Full Screen mode
    • Loads a book external to the current catalog path
    • Changes the toolbar
    • Changes the WPF theme to dark, New icons
    • Adds Fit to width or height functions
    • Creates 2 user controls: MagnifyGlass, PageViewer
    • New function to convert PDF to ZIP with the help of pdftohtml.exe (you have to check by yourself if it has worked !)
    • Integration of my new GridSplitterExpander that I developed for NPerf at http://nperf.codeplex.com/
    • Removed all possible exceptions of v2, this is very stable
  • Version 3.0
    • Added menu in page view
    • Context menu colors - Bad Theme correction
    • Full screen, no reduction of the catalog splitter correction
    • Double-click on comic in the catalog to open it
    • Auto fit in options, will update the scale at each page change
    • New window frame, new commands, new status bar
    • New "delete" command
    • New "mark as read" command, display reading status on covers
    • New "search" command in filename by filtering the covers

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

Guillaume Waser

Architect
A.C.S.
France France

Member


Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
Questionpas mal mais très lent PinmemberMember 34035606:10 4 Nov '11  
AnswerRe: pas mal mais très lent PinmemberGuillaume Waser11:45 4 Nov '11  
AnswerRe: pas mal mais très lent PinmemberGuillaume Waser2:07 5 Dec '11  
GeneralDiagram Editor Pinmemberelgaabeb1:06 17 Dec '10  
GeneralRe: Diagram Editor Pinmemberchameau1:20 17 Dec '10  
Questionsupport regular PDF text files? Pinmemberpuremood_200212:02 2 Feb '10  
AnswerRe: support regular PDF text files? Pinmemberchameau22:35 2 Feb '10  
AnswerRe: support regular PDF text files? PinmemberTarun.K.S5:49 7 Jan '11  
GeneralRe: support regular PDF text files? Pinmemberchameau3:23 9 Jan '11  
GeneralProject is now hosted on Codeplex Pinmemberchameau21:36 1 Sep '09  
GeneralI did a comic reader too, please, take a look Pinmemberquicoli1:53 1 Jul '09  
GeneralZero Length PinmemberCreF22:50 30 Jun '09  
GeneralRe: Zero Length Pinmemberchameau23:44 30 Jun '09  
GeneralDownload Files Are FUBAR Pinmembersam.hill17:11 30 Jun '09  
GeneralRe: Download Files Are FUBAR Pinmemberthund3rstruck17:27 30 Jun '09  
GeneralRe: Download Files Are FUBAR Pinmembersam.hill19:19 30 Jun '09  
Just noticed the subtitle: A WPF Book reader for cbz/cbr files
http://en.wikipedia.org/wiki/Comic_Book_Archive_file
GeneralRe: Download Files Are FUBAR Pinmemberchameau23:46 30 Jun '09  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120210.1 | Last Updated 4 Dec 2011
Article Copyright 2009 by Guillaume Waser
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid