Wednesday, May 8, 2019

Document Thumbnails

I was wondering how hard it would be to show thumbnails of attachments the user had added to my accounts payable and purchasing applications. Some analysis shows the vast majority are pdf files, with image files a distant second. There are also some Office documents and emails but they total less than five percent of all the attachments my users have saved.

I found a reasonably priced product ($600) that will create an image of a pdf page and has a free demo version. The product is called Spire.pdf. Other products such as ExpertPdf and ActivePdf don't appear to offer this feature.

Creating a thumbnail from an image file is trivial and requires no special downloads.

I also wanted to leverage the Shell dll to display application icons for the other file types.

Start a new WPF project called FileThumbnails. I'm using Visual Studio 2019, C#, and targeting Framework 4.7.


Start by adding the Spire demo product using NuGet. Browse to Tools -> NuGet Package Manager -> Manage NugetPackages for Solution. Click Browse and search for Spire.pdf. Install FreeSpire.pdf.



Now we need to install the windows api code pack. Select Browse and search for microsoft.windowsapicodepack.shell. Pick the one from Microsoft and install it into the project.


Now add a reference to System.Drawing.

The application will list three files (substitute your own). Each one will have a tooltip which is either a thumbnail of the first page of the file or the icon of the owning application.

Here's the XAML.


<Window x:Class="FileThumbnails.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:FileThumbnails"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel Orientation="Vertical">
        <TextBlock Text="{Binding pdfFileName}">
            <TextBlock.ToolTip>
                <Image Source="{Binding pdfTooltip}" Stretch="Uniform" Width="200"/>
            </TextBlock.ToolTip>
        </TextBlock>
        <TextBlock Text="{Binding imgFileName}">
            <TextBlock.ToolTip>
                <Image Source="{Binding imgTooltip}" Stretch="Uniform" Width="200"/>
            </TextBlock.ToolTip>
        </TextBlock>
        <TextBlock Text="{Binding otherFileName}">
            <TextBlock.ToolTip>
                <Image Source="{Binding otherTooltip}" Stretch="Uniform" Width="200"/>
            </TextBlock.ToolTip>
        </TextBlock>
    </StackPanel>
</Window>

By specifying Stretch="Uniform" Width="200" we cause the thumbnail to maintain its aspect ratio and resize to 200 width.

Here's the code-behind. We'll complete the interesting bits later.


using System;
using System.Windows;
using System.Windows.Media.Imaging;
using Microsoft.WindowsAPICodePack.Shell;
using System.Drawing;
using Spire.Pdf;
using System.IO;
using System.Drawing.Imaging;
using System.ComponentModel;

namespace FileThumbnails
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public String pdfFileName { get; set; }
        public BitmapImage pdfTooltip
        {
            get { return GetToolTip(pdfFileName); }
        }

        public String imgFileName { get; set; }
        public BitmapImage imgTooltip
        {
            get
            {
                return GetToolTip(imgFileName);
            }
        }

        public String otherFileName { get; set; }
        public BitmapImage otherTooltip
        {
            get
            {
                return GetToolTip(otherFileName);
            }
        }

        public MainWindow()
        {
            pdfFileName = @"C:\Users\me\documents\Expectations.pdf";
            imgFileName = @"C:\Users\me\documents\20190421.jpg";
            otherFileName = @"C:\Users\me\documents\ELTS.docx";
            InitializeComponent();
        }

        public BitmapImage GetToolTip(String FileName)
        {
            String Extension = System.IO.Path.GetExtension(FileName).ToUpper();
            switch (Extension)
            {
                case ".PDF": return GetPDFThumbnail(FileName);
                case ".PNG":
                case ".BMP":
                case ".JPG": return GetImageThumbnail(FileName);
                default: return GetApplicationIcon(FileName);
            }
        }

        private BitmapImage GetPDFThumbnail(String FileName)
        {
        }

        private BitmapImage GetImageThumbnail(String FileName)
        {
        }

        private BitmapImage GetApplicationIcon(String FileName)
        {
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void PropChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

    }
}

Let's start with the pdf.


private BitmapImage GetPDFThumbnail(String FileName)
{
    PdfDocument doc = new PdfDocument();
    doc.LoadFromFile(pdfFileName);
    Bitmap bmp = (Bitmap)doc.SaveAsImage(0);
    using (MemoryStream ms = new MemoryStream())
    {
        bmp.Save(ms, ImageFormat.Png);
        ms.Position = 0;
        BitmapImage bmi = new BitmapImage();
        bmi.BeginInit();
        bmi.StreamSource = ms;
        bmi.CacheOption = BitmapCacheOption.OnLoad;
        bmi.EndInit();
        return bmi;
    }
}

PdfDocument is a Spire.pdf class. It's very easy to load it with a pdf file and get a bitmap of the first page. We have to convert that into a BitmapImage in order to use it as a tooltip. Most of the complexity is there.

Now the image, which is trivial.


private BitmapImage GetImageThumbnail(String FileName)
{
    BitmapImage bmi = new BitmapImage();
    bmi.BeginInit();
    bmi.UriSource = new Uri(FileName);
    bmi.DecodePixelWidth = 200;
    bmi.EndInit();
    return bmi;
}


The only interesting thing here is bmi.DecodePixelWidth = 200 which speeds up the resizing.

The last method we need to complete fetches the icon of the owner of the specified file type and converts that to a BitmapImage. Again - the bulk of the code is involved with converting from one type of image to another.


private BitmapImage GetApplicationIcon(String FileName)
{
    BitmapSource bms = ShellFile.FromFilePath(FileName).Thumbnail.BitmapSource;
    JpegBitmapEncoder encoder = new JpegBitmapEncoder();
    MemoryStream ms = new MemoryStream();
    BitmapImage bmi = new BitmapImage();
    encoder.Frames.Add(BitmapFrame.Create(bms));
    encoder.Save(ms);
    ms.Position = 0;
    bmi.BeginInit();
    bmi.StreamSource = new MemoryStream(ms.ToArray());
    bmi.EndInit();
    return bmi;
}


Here are the results.

Image tool tip

Other tool tip

Pdf tool tip

No comments:

Post a Comment