![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
Applications
Intermediate
License: The GNU General Public License (GPL)
WPF Book ReaderBy chameauA WPF book reader for cbz/cbr files |
C#, .NET (.NET 3.5), WPF, WinForms, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
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.
The application (in my opinion) has to fulfill the following:


This project is very simple. As you can see in the class diagram below, it contains three forms and a few classes.
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;
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);
}
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.
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".
That was the other major point because I put myself in a hurry when I saw the performance :-) but it works.
Many thanks to SevenZip which allows me to uncompress in memory.
I hope you will enjoy it as much as I do. If you get any solution for the problems I faced, please forward...
ZipBook and ZipPage useless. So, all the file reading is done through SevenZip MagnifyGlass, PageViewer GridSplitterExpander that I developed for NPerf at http://nperf.codeplex.com/ | You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 20 Nov 2009 Editor: Deeksha Shenoy |
Copyright 2009 by chameau Everything else Copyright © CodeProject, 1999-2009 Web10 | Advertise on the Code Project |