Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C#

Extending the ImageBox Component to Display the Contents of a PDF File using C#

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
20 Jan 2012CPOL4 min read 19.8K   9   2
How to extend the ImageBox control to be able to display PDF files with the help of the GhostScript library and the conversion library

In this article, I'll describe how to extend the ImageBox control discussed in earlier articles to be able to display PDF files with the help of the GhostScript library and the conversion library described in the previous article.

Getting Started

You can download the source code used in this article from the links below. These are:

  • Cyotek.GhostScript - Core library providing GhostScript integration support
  • Cyotek.GhostScript.PdfConversion - Support library for converting a PDF document into images
  • PdfImageBoxSample - Sample project containing an updated ImageBox control, and the extended PdfImageBox.

Please note that the native GhostScript DLL is not included in these downloads, you will need to obtain that from the GhostScript project page.

Extending the ImageBox

To start extending the ImageBox, create a new class and inherit the ImageBox control. I also decided to override some of the default properties, so I added a constructor which sets the new values.

C#
public PdfImageBox()
{
  // override some of the original ImageBox defaults
  this.GridDisplayMode = ImageBoxGridDisplayMode.None;
  this.BackColor = SystemColors.AppWorkspace;
  this.ImageBorderStyle = ImageBoxBorderStyle.FixedSingleDropShadow;

  // new pdf conversion settings
  this.Settings = new Pdf2ImageSettings();
}

To ensure correct designer support, override versions of the properties with new DefaultValue attributes were added. With this done, it's time to add the new properties that will support viewing PDF files. The new properties are:

  • PdfFileName - The filename of the PDF to view
  • PdfPassword - Specifies the password of the PDF file if one is required to open it (note, I haven't actually tested that this works!)
  • Settings - Uses the Pdf2ImageSettings class discussed earlier to control quality settings for the converted document.
  • PageCache - An internal dictionary which stores a Bitmap against a page number to cache pages after these have loaded.

With the exception of PageCache, each of these properties also has backing event for change notifications, and as Pdf2ImageSettings implements INotifyPropertyChanged, we'll also bind an event detect when the individual setting properties are modified.

C#
[Category("Appearance"), DefaultValue(typeof(Pdf2ImageSettings), "")]
public virtual Pdf2ImageSettings Settings
{
  get { return _settings; }
  set
  {
    if (this.Settings != value)
    {
      if (_settings != null)
        _settings.PropertyChanged -= SettingsPropertyChangedHandler;

      _settings = value;
      _settings.PropertyChanged += SettingsPropertyChangedHandler;

      this.OnSettingsChanged(EventArgs.Empty);
    }
  }
}

private void SettingsPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
  this.OnSettingsChanged(e);
}

protected virtual void OnSettingsChanged(EventArgs e)
{
  this.OpenPDF();

  if (this.SettingsChanged != null)
    this.SettingsChanged(this, e);
}

Navigation Support

Although the PdfImageBox doesn't supply a user interface for navigating to different pages, we want to make it easy for the hosting application to provide one. To support this, a new CurrentPage property will be added for allowing the active page to retrieved or set, and also a number of readonly CanMove* properties. These properties allow the host to query which navigation options are applicable in order to present the correct UI.

C#
[Browsable(false)]
public virtual int PageCount
{ get { return _converter != null ? _converter.PageCount : 0; } }

[Category("Appearance"), DefaultValue(1)]
public int CurrentPage
{
  get { return _currentPage; }
  set
  {
    if (this.CurrentPage != value)
    {
      if (value < 1 || value > this.PageCount)
        throw new ArgumentException("Page number is out of bounds");

      _currentPage = value;

      this.OnCurrentPageChanged(EventArgs.Empty);
    }
  }
}

[Browsable(false)]
public bool CanMoveFirst
{ get { return this.PageCount != 0 && this.CurrentPage != 1; } }

[Browsable(false)]
public bool CanMoveLast
{ get { return this.PageCount != 0 && this.CurrentPage != this.PageCount; } }

[Browsable(false)]
public bool CanMoveNext
{ get { return this.PageCount != 0 && this.CurrentPage < this.PageCount; } }

[Browsable(false)]
public bool CanMovePrevious
{ get { return this.PageCount != 0 && this.CurrentPage > 1; } }

Again, to make it easier for the host to connect to the control, we also add some helper navigation methods.

C#
public void FirstPage()
{
  this.CurrentPage = 1;
}

public void LastPage()
{
  this.CurrentPage = this.PageCount;
}

public void NextPage()
{
  this.CurrentPage++;
}

public void PreviousPage()
{
  this.CurrentPage--;
}

Finally, it can sometimes take a few seconds to convert a page in a PDF file. To allow the host to provide a busy notification, such as setting the wait cursor or displaying a status bar message, we'll add a pair of events which will be called before and after a page is converted.

C#
public event EventHandler LoadingPage;

public event EventHandler LoadedPage;

Opening the PDF File

Each of the property changed handlers in turn call the OpenPDF method. This method first clears any existing image cache and then initializes the conversion class based on the current PDF file name and quality settings. If the specified file is a valid PDF, the first page is converted, cached, and displayed.

C#
public void OpenPDF()
{
  this.CleanUp();

  if (!this.DesignMode)
  {
    _converter = new Pdf2Image()
    {
      PdfFileName = this.PdfFileName,
      PdfPassword = this.PdfPassword,
      Settings = this.Settings
    };

    this.Image = null;
    this.PageCache= new Dictionary<int, Bitmap>();
    _currentPage = 1;

    if (this.PageCount != 0)
    {
      _currentPage = 0;
      this.CurrentPage = 1;
    }
  }
}

private void CleanUp()
{
  // release  bitmaps
  if (this.PageCache != null)
  {
    foreach (KeyValuePair<int, Bitmap> pair in this.PageCache)
      pair.Value.Dispose();
    this.PageCache = null;
  }
}

Displaying the Image

Each time the CurrentPage property is changed, it calls the SetPageImage method. This method first checks to ensure the specified page is present in the cache. If it is not, it will load the page in. Once the page is in the cache, it is then displayed in the ImageBox, and the user can then pan and zoom as with any other image.

C#
protected virtual void SetPageImage()
{
  if (!this.DesignMode && this.PageCache != null)
  {
    lock (_lock)
    {
      if (!this.PageCache.ContainsKey(this.CurrentPage))
      {
        this.OnLoadingPage(EventArgs.Empty);
        this.PageCache.Add(this.CurrentPage, _converter.GetImage(this.CurrentPage));
        this.OnLoadedPage(EventArgs.Empty);
      }

      this.Image = this.PageCache[this.CurrentPage];
    }
  }
}

Note that we operate a lock during the execution of this method, to ensure that you can't try and load the same page twice.

With this method in place, the control is complete and ready to be used as a basic PDF viewer. In order to keep the article down to a reasonable size, I've excluded some of the definitions, overloads and helper methods; these can all be found in the sample download below.

The sample project demonstrates all the features described above and provides an example setting up a user interface for navigating a PDF document.

Future Changes

At the moment, the PdfImageBox control processes one page at a time and caches the results. This means that navigation through already viewed pages is fast, but displaying new pages can be less than ideal. A possible enhancement would be to make the control multithreaded, and continue to load pages on a background thread.

Another issue is that as the control is caching the converted images in memory, it may use a lot of memory in order to display large PDF files. Not quite sure on the best approach to resolve this one, either to "expire" older pages, or to keep only a fixed number in memory. Or even save each page to a temporary disk file.

Finally, I haven't put in any handling at all for if the converter fails to convert a given page... I'll add this to a future update, and hopefully get the code hosted on an SVN server for interested parties.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionIt is very cool! Is it possible to convert PostScript to image in the same way? Pin
zhangzq7121-Jan-12 14:56
zhangzq7121-Jan-12 14:56 
It is very cool! Is it possible to convert PostScript to image in the same way?
email:zhangzq71@hotmail.com

AnswerRe: It is very cool! Is it possible to convert PostScript to image in the same way? Pin
Josip.Habjan27-Aug-13 19:56
Josip.Habjan27-Aug-13 19:56 

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

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