Introduction and Background
This is an attempt at creating an e-Book Reader. Right now, it only reads txt files. I will be adding *.doc and *.rtf files for a more visual effect. It uses a File Splitter
that can split files to a specified range (2000 to 500000). The File Splitter
can be found here at CP.
I re-edited the DLL file to produce nothing but TXT files, as this makes it a lot easier and quicker to read after splitting a much larger file into smaller pieces. It also adds the correct name and part numbers so when displayed in the listbox
, they show up in alphabetical order. (See picture above.)
The eBookReader
will also keep track of your last read place. When you are at the end of the current file part, it automatically advances to the next file part and displays it in the RichTextBox
. If you want to take a break from reading, you can save your position by clicking the menu item Save Position
and when you want to continue, just open the same book and click the menu item Get Position
. It will display where you left off at the top of the book part that you were last reading.
Using the Code
YOU MUST RUN AS ADMINISTRATOR. This is a must as we are creating and reading keys from the Registry
and creating Folders
to store our e-books
in after splitting them.
When the application is first booted, it will create a folder named "eBooks
" in your Documents folder. If it already exists, then we exit the sub
gracefully and continue on with the Form loading
. This is the reason for "Running As Administrator" and later when we write and read values to and from the Registry
.
If you do not have any e-books to read, I am posting a few sites in the Points Of Interest section at the bottom of this page. Here is a FREE e-book site that I use, wwwgutenbergorg, in the menu item E-Books
. When selecting your books to read, make sure to press the txt
format.
You will have to copy and paste, so make sure you have a text editor open (Notepad or Notepad ++), then save it as Name of the file.txt
. This is a little time consuming, but, the books are FREE.
Now that we have 4 or 5 ebooks to read, let's start splitting them by clicking the File Splitter
menu item
, then Split File
, this form appears...
For the source file, enter one of the files you just saved. For a destination, select the folder eBooks
that we created when the form first loaded. Click the split button (btnGo
).
Another folder gets created and named after the file that we are splitting (FileName.txt). The files that get created are (FileName000.txt, FileName001.txt, FileName002.txt) and so on. When the numbers are added on, this is one of the areas in the DLL file that I re-edited, they are added so that they show up in alphabetical order. DLL code here...
do
{
if (nCounter <= 9)
m_fsOut = new FileStream(m_OutDir + "\\" +
Path.GetFileNameWithoutExtension(m_FileName)
+ "00" + nCounter.ToString() + ".txt", FileMode.Create);
else if (nCounter <= 99)
m_fsOut = new FileStream(m_OutDir + "\\" +
Path.GetFileNameWithoutExtension(m_FileName)
+ "0" + nCounter.ToString() + ".txt", FileMode.Create);
else
{
m_fsOut = new FileStream(m_OutDir + "\\" +
Path.GetFileNameWithoutExtension(m_FileName)
+ nCounter.ToString() + ".txt", FileMode.Create);
}
//code continues
As I do not know how to program in C#
yet, this seemed to work very well. The main picture up above shows the results of this code in the ListBox
. I also changed all the ".part"
extensions to ".txt" in both the Splitter Class
and the Merger Class
that reside in the The_FileSplitter_v2 DLL.
In the btnGo_Click
event, we call the fs.BeginSplit()
sub
(below). This, in turn, calls the code that follows (Split() sub)
. 80% of the splitting is done here. The other 20% is in the sub
s, and or functions that we do not call.
public void BeginSplit()
{
#if DEBUG
split();
#else
m_tdSplitter = new Thread(new ThreadStart(split));
m_tdSplitter.Priority = ThreadPriority.AboveNormal;
m_tdSplitter.IsBackground = false;
m_tdSplitter.Name = "Splitting";
m_TimeStart = DateTime.Now;
m_tdSplitter.Start();
#endif
}
In order to program in the .NET Framework, you have to be multi-lingual, using any of the .NET languages that are out there. Although I can't program in C# yet, I can read it.
In the following Split sub below, is where the 80% takes place...
- We set the
m_CacheSize
equal to the m_SizeLimit
, if larger
- We declare a buffer and set it to the
m_CacheSize
- We set
m_fsin
equal to a New FileStream
- We set
m_bReader
equal to a New BinaryReader
- We create a
Directory
if one does not exist
- We name each file so they display in order.
- We set
cBuffer
equal to a New byte[m_fsIn.Length - m_fsIn.Position]
- After doing a few more steps, we get the position and start over again.
private void split()
{
if (m_CacheSize > m_SizeLimit)
m_CacheSize = (uint)m_SizeLimit;
byte[] cBuffer = new byte[m_CacheSize];
int nCounter = 0;
m_fsIn = new FileStream(m_FileName, FileMode.Open, FileAccess.Read);
m_bReader = new BinaryReader(m_fsIn);
if (!Directory.Exists(m_OutDir))
Directory.CreateDirectory(m_OutDir);
else
{
Directory.Delete(m_OutDir, true);
Directory.CreateDirectory(m_OutDir);
}
int reads = 0;
try
{
do
{
if (nCounter <= 9)
m_fsOut = new FileStream(m_OutDir + "\\" +
Path.GetFileNameWithoutExtension(m_FileName)
+ "00" + nCounter.ToString() + ".txt", FileMode.Create);
else if (nCounter <= 99)
m_fsOut = new FileStream(m_OutDir + "\\" +
Path.GetFileNameWithoutExtension(m_FileName)
+ "0" + nCounter.ToString() + ".txt", FileMode.Create);
else
{
m_fsOut = new FileStream(m_OutDir + "\\" +
Path.GetFileNameWithoutExtension(m_FileName)
+ nCounter.ToString() + ".txt", FileMode.Create);
}
do
{
if ((m_fsIn.Length - m_fsIn.Position) < cBuffer.Length)
cBuffer = new byte[m_fsIn.Length - m_fsIn.Position];
reads = m_bReader.Read(cBuffer, 0, cBuffer.Length);
m_bWriter = new BinaryWriter(m_fsOut);
m_bWriter.Write(cBuffer, 0, reads);
m_Written += reads; // = fsIn.Position;
m_Progress = (uint)((float)m_Written * 100 / (float)m_FileSize);
OnPartialSplitDone(EventArgs.Empty);
} while ((m_fsOut.Position < m_SizeLimit) &&
(m_fsIn.Position < m_FileSize));
m_bWriter.BaseStream.Close();
m_Written = m_fsIn.Position;
nCounter++;
m_Progress = (uint)((float)m_Written * 100 / (float)m_FileSize);
OnPartialSplitDone(EventArgs.Empty);
} while ((m_fsIn.Position < m_fsIn.Length));
m_bReader.BaseStream.Close();
OnSplitDone(EventArgs.Empty);
}
catch (Exception e)
{
m_SplitErrorMessage = e.Message;
OnError(EventArgs.Empty);
abort();
}
GC.Collect();
}
Now that the splitting part is out of the way, we can concentrate on the eBookReader
.
I originally started this project for my grand-daughter, But, I decided to post it. She still uses it.
There are 17 subs and functions, but we will be going over a select few as most of them are pretty straight forward. I have commented just about every line of code except for code that explains itself (example: Application.Exit()
), etc...). The reading from and writing to the registry
, advancing automatically to the next book part, and setting/finding the last place you read from, are pieces we will examine.
This sub
is fairly standard for a file read. All it does is it opens a file, reads a line, writes it to a RichTextBox
and we do it again and again until our file is at its end. We set the NumberOfPages
, close the file, and display the results to the user. Code follows...
Private Sub OpentextFile(ByVal fname As String)
rtb1.Clear()
lineCount = 0
Dim fs As FileStream
Dim sr As StreamReader
Dim strFile As String
Try
fs = New FileStream(fname, FileMode.Open, FileAccess.Read)
sr = New StreamReader(fs)
strFile = sr.ReadLine()
Do Until strFile Is Nothing
rtb1.Text &= strFile & vbCrLf
lineCount += 1
strFile = sr.ReadLine()
Loop
NumberOfPages = lineCount / NUMBEROFLINES
fs.Close()
lblStatus.Text = "Number of Pages = " & NumberOfPages.ToString()
lblTotalLineCount.Text = "Total Line Count = " & lineCount.ToString()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
We have to save a spot in our book part so we can continue reading when we return to start reading again. We do this by setting the currentFolder string
when we click the menu Open
button. This opens a FolderDialog
box and we then select a folder to open and retrieve the files in it.
Next, we have to set a position to return to. I was going to set the caret
position, but I could not find away to do this. The only way I found was in WPF
, and I was not going to change the application all around just for a caret setting. So I settled for some simple highlighting
. This is very simple, just highlight some text at the beginning of the next sentence where you want to begin reading, then click the menu item Save Position
. Code follows...
Private Sub mnuFSavePosition_Click(sender As System.Object,
e As System.EventArgs) _
Handles mnuFSavePosition.Click
If currentFolder = "" Then Exit Sub
mySelectedText = rtb1.SelectedText
selection = lstFiles.SelectedItem
My.Computer.Registry.SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\eBookReader",
"selection", selection)
My.Computer.Registry.SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\eBookReader",
"folder", selectedFolder)
My.Computer.Registry.SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\eBookReader",
"mySelectedText", mySelectedText)
CanBeSaved = False
saved = True
End Sub
Now that we have our settings set in the Registry
and our position saved, we can now retrieve it and place the settings back into the original variables. If no text was previously selected, then we display a message stating this fact, otherwise we can continue. Our selected text from a previous read will be highlighted and set at the top of our RichTextBox(HighLighted)
.
Private Sub mnuFGetPosition_Click(sender As System.Object,
e As System.EventArgs) Handles mnuFGetPosition.Click
If My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\eBookReader",
"selection",
selection) <> "" Then
If currentFolder = My.Computer.Registry.GetValue _
("HKEY_LOCAL_MACHINE\SOFTWARE\eBookReader",
"folder", selectedFolder) Then
lstFiles.SelectedItem = My.Computer.Registry.GetValue _
("HKEY_LOCAL_MACHINE\SOFTWARE\eBookReader",
"selection", selection)
mySelectedText = My.Computer.Registry.GetValue _
("HKEY_LOCAL_MACHINE\SOFTWARE\eBookReader",
"mySelectedText", mySelectedText)
If mySelectedText = "" Then
MessageBox.Show("Selected text was not set during the last read...", _
"Highlight Info",
MessageBoxButtons.OK, MessageBoxIcon.Information)
Exit Sub
End If
FindMyText(mySelectedText, 0, rtb1.Text.Length)
rtb1.ScrollToCaret()
CanBeSaved = True
saved = False
Else
mySelectedText = ""
CanBeSaved = False
saved = True
Exit Sub
End If
Else
mySelectedText = ""
End If
End Sub
In the sub displayed above, you will see this line, "FindMyText(mySelectedText, 0, rtb1.Text.Length)"
. This is called so we can use the RichTextBox.Find
method. When FindMyText
is called, it should look like this... FindMyText(mySelectedText, 0, rtb1.Text.Length)
. It uses the selected text that we called from the Registry, a starting point (beginning of the file), and an ending point (end of file). This function returns the caret position (indexToText
), but in the form of highlighted text.
Public Function FindMyText(ByVal searchText As String,
ByVal searchStart As Integer,
ByVal searchEnd As Integer) As Integer
Dim returnValue As Integer = -1
If searchText.Length > 0 And searchStart >= 0 Then
If searchEnd > searchStart Or searchEnd = -1 Then
Dim indexToText As Integer = rtb1.Find(searchText, searchStart,
searchEnd,
RichTextBoxFinds.MatchCase)
If indexToText >= 0 Then
returnValue = indexToText
End If
End If
End If
Return returnValue
End Function
As we are reading along at a steady pace, we get to the end of the file. Now we have to find the next book part. Forget this. This sub
does this for you by auto-matically going to the next book part. It clears the just read book part from the RichTextBox
and opens the next book part by calling this sub
... lstFiles_SelectedIndexChanged()
. This sub
actually gets called when we advance the selected index. Code below...
Private Sub nextBookPartTimer_Tick(sender As Object,
e As System.EventArgs) _
Handles nextBookPartTimer.Tick
If lineNumber = lineCount Then
rtb1.Clear()
lstFiles.SelectedIndex += 1
End If
End Sub
All this sub
does is to keep track of our current caret position by displaying this to the user in labels that are in the StatusStrip
at the bottom of the form.
Private Sub rtb1_KeyDown(sender As Object,
e As System.Windows.Forms.KeyEventArgs) Handles rtb1.KeyDown
If e.KeyData = Keys.Down Then
lineNumber = rtb1.GetLineFromCharIndex(rtb1.SelectionStart) + 1
intPosition = rtb1.SelectionStart - rtb1.GetFirstCharIndexOfCurrentLine() + 1
lblLineNumber.Text = "Line Number = " & lineNumber.ToString
lblColumn.Text = "Column Position = " & intPosition.ToString()
rtb1.Focus()
ElseIf e.KeyData = Keys.Up Then
lineNumber = rtb1.GetLineFromCharIndex(rtb1.SelectionStart) + 1
intPosition = rtb1.SelectionStart - rtb1.GetFirstCharIndexOfCurrentLine() + 1
lblLineNumber.Text = "Line Number = " & lineNumber.ToString
lblColumn.Text = "Column Position = " & intPosition.ToString()
rtb1.Focus()
ElseIf e.KeyData = Keys.Left Then
lineNumber = rtb1.GetLineFromCharIndex(rtb1.SelectionStart) + 1
intPosition = rtb1.SelectionStart - rtb1.GetFirstCharIndexOfCurrentLine() + 1
lblLineNumber.Text = "Line Number = " & lineNumber.ToString
lblColumn.Text = "Column Position = " & intPosition.ToString()
rtb1.Focus()
ElseIf e.KeyData = Keys.Right Then
lineNumber = rtb1.GetLineFromCharIndex(rtb1.SelectionStart) + 1
intPosition = rtb1.SelectionStart - rtb1.GetFirstCharIndexOfCurrentLine() + 1
lblLineNumber.Text = "Line Number = " & lineNumber.ToString
lblColumn.Text = "Column Position = " & intPosition.ToString()
rtb1.Focus()
End If
End Sub
Points of Interest
History