Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

eBookReader

0.00/5 (No votes)
12 Oct 2011 1  
This is a e-Book Reader for text files that uses a file splitter
eBookReader1.jpg

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...

SplitMerge1.jpg

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 subs, 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.

  • Private Sub OpentextFile()

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)

        'Clear the RichTextBox and
        'set the line count equal to zero.
        rtb1.Clear()
        lineCount = 0

        'Declare a FileStream and a StreamReader.
        Dim fs As FileStream
        Dim sr As StreamReader

        'Declare a place holder for each string we read.
        Dim strFile As String

        Try
            fs = New FileStream(fname, FileMode.Open, FileAccess.Read)
            sr = New StreamReader(fs)
            strFile = sr.ReadLine()

            'While the file is opened,
            'we read each line and put
            'it in the RichTextBox and
            'add 1 to our line count.
            'We do it repeatedly until
            'we cant do it no more.
            Do Until strFile Is Nothing
                rtb1.Text &= strFile & vbCrLf
                lineCount += 1
                strFile = sr.ReadLine()
            Loop

            'We set the NumberOfPages equal to
            'the lineCount divided by the
            'NUMBEROFLINES constant.
            NumberOfPages = lineCount / NUMBEROFLINES

            'Close the file.
            fs.Close()

            'Display the to the user, the
            'number of pages, and the line count.
            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
  • Private Sub mnuFSavePosition_Click()

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 the current folder is equal to an empty string
        'Exit the sub and put something in it.
        If currentFolder = "" Then Exit Sub

        'Select some text so we can start reading where we left off.
        mySelectedText = rtb1.SelectedText

        'set the current book part
        selection = lstFiles.SelectedItem

        'Set are Registry values
        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)
        'Set the Booleans
        CanBeSaved = False
        saved = True
    End Sub
  • Private Sub mnuFGetPosition_Click()

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
                'Get the last read Book part
                lstFiles.SelectedItem = My.Computer.Registry.GetValue _
                               ("HKEY_LOCAL_MACHINE\SOFTWARE\eBookReader",
                                "selection", selection)
                'Get the last selected text so we can start reading
                'where we left off.
                mySelectedText = My.Computer.Registry.GetValue _
                                ("HKEY_LOCAL_MACHINE\SOFTWARE\eBookReader",
                                 "mySelectedText", mySelectedText)

                'If RichTextBox has no selected text...Then?
                If mySelectedText = "" Then
                    MessageBox.Show("Selected text was not set during the last read...", _
                                    "Highlight Info",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information)
                    Exit Sub
                End If

                'Display the selected text at the top of
                'the RichTextBox
                FindMyText(mySelectedText, 0, rtb1.Text.Length)

                'Set the caret position
                rtb1.ScrollToCaret()

                'Set the Booleans
                CanBeSaved = True
                saved = False
            Else
                mySelectedText = ""
                CanBeSaved = False
                saved = True
                Exit Sub
            End If
        Else
            mySelectedText = ""
        End If
    End Sub
  • Public Function FindMyText()

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

        ' Initialize the return value to false by default.
        Dim returnValue As Integer = -1

        ' Ensure that a search string and a valid starting point are specified.
        If searchText.Length > 0 And searchStart >= 0 Then

            ' Ensure that a valid ending value is provided.
            If searchEnd > searchStart Or searchEnd = -1 Then

                ' Obtain the location of the search string in rtb1.
                Dim indexToText As Integer = rtb1.Find(searchText, searchStart,
                                                       searchEnd,
                                                       RichTextBoxFinds.MatchCase)

                ' Determine whether the text was found in rtb1.
                If indexToText >= 0 Then

                    ' Return the index to the specified search text.
                    returnValue = indexToText
                End If
            End If
        End If

        Return returnValue
    End Function
  • Private Sub nextBookPartTimer_Tick()

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
            'Clear the RichTextBox
            rtb1.Clear()
            'Advance to the next book part automatically
            lstFiles.SelectedIndex += 1
        End If
    End Sub
  • Private Sub rtb1_KeyDown()

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
        'Keep track of all positions when pressing the arrow keys.
        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

  • Original post 10·08·2011

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here