Click here to Skip to main content
15,891,607 members
Articles / Desktop Programming / Windows Forms

Crystal Image Toolkit: thumbnail image control and picture viewing.

Rate me:
Please Sign up or sign in to vote.
4.68/5 (21 votes)
11 May 2011LGPL37 min read 109.8K   8.3K   95  
Thumbnail and image viewing controls for Windows Forms, using C#.
#region gnu_license

/*
    Crystal Controls - C# control library containing the following tools:
        CrystalControl - base class
        CrystalGradientControl - a control that can either have a gradient background or be totally transparent.
        CrystalLabel - a homegrown label that can have a gradient or transparent background.
        CrystalPanel - a panel that can have a gradient or transparent background.
        CrystalTrackBar - a homegrown trackbar that can have a gradient or transparent background.
        CrystalToolStripTrackBar - a host for CrystalTrackBar that allows it to work in a ToolStrip.
        
        CrystalImageGridView - a control that hosts thumbnail images in a virtual grid.
        CrystalImageGridModel - a data model that holds a collection of CrystalImageItems
                                to feed to CrystalImageGridView.
        CrystalImageItem - a class that describes an Image file.
        CrystalThumbnailer - provides thumbnailing methods for images.

        CrystalCollector - a base class for a controller that links 
                            CrystalImageGridView to the CrystalImageGridModel.
        CrystalFileCollector - a controller that works on disk-based Image files.
        CrystalDesignCollector - a controller that works in Visual Studio toolbox designer.
        CrystalMemoryCollector - a controller that can be used to add images from memory.
        CrystalMemoryZipCollector - a controller that accesses images in zip files by streaming them into memory.
        CrystalZipCollector - a controller that accesses images in zip files by unpacking them.
        CrystalRarCollector - a controller that accesses images in rar files by unpacking them.

        CrystalPictureBox - a picture box control, derived from CrystalGradientControl.
        CrystalPictureShow - a control for viewing images and processing slideshows.
        CrystalComicShow - a control for viewing comic-book images in the CDisplay format.
 
    Copyright (C) 2006, 2008 Richard Guion
    Attilan Software Factory: http://www.attilan.com
    Contact: richard@attilan.com

   Version 1.0.0
        This is a work in progress: USE AT YOUR OWN RISK!  Interfaces/Methods may change!
 
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#endregion

using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Attilan.Crystal.Tools;
using Schematrix;

namespace Attilan.Crystal.Controls
{
    /// <summary>
    /// A Crystal Image collector variant that operates on images stored in RAR archives.
    /// NOTE: To use this control, you must ship unrar.dll with your application.
    /// DLL is available at http://www.rarlab.com/rar/UnRARDLL.exe
    /// </summary>
    [ToolboxItem(false)]
    public class CrystalRarCollector : CrystalFileCollector, ICrystalCompressedCollector
    {
        #region Fields

        /// <summary>
        /// Thread used to collect images from the Rar file.
        /// </summary>
        protected Thread _collectRarThread = null;

        /// <summary>
        /// Root location to use for unpacking image files in the RAR archive.
        /// </summary>
        private string _unpackLocationRoot = string.Empty;

        /// <summary>
        /// Name of the RAR file.
        /// </summary>
        private string _rarFileName = string.Empty;

        /// <summary>
        /// RAR file object.
        /// </summary>
        protected Unrar _rarFile = null;

        #endregion

        #region Properties

        /// <summary>
        /// The root location where images will be unpacked.
        /// </summary>
        public string UnpackLocationRoot
        {
            get { return _unpackLocationRoot; }
            set { _unpackLocationRoot = value; }
        }

        /// <summary>
        /// Name and location of the archive file.
        /// </summary>
        public string CompressedFileName
        {
            get { return _rarFileName; }
            set { _rarFileName = value; }
        }

        /// <summary>
        /// Gets the native object that represents the compressed file.
        /// </summary>
        public object CompressedFile
        {
            get
            {
                if ((CompressedFileName == null) || (CompressedFileName == string.Empty))
                    return null;

                if ((_rarFile == null) || (_rarFile.ArchivePathName != CompressedFileName))
                {
                    _rarFile = new Unrar();
                }

                return _rarFile;
            }
        }

        #endregion

        #region Interface implementation

        /// <summary>
        /// Inits the collector with the compressed archive file name,
        /// sets UnpackLocationRoot and CompressedFileName properties.
        /// </summary>
        /// <param name="compressedFileName">Name and path of compressed file.</param>
        public void InitCompressedFile(string compressedFileName)
        {
            if ((compressedFileName == null) || (compressedFileName == string.Empty) ||
                (!File.Exists(compressedFileName)))
                return;

            CompressedFileName = compressedFileName;

            // Compute the Hash value of the imageLocation.
            int hashVal = compressedFileName.ToLower().GetHashCode();

            string unzipLocation = UnpackLocationRoot;
            unzipLocation += "\\";
            unzipLocation += hashVal.ToString();

            ImageLocation = unzipLocation;
            ImageLocation += '\\';

            if (!Directory.Exists(ImageLocation))
                Directory.CreateDirectory(ImageLocation);
        }

        /// <summary>
        /// Unpacks the image files in the archive to the UnpackLocation root.
        /// </summary>
        /// <returns>True if images were unpacked, false if an error occurred.</returns>
        public bool UncompressFiles()
        {
            _rarFile = (Unrar) CompressedFile;
            _rarFile.Open(CompressedFileName, Unrar.OpenMode.Extract);
            _rarFile.DestinationPath = ImageLocation;

            // Read each header, skipping directory entries
            while (_rarFile.ReadHeader())
            {
                /////////////////////////////////////////////////////
                // Only process images in the compressed file!
                if (_rarFile.CurrentFile.IsDirectory)
                {
                    _rarFile.Skip();
                    continue;
                }

                // Check for '.' in first character, some broken archives have this.
                string fileName = Path.GetFileName(_rarFile.CurrentFile.FileName);
                if ((fileName.Length > 0) && (fileName[0] == '.'))
                {
                    _rarFile.Skip();
                    continue;
                }

                if (!CrystalTools.IsFileImage(_rarFile.CurrentFile.FileName))
                {
                    _rarFile.Skip();
                    continue;
                }
                /////////////////////////////////////////////////////

                //if (!File.Exists(theImage.ImageFullPath))
                _rarFile.Extract();
                //else
                //_rarFile.Skip();
            }

            return true;
        }

        /// <summary>
        /// Deletes any images unpacked at the UnpackLocation root folder.
        /// </summary>
        /// <returns>True if images were deleted, false if an error occurred.</returns>
        public bool RemovedUncompressedFiles()
        {
            if ((!string.IsNullOrEmpty(UnpackLocationRoot)) && (ImageLocation.Contains(UnpackLocationRoot)))
            {
                CrystalLogger.LogEvent(
                    string.Format("Removing Uncompressed Files from Rar: {0}", ImageLocation));

                if (Directory.Exists(ImageLocation))
                    Directory.Delete(ImageLocation, true);
            }

            return true;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Default constructor.
        /// </summary>
        public CrystalRarCollector()
        {
            StringBuilder strBuilder = new StringBuilder();
            strBuilder.Append(Application.UserAppDataPath);
            strBuilder.Append("\\");
            strBuilder.Append("unpack");

            UnpackLocationRoot = strBuilder.ToString();
        }

        /// <summary>
        /// Sets the tool tip text inside a CrystalImageItem object.
        /// </summary>
        /// <param name="theImage">CrystalImageItem object.</param>
        /// <param name="entry">File information about the image item in the rar file.</param>
        protected virtual void SetToolTipText(ref CrystalImageItem theImage, RARFileInfo entry)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(theImage.ImageName);
            builder.Append("\n");
            builder.Append(entry.FileTime.ToShortDateString());

            builder.Append("\nType: ");
            string ext = Path.GetExtension(entry.FileName);
            if (ext != string.Empty)
                ext = ext.Remove(0, 1);
            theImage.ImageFormatString = ext.ToUpper();
            builder.Append(theImage.ImageFormatString);
            builder.Append(" Image");

            builder.Append("\nSize: ");
            theImage.ImageSize = CrystalTools.FormatFileSizeString(entry.UnpackedSize);
            builder.Append(theImage.ImageSize);

            theImage.ToolTipText = builder.ToString();
        }

        /// <summary>
        /// Sets the image attributes into the CrystalImageItem object.
        /// </summary>
        /// <param name="theImage">CrystalImageItem object.</param>
        /// <param name="entry">File information about the rar image item.</param>
        protected virtual void SetImageAttributes(ref CrystalImageItem theImage, RARFileInfo entry)
        {
            theImage.LastWriteTime = entry.FileTime;
            theImage.CreationTime = entry.FileTime;
            SetImageFormat(ref theImage, Path.GetExtension(entry.FileName));
        }

        /// <summary>
        /// Kills the collection threads.
        /// </summary>
        protected override void KillCollectionThread()
        {
            if (_collectRarThread != null)
            {
                _collectRarThread.Abort();
                _collectRarThread = null;
            }

            base.KillCollectionThread();
        }

        /// <summary>
        /// Collects the images at the ImageLocation into the model and then tells the view to display them.
        /// </summary>
        /// <returns>True if images were successfully collected, false otherwise.</returns>
        public override bool CollectImages()
        {
            if ((CompressedFileName == null) || (CompressedFileName == string.Empty) ||
                (!File.Exists(CompressedFileName)))
                return false;

            if (GridView == null)
                return false;

            PurgeImages();

            _rarFile = null;

            // BUG: This won't work if the images are within subfolders.
            //if (UncompressFiles())
            //return CollectModelImages();

#if CRYSTAL_DEBUG
            CrystalLogger.LogDebug(string.Format("Total Memory before CrystalImage List = {0}", GC.GetTotalMemory(false)));
#endif
            // Kill the background population if it is already running
            KillCollectionThread();

            // Kick off a background thread that thumbnails the images in our collection
            _collectRarThread = new Thread(CollectRarImages);
            _collectRarThread.Start();

            return true;
        }

        /// <summary>
        /// Unpacks a single image referenced by a CrystalImageItem object.
        /// </summary>
        /// <param name="imageItem">CrystalImageItem object referencing the image to unpack.</param>
        /// <returns>True if the image was found and unpacked, false otherwise.</returns>
        protected bool UnrarImage(CrystalImageItem imageItem)
        {
            Unrar unrarFile = new Unrar();
            unrarFile.Open(CompressedFileName, Unrar.OpenMode.Extract);
            unrarFile.DestinationPath = ImageLocation;
            RARFileInfo fileInfo = (RARFileInfo) imageItem.Tag;

            while (unrarFile.ReadHeader())
            {
                if (unrarFile.CurrentFile.FileName == fileInfo.FileName)
                {
                    unrarFile.Extract();
                    return true;
                }
                unrarFile.Skip();
            }

            return false;
        }

        /// <summary>
        /// Asynchronously collects thumbnail images from the CrystalImageItems in the model.
        /// </summary>
        protected void CollectRarImages()
        {
            _rarFile = (Unrar) CompressedFile;
            _rarFile.Open(CompressedFileName, Unrar.OpenMode.Extract);
            _rarFile.DestinationPath = ImageLocation;

            List<CrystalImageItem> _crystalList = new List<CrystalImageItem>();

            // Read each header, skipping directory entries
            while (_rarFile.ReadHeader())
            {
                /////////////////////////////////////////////////////
                // Only process images in the compressed file!
                if (_rarFile.CurrentFile.IsDirectory)
                {
                    _rarFile.Skip();
                    continue;
                }

                // Check for '.' in first character, some broken archives have this.
                string fileName = Path.GetFileName(_rarFile.CurrentFile.FileName);
                if ((fileName.Length > 0) && (fileName[0] == '.'))
                {
                    _rarFile.Skip();
                    continue;
                }

                if (!CrystalTools.IsFileImage(_rarFile.CurrentFile.FileName))
                {
                    _rarFile.Skip();
                    continue;
                }
                /////////////////////////////////////////////////////

                CrystalImageItem theImage = CreateCrystalImage();
                theImage.ImageLocation = ImageLocation;

                theImage.ImageName = _rarFile.CurrentFile.FileName;
                theImage.DisplayName = Path.GetFileNameWithoutExtension(theImage.ImageName);
                theImage.Tag = _rarFile.CurrentFile;
                SetToolTipText(ref theImage, _rarFile.CurrentFile);
                SetImageAttributes(ref theImage, _rarFile.CurrentFile);

                // Default border color is what was set in the view.
                if (GridView != null)
                    theImage.BorderColor = GridView.CellBorderColor;

                Thumbnailer.AdjustThumbLocation(theImage);

                _crystalList.Add(theImage);

                if (!File.Exists(theImage.ImageFullPath))
                    _rarFile.Extract();
                else
                    _rarFile.Skip();
            }

            // Sort the list by the image name
            SortCrystalList(CrystalSortType.DisplayName, true, _crystalList);

            // Replace the Crystal Image List in the model.
            _gridModel.AddList(_crystalList);

            // Have the model calculate its virtual size,
            // and the size/position of each image item.
            _gridModel.CalculateVirtualGrid();

#if CRYSTAL_DEBUG
            CrystalLogger.LogDebug(string.Format("Total Memory after CrystalImage List = {0}", GC.GetTotalMemory(false)));
#endif

            // Kick off a background thread that thumbnails the images in our collection
            _collectThread = new Thread(CollectThumbnailImages);
            _collectThread.Start();

            // Tell the view to start drawing the image grid
            UpdateResizeVirtualGrid();

            return;
        }

        /// <summary>
        /// Disposes resources used by this object.
        /// </summary>
        public override void Dispose()
        {
            base.Dispose();

            RemovedUncompressedFiles();

            if (_rarFile != null)
            {
                _rarFile.Dispose();
                _rarFile = null;
            }
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer (Senior)
United States United States
Richard has been working with Windows software since 1991. He has worked for Borland, Microsoft, Oracle, and various startup companies such as Livescribe. Currently he is developing projects in C#, Windows Forms, and Net Framework. Visit his blog Attilan (www.attilan.com) to learn more about his tools, projects and discoveries.

Comments and Discussions