Extending the ImageBox component to display the contents of a PDF file using C#
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 extendedPdfImageBox
.
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.
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 viewPdfPassword
- 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 thePdf2ImageSettings
class discussed earlier to control quality settings for the converted document.PageCache
- an internal dictionary which stores aBitmap
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.
[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.
[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.
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.
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.
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.
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 on 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.
Update History
- 2011-09-04 - First published
- 2020-11-21 - Updated formatting
Related articles you may be interested in
- Displaying multi-page tiff files using the ImageBox control and C#
- Adding drag handles to an ImageBox to allow resizing of selection regions
- ImageBox 1.1.4.0 update
- ImageBox and TabList update's - virtual mode, pixel grid, bug fixes and more!
- ImageBox update, version 1.1.0.0
- Zooming to fit a region in a ScrollableControl
- Zooming into a fixed point on a ScrollableControl
- Arcade explosion generator
- Creating an image viewer in C# Part 5: Selecting part of an image
- Creating a scrollable and zoomable image viewer in C# Part 4
- Creating a scrollable and zoomable image viewer in C# Part 3
- Creating a scrollable and zoomable image viewer in C# Part 2
- Creating a scrollable and zoomable image viewer in C# Part 1
Downloads
Filename | Description | Version | Release Date | |
---|---|---|---|---|
PdfImageBoxSample.zip
|
Sample project showing how to extend the |
04/09/2011 | Download | |
Cyotek.GhostScript.zip
|
Work in progress class library for providing GhostScript integration in a .NET application. |
04/09/2011 | Download | |
Cyotek.GhostScript.PdfConversion.zip
|
Class library for converting PDF files into images using GhostScript. Also requires the Cyotek.GhostScript assembly. |
04/09/2011 | Download |
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?
Comments
DotNetKicks.com
#
[b]Extending the ImageBox component to display the contents of a PDF file[/b] You've been kicked (a good thing) - Trackback from DotNetKicks.com
DotNetShoutout
#
[b]Extending the ImageBox component to display the contents of a PDF file using C#[/b] Thank you for submitting this cool story - Trackback from DotNetShoutout