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

ImageListView

Rate me:
Please Sign up or sign in to vote.
4.93/5 (145 votes)
18 Feb 2010Apache10 min read 601.4K   29.4K   380  
A .NET ListView like control for displaying image files with asynchronously loaded thumbnails.
// ImageListView - A listview control for image files
// Copyright (C) 2009 Ozgur Ozcitak
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Ozgur Ozcitak (ozcitak@yahoo.com)

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.ComponentModel.Design.Serialization;
using System.Resources;
using System.Reflection;

namespace Manina.Windows.Forms
{
    /// <summary>
    /// Represents an image list view control.
    /// </summary>
    [ToolboxBitmap(typeof(ImageListView))]
    [Description("Represents an image list view control.")]
    [DefaultEvent("ItemClick")]
    [DefaultProperty("Items")]
    [Designer(typeof(ImageListViewDesigner))]
    [DesignerSerializer(typeof(ImageListViewSerializer), typeof(CodeDomSerializer))]
    [Docking(DockingBehavior.Ask)]
    public partial class ImageListView : Control
    {
        #region Constants
        /// <summary>
        /// Default width of column headers in pixels.
        /// </summary>
        internal const int DefaultColumnWidth = 100;
        /// <summary>
        /// Selection tolerance for column separators.
        /// </summary>
        internal const int SeparatorSize = 12;
        private const int WS_BORDER = 0x00800000;
        private const int WS_EX_CLIENTEDGE = 0x00000200;
        #endregion

        #region Member Variables
        private BorderStyle mBorderStyle;
        private int mCacheLimitAsItemCount;
        private long mCacheLimitAsMemory;
        private ImageListViewColumnHeaderCollection mColumns;
        private Image mDefaultImage;
        private Image mErrorImage;
        private Font mHeaderFont;
        private ImageListViewItemCollection mItems;
        private Size mItemMargin;
        internal ImageListViewRenderer mRenderer;
        internal ImageListViewSelectedItemCollection mSelectedItems;
        private ColumnType mSortColumn;
        private SortOrder mSortOrder;
        private Size mThumbnailSize;
        private UseEmbeddedThumbnails mUseEmbeddedThumbnails;
        private View mView;
        private Point mViewOffset;

        // Layout variables
        internal System.Windows.Forms.HScrollBar hScrollBar;
        internal System.Windows.Forms.VScrollBar vScrollBar;
        internal ImageListViewLayoutManager layoutManager;
        private bool disposed;
        private bool forceRefresh;

        // Interaction variables
        internal ImageListViewNavigationManager navigationManager;

        // Cache thread
        internal ImageListViewCacheManager cacheManager;
        internal ImageListViewItemCacheManager itemCacheManager;
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets whether column headers respond to mouse clicks.
        /// </summary>
        [Category("Behavior"), Description("Gets or sets whether column headers respond to mouse clicks."), DefaultValue(true)]
        public bool AllowColumnClick { get; set; }
        /// <summary>
        /// Gets or sets whether column headers can be resized with the mouse.
        /// </summary>
        [Category("Behavior"), Description("Gets or sets whether column headers can be resized with the mouse."), DefaultValue(true)]
        public bool AllowColumnResize { get; set; }
        /// <summary>
        /// Gets or sets whether the user can drag items for drag-and-drop operations.
        /// </summary>
        [Category("Behavior"), Description("Gets or sets whether the user can drag items for drag-and-drop operations."), DefaultValue(false)]
        public bool AllowDrag { get; set; }
        /// <summary>
        /// Gets or sets whether duplicate items (image files pointing to the same path 
        /// on the file system) are allowed.
        /// </summary>
        [Category("Behavior"), Description("Gets or sets whether duplicate items (image files pointing to the same path on the file system) are allowed."), DefaultValue(false)]
        public bool AllowDuplicateFileNames { get; set; }
        /// <summary>
        /// Gets or sets the background color of the control.
        /// </summary>
        [Category("Appearance"), Description("Gets or sets the background color of the control."), DefaultValue(typeof(Color), "Window")]
        public override Color BackColor { get { return base.BackColor; } set { base.BackColor = value; Refresh(); } }
        /// <summary>
        /// Gets or sets the border style of the control.
        /// </summary>
        [Category("Appearance"), Description("Gets or sets the border style of the control."), DefaultValue(typeof(BorderStyle), "Fixed3D")]
        public BorderStyle BorderStyle { get { return mBorderStyle; } set { mBorderStyle = value; UpdateStyles(); } }
        /// <summary>
        /// Gets or sets the cache limit as either the count of thumbnail images or the memory allocated for cache (e.g. 10MB).
        /// </summary>
        [Category("Behavior"), Description("Gets or sets the cache limit as either the count of thumbnail images or the memory allocated for cache (e.g. 10MB)."), DefaultValue("20MB")]
        public string CacheLimit
        {
            get
            {
                if (mCacheLimitAsMemory != 0)
                    return (mCacheLimitAsMemory / 1024 / 1024).ToString() + "MB";
                else
                    return mCacheLimitAsItemCount.ToString();
            }
            set
            {
                string slimit = value;
                int limit = 0;
                if ((slimit.EndsWith("MB", StringComparison.OrdinalIgnoreCase) &&
                    int.TryParse(slimit.Substring(0, slimit.Length - 2).Trim(), out limit)) ||
                    (slimit.EndsWith("MiB", StringComparison.OrdinalIgnoreCase) &&
                    int.TryParse(slimit.Substring(0, slimit.Length - 3).Trim(), out limit)))
                {
                    mCacheLimitAsItemCount = 0;
                    mCacheLimitAsMemory = limit * 1024 * 1024;
                    if (cacheManager != null)
                        cacheManager.CacheLimitAsMemory = mCacheLimitAsMemory;
                }
                else if (int.TryParse(slimit, out limit))
                {
                    mCacheLimitAsMemory = 0;
                    mCacheLimitAsItemCount = limit;
                    if (cacheManager != null)
                        cacheManager.CacheLimitAsItemCount = mCacheLimitAsItemCount;
                }
                else
                    throw new ArgumentException("Cache limit must be specified as either the count of thumbnail images or the memory allocated for cache (eg 10MB)", "value");
            }
        }
        /// <summary>
        /// Gets or sets the collection of columns of the image list view.
        /// </summary>
        [Category("Appearance"), Description("Gets the collection of columns of the image list view."), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ImageListViewColumnHeaderCollection Columns { get { return mColumns; } internal set { mColumns = value; Refresh(); } }
        /// <summary>
        /// Gets or sets the placeholder image.
        /// </summary>
        [Category("Appearance"), Description("Gets or sets the placeholder image.")]
        public Image DefaultImage { get { return mDefaultImage; } set { mDefaultImage = value; Refresh(); } }
        /// <summary>
        /// Gets the rectangle that represents the display area of the control.
        /// </summary>
        [Category("Appearance"), Browsable(false), Description("Gets the rectangle that represents the display area of the control.")]
        public override Rectangle DisplayRectangle { get { return layoutManager.ClientArea; } }
        /// <summary>
        /// Gets or sets the error image.
        /// </summary>
        [Category("Appearance"), Description("Gets or sets the error image.")]
        public Image ErrorImage { get { return mErrorImage; } set { mErrorImage = value; Refresh(); } }
        /// <summary>
        /// Gets or sets the font of the column headers.
        /// </summary>
        [Category("Appearance"), Description("Gets or sets the font of the column headers."), DefaultValue(typeof(Font), "Microsoft Sans Serif; 8.25pt")]
        public Font HeaderFont
        {
            get
            {
                return mHeaderFont;
            }
            set
            {
                if (mHeaderFont != null)
                    mHeaderFont.Dispose();
                mHeaderFont = (Font)value.Clone();
                Refresh();
            }
        }
        /// <summary>
        /// Gets the collection of items contained in the image list view.
        /// </summary>
        [Browsable(false), Category("Behavior"), Description("Gets the collection of items contained in the image list view.")]
        public ImageListView.ImageListViewItemCollection Items { get { return mItems; } }
        /// <summary>
        /// Gets or sets the spacing between items.
        /// </summary>
        [Category("Appearance"), Description("Gets or sets the spacing between items."), DefaultValue(typeof(Size), "4,4")]
        public Size ItemMargin { get { return mItemMargin; } set { mItemMargin = value; Refresh(); } }
        /// <summary>
        /// Gets the collection of selected items contained in the image list view.
        /// </summary>
        [Browsable(false), Category("Behavior"), Description("Gets the collection of selected items contained in the image list view.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ImageListView.ImageListViewSelectedItemCollection SelectedItems { get { return mSelectedItems; } }
        /// <summary>
        /// Gets or sets the sort column.
        /// </summary>
        [Category("Appearance"), DefaultValue(typeof(ColumnType), "Name"), Description("Gets or sets the sort column.")]
        public ColumnType SortColumn
        {
            get
            {
                return mSortColumn;
            }
            set
            {
                if (value != mSortColumn)
                {
                    mSortColumn = value;
                    Sort();
                }
            }
        }
        /// <summary>
        /// Gets or sets the sort order.
        /// </summary>
        [Category("Appearance"), DefaultValue(typeof(SortOrder), "None"), Description("Gets or sets the sort order.")]
        public SortOrder SortOrder
        {
            get
            {
                return mSortOrder;
            }
            set
            {
                if (value != mSortOrder)
                {
                    mSortOrder = value;
                    Sort();
                }
            }
        }
        /// <summary>
        /// This property is not relevant for this class.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false), Bindable(false), DefaultValue(null)]
        public override string Text { get; set; }
        /// <summary>
        /// Gets or sets the size of image thumbnails.
        /// </summary>
        [Category("Appearance"), Description("Gets or sets the size of image thumbnails."), DefaultValue(typeof(Size), "96,96")]
        public Size ThumbnailSize
        {
            get
            {
                return mThumbnailSize;
            }
            set
            {
                if (mThumbnailSize != value)
                {
                    mThumbnailSize = value;
                    cacheManager.Clear();
                    Refresh();
                }
            }
        }
        /// <summary>
        /// Gets or sets the embedded thumbnails extraction behavior.
        /// </summary>
        [Category("Behavior"), Description("Gets or sets the embedded thumbnails extraction behavior."), DefaultValue(typeof(UseEmbeddedThumbnails), "Auto")]
        public UseEmbeddedThumbnails UseEmbeddedThumbnails
        {
            get
            {
                return mUseEmbeddedThumbnails;
            }
            set
            {
                if (mUseEmbeddedThumbnails != value)
                {
                    mUseEmbeddedThumbnails = value;
                    cacheManager.Clear();
                    Refresh();
                }
            }
        }
        /// <summary>
        /// Gets or sets the view mode of the image list view.
        /// </summary>
        [Category("Appearance"), Description("Gets or sets the view mode of the image list view."), DefaultValue(typeof(View), "Thumbnails")]
        public View View
        {
            get
            {
                return mView;
            }
            set
            {
                mRenderer.SuspendPaint();
                int current = layoutManager.FirstVisible;
                mView = value;
                layoutManager.Update();
                EnsureVisible(current);
                Refresh();
                mRenderer.ResumePaint();
            }
        }
        /// <summary>
        /// Gets or sets the scroll offset.
        /// </summary>
        internal Point ViewOffset { get { return mViewOffset; } set { mViewOffset = value; } }
        /// <summary>
        /// Gets the scroll orientation.
        /// </summary>
        internal ScrollOrientation ScrollOrientation { get { return (mView == View.Gallery ? ScrollOrientation.HorizontalScroll : ScrollOrientation.VerticalScroll); } }
        /// <summary>
        /// Gets the required creation parameters when the control handle is created.
        /// </summary>
        /// <value></value>
        /// <returns>A CreateParams that contains the required creation parameters.</returns>
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams p = base.CreateParams;
                p.Style &= ~WS_BORDER;
                p.ExStyle &= ~WS_EX_CLIENTEDGE;
                if (mBorderStyle == BorderStyle.Fixed3D)
                    p.ExStyle |= WS_EX_CLIENTEDGE;
                else if (mBorderStyle == BorderStyle.FixedSingle)
                    p.Style |= WS_BORDER;
                return p;
            }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the ImageListView class.
        /// </summary>
        public ImageListView()
        {
            SetRenderer(new ImageListViewRenderer());

            AllowColumnClick = true;
            AllowColumnResize = true;
            AllowDrag = false;
            AllowDuplicateFileNames = false;
            BackColor = SystemColors.Window;
            mBorderStyle = BorderStyle.Fixed3D;
            mCacheLimitAsItemCount = 0;
            mCacheLimitAsMemory = 20 * 1024 * 1024;
            mColumns = new ImageListViewColumnHeaderCollection(this);
            ResourceManager manager = new ResourceManager("Manina.Windows.Forms.ImageListViewResources",
                Assembly.GetExecutingAssembly());
            mDefaultImage = manager.GetObject("DefaultImage") as Image;
            mErrorImage = manager.GetObject("ErrorImage") as Image;
            HeaderFont = this.Font;
            mItems = new ImageListViewItemCollection(this);
            mItemMargin = new Size(4, 4);
            mSelectedItems = new ImageListViewSelectedItemCollection(this);
            mSortColumn = ColumnType.Name;
            mSortOrder = SortOrder.None;
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.Opaque | ControlStyles.Selectable | ControlStyles.UserMouse, true);
            Size = new Size(120, 100);
            mThumbnailSize = new Size(96, 96);
            mUseEmbeddedThumbnails = UseEmbeddedThumbnails.Auto;
            mView = View.Thumbnails;

            mViewOffset = new Point(0, 0);
            hScrollBar = new HScrollBar();
            vScrollBar = new VScrollBar();
            hScrollBar.Visible = false;
            vScrollBar.Visible = false;
            hScrollBar.Scroll += new ScrollEventHandler(hScrollBar_Scroll);
            vScrollBar.Scroll += new ScrollEventHandler(vScrollBar_Scroll);
            Controls.Add(hScrollBar);
            Controls.Add(vScrollBar);
            layoutManager = new ImageListViewLayoutManager(this);
            forceRefresh = false;

            navigationManager = new ImageListViewNavigationManager(this);

            cacheManager = new ImageListViewCacheManager(this);
            itemCacheManager = new ImageListViewItemCacheManager(this);

            disposed = false;
        }
        #endregion

        #region Instance Methods
        /// <summary>
        /// Clears the thumbnail cache.
        /// </summary>
        public void ClearThumbnailCache()
        {
            cacheManager.Clear();
            Refresh();
        }
        /// <summary>
        /// Temporarily suspends the layout logic for the control.
        /// </summary>
        public new void SuspendLayout()
        {
            base.SuspendLayout();
            mRenderer.SuspendPaint(true);
        }
        /// <summary>
        /// Resumes usual layout logic.
        /// </summary>
        public new void ResumeLayout()
        {
            ResumeLayout(false);
        }
        /// <summary>
        /// Resumes usual layout logic, optionally forcing an immediate layout of pending layout requests.
        /// </summary>
        /// <param name="performLayout">true to execute pending layout requests; otherwise, false.</param>
        public new void ResumeLayout(bool performLayout)
        {
            base.ResumeLayout(performLayout);
            if (performLayout) Refresh();
            mRenderer.ResumePaint(true);
        }
        /// <summary>
        /// Sets the properties of the specified column header.
        /// </summary>
        /// <param name="type">The column header to modify.</param>
        /// <param name="text">Column header text.</param>
        /// <param name="width">Width (in pixels) of the column header.</param>
        /// <param name="displayIndex">Display index of the column header.</param>
        /// <param name="visible">true if the column header will be shown; otherwise false.</param>
        public void SetColumnHeader(ColumnType type, string text, int width, int displayIndex, bool visible)
        {
            mRenderer.SuspendPaint();
            ImageListViewColumnHeader col = Columns[type];
            col.Text = text;
            col.Width = width;
            col.DisplayIndex = displayIndex;
            col.Visible = visible;
            Refresh();
            mRenderer.ResumePaint();
        }
        /// <summary>
        /// Sets the properties of the specified column header.
        /// </summary>
        /// <param name="type">The column header to modify.</param>
        /// <param name="width">Width (in pixels) of the column header.</param>
        /// <param name="displayIndex">Display index of the column header.</param>
        /// <param name="visible">true if the column header will be shown; otherwise false.</param>
        public void SetColumnHeader(ColumnType type, int width, int displayIndex, bool visible)
        {
            mRenderer.SuspendPaint();
            ImageListViewColumnHeader col = Columns[type];
            col.Width = width;
            col.DisplayIndex = displayIndex;
            col.Visible = visible;
            Refresh();
            mRenderer.ResumePaint();
        }
        /// <summary>
        /// Sets the renderer for this instance.
        /// </summary>
        public void SetRenderer(ImageListViewRenderer renderer)
        {
            if (renderer == null)
                throw new ArgumentNullException("renderer");

            if (mRenderer != null)
                mRenderer.Dispose();
            mRenderer = renderer;
            mRenderer.mImageListView = this;
            if (layoutManager != null)
                layoutManager.Update(true);
            Refresh();
        }
        /// <summary>
        /// Sorts the items.
        /// </summary>
        public void Sort()
        {
            mItems.Sort();
            Refresh();
        }
        /// <summary>
        /// Marks all items as selected.
        /// </summary>
        public void SelectAll()
        {
            mRenderer.SuspendPaint();

            foreach (ImageListViewItem item in Items)
                item.mSelected = true;

            OnSelectionChangedInternal();

            Refresh();
            mRenderer.ResumePaint();
        }
        /// <summary>
        /// Marks all items as unselected.
        /// </summary>
        public void ClearSelection()
        {
            mRenderer.SuspendPaint();
            mSelectedItems.Clear();
            Refresh();
            mRenderer.ResumePaint();
        }
        /// <summary>
        /// Determines the image list view element under the specified coordinates.
        /// </summary>
        /// <param name="pt">The client coordinates of the point to be tested.</param>
        /// <param name="hitInfo">Details of the hit test.</param>
        public void HitTest(Point pt, out HitInfo hitInfo)
        {
            if (View == View.Details && pt.Y <= mRenderer.MeasureColumnHeaderHeight())
            {
                int i = 0;
                int x = layoutManager.ColumnHeaderBounds.Left;
                ColumnType colIndex = (ColumnType)(-1);
                ColumnType sepIndex = (ColumnType)(-1);
                foreach (ImageListViewColumnHeader col in Columns.GetUIColumns())
                {
                    // Over a column?
                    if (pt.X >= x && pt.X < x + col.Width + SeparatorSize / 2)
                        colIndex = col.Type;

                    // Over a colummn separator?
                    if (pt.X > x + col.Width - SeparatorSize / 2 && pt.X < x + col.Width + SeparatorSize / 2)
                        sepIndex = col.Type;

                    if (colIndex != (ColumnType)(-1)) break;
                    x += col.Width;
                    i++;
                }
                hitInfo = new HitInfo(colIndex, sepIndex);
            }
            else
            {
                int itemIndex = -1;

                // Normalize to item area coordinates
                pt.X -= layoutManager.ItemAreaBounds.Left;
                pt.Y -= layoutManager.ItemAreaBounds.Top;

                if (pt.X > 0 && pt.Y > 0)
                {
                    int col = (pt.X + mViewOffset.X) / layoutManager.ItemSizeWithMargin.Width;
                    int row = (pt.Y + mViewOffset.Y) / layoutManager.ItemSizeWithMargin.Height;

                    if (ScrollOrientation == ScrollOrientation.HorizontalScroll ||
                        (ScrollOrientation == ScrollOrientation.VerticalScroll && col <= layoutManager.Cols))
                    {
                        int index = row * layoutManager.Cols + col;
                        if (index >= 0 && index <= Items.Count - 1)
                        {
                            Rectangle bounds = layoutManager.GetItemBounds(index);
                            if (bounds.Contains(pt.X + layoutManager.ItemAreaBounds.Left, pt.Y + layoutManager.ItemAreaBounds.Top))
                                itemIndex = index;
                        }
                    }
                }

                hitInfo = new HitInfo(itemIndex);
            }
        }
        /// <summary>
        /// Scrolls the image list view to ensure that the item with the specified 
        /// index is visible on the screen.
        /// </summary>
        /// <param name="itemIndex">The index of the item to make visible.</param>
        /// <returns>true if the item was made visible; otherwise false (item is already visible or the image list view is empty).</returns>
        public bool EnsureVisible(int itemIndex)
        {
            if (itemIndex == -1) return false;
            if (Items.Count == 0) return false;

            // Already visible?
            Rectangle bounds = layoutManager.ItemAreaBounds;
            Rectangle itemBounds = layoutManager.GetItemBounds(itemIndex);
            if (!bounds.Contains(itemBounds))
            {
                if (ScrollOrientation == ScrollOrientation.HorizontalScroll)
                {
                    int delta = 0;
                    if (itemBounds.Left < bounds.Left)
                        delta = bounds.Left - itemBounds.Left;
                    else
                    {
                        int topItemIndex = itemIndex - (layoutManager.Cols - 1) * layoutManager.Rows;
                        if (topItemIndex < 0) topItemIndex = 0;
                        delta = bounds.Left - layoutManager.GetItemBounds(topItemIndex).Left;
                    }
                    int newXOffset = mViewOffset.X - delta;
                    if (newXOffset > hScrollBar.Maximum - hScrollBar.LargeChange + 1)
                        newXOffset = hScrollBar.Maximum - hScrollBar.LargeChange + 1;
                    if (newXOffset < hScrollBar.Minimum)
                        newXOffset = hScrollBar.Minimum;
                    mViewOffset.X = newXOffset;
                    mViewOffset.Y = 0;
                    hScrollBar.Value = newXOffset;
                    vScrollBar.Value = 0;
                }
                else
                {
                    int delta = 0;
                    if (itemBounds.Top < bounds.Top)
                        delta = bounds.Top - itemBounds.Top;
                    else
                    {
                        int topItemIndex = itemIndex - (layoutManager.Rows - 1) * layoutManager.Cols;
                        if (topItemIndex < 0) topItemIndex = 0;
                        delta = bounds.Top - layoutManager.GetItemBounds(topItemIndex).Top;
                    }
                    int newYOffset = mViewOffset.Y - delta;
                    if (newYOffset > vScrollBar.Maximum - vScrollBar.LargeChange + 1)
                        newYOffset = vScrollBar.Maximum - vScrollBar.LargeChange + 1;
                    if (newYOffset < vScrollBar.Minimum)
                        newYOffset = vScrollBar.Minimum;
                    mViewOffset.X = 0;
                    mViewOffset.Y = newYOffset;
                    hScrollBar.Value = 0;
                    vScrollBar.Value = newYOffset;
                }
                Refresh();
                return true;
            }
            else
                return false;
        }
        /// <summary>
        /// Determines whether the specified item is visible on the screen.
        /// </summary>
        /// <param name="item">The item to test.</param>
        /// <returns>An ItemVisibility value.</returns>
        public ItemVisibility IsItemVisible(ImageListViewItem item)
        {
            return IsItemVisible(item.Index);
        }
        #endregion

        #region Helper Methods
        /// <summary>
        /// Refreshed the control.
        /// </summary>
        internal void Refresh(bool force)
        {
            forceRefresh = force;
            Refresh();
            forceRefresh = false;
        }
        /// <summary>
        /// Returns the item index after applying the given navigation key.
        /// </summary>
        private int ApplyNavKey(int index, System.Windows.Forms.Keys key)
        {
            if (key == Keys.Up && index >= layoutManager.Cols)
                index -= layoutManager.Cols;
            else if (key == Keys.Down && index < Items.Count - layoutManager.Cols)
                index += layoutManager.Cols;
            else if (key == Keys.Left && index > 0)
                index--;
            else if (key == Keys.Right && index < Items.Count - 1)
                index++;
            else if (key == Keys.PageUp && index >= layoutManager.Cols * (layoutManager.Rows - 1))
                index -= layoutManager.Cols * (layoutManager.Rows - 1);
            else if (key == Keys.PageDown && index < Items.Count - layoutManager.Cols * (layoutManager.Rows - 1))
                index += layoutManager.Cols * (layoutManager.Rows - 1);
            else if (key == Keys.Home)
                index = 0;
            else if (key == Keys.End)
                index = Items.Count - 1;

            if (index < 0)
                index = 0;
            else if (index > Items.Count - 1)
                index = Items.Count - 1;

            return index;
        }
        /// <summary>
        /// Determines whether the specified item is visible on the screen.
        /// </summary>
        /// <param name="guid">The Guid of the item to test.</param>
        /// <returns>true if the item is visible or partially visible; otherwise false.</returns>
        internal bool IsItemVisible(Guid guid)
        {
            return layoutManager.IsItemVisible(guid);
        }
        /// <summary>
        /// Determines whether the specified item is visible on the screen.
        /// </summary>
        /// <param name="itemIndex">The index of the item to test.</param>
        /// <returns>An ItemVisibility value.</returns>
        internal ItemVisibility IsItemVisible(int itemIndex)
        {
            if (mItems.Count == 0) return ItemVisibility.NotVisible;
            if (itemIndex < 0 || itemIndex > mItems.Count - 1) return ItemVisibility.NotVisible;

            if (itemIndex < layoutManager.FirstPartiallyVisible || itemIndex > layoutManager.LastPartiallyVisible)
                return ItemVisibility.NotVisible;
            else if (itemIndex >= layoutManager.FirstVisible && itemIndex <= layoutManager.LastVisible)
                return ItemVisibility.Visible;
            else
                return ItemVisibility.PartiallyVisible;
        }
        /// <summary>
        /// Gets the guids of visible items.
        /// </summary>
        internal Dictionary<Guid, bool> GetVisibleItems()
        {
            Dictionary<Guid, bool> visible = new Dictionary<Guid, bool>();
            if (layoutManager.FirstPartiallyVisible != -1 && layoutManager.LastPartiallyVisible != -1)
            {
                int start = layoutManager.FirstPartiallyVisible;
                int end = layoutManager.LastPartiallyVisible;

                start -= layoutManager.Cols * layoutManager.Rows;
                end += layoutManager.Cols * layoutManager.Rows;

                start = Math.Min(mItems.Count - 1, Math.Max(0, start));
                end = Math.Min(mItems.Count - 1, Math.Max(0, end));

                for (int i = start; i <= end; i++)
                    visible.Add(mItems[i].Guid, false);
            }
            return visible;
        }

        #endregion

        #region Event Handlers
        /// <summary>
        /// Handles the DragOver event.
        /// </summary>
        protected override void OnDragOver(DragEventArgs e)
        {
            navigationManager.DragOver(e);
            base.OnDragOver(e);
        }
        /// <summary>
        /// Handles the DragEnter event.
        /// </summary>
        protected override void OnDragEnter(DragEventArgs e)
        {
            navigationManager.DragEnter(e);
            base.OnDragEnter(e);
        }
        /// <summary>
        /// Handles the DragLeave event.
        /// </summary>
        protected override void OnDragLeave(EventArgs e)
        {
            navigationManager.DragLeave();
            base.OnDragLeave(e);
        }

        /// <summary>
        /// Handles the DragDrop event.
        /// </summary>
        protected override void OnDragDrop(DragEventArgs e)
        {
            navigationManager.DragDrop(e);
            base.OnDragDrop(e);
        }
        /// <summary>
        /// Handles the Scroll event of the vScrollBar control.
        /// </summary>
        private void vScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            mViewOffset.Y = e.NewValue;
            Refresh();
        }
        /// <summary>
        /// Handles the Scroll event of the hScrollBar control.
        /// </summary>
        private void hScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            mViewOffset.X = e.NewValue;
            Refresh();
        }
        /// <summary>
        /// Handles the Resize event.
        /// </summary>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            if (!disposed && mRenderer != null)
                mRenderer.RecreateBuffer();

            if (hScrollBar == null)
                return;

            layoutManager.Update();
            Refresh();
        }
        /// <summary>
        /// Handles the Paint event.
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            if (!disposed && mRenderer != null)
                mRenderer.Refresh(e.Graphics, forceRefresh);
        }
        /// <summary>
        /// Handles the MouseDown event.
        /// </summary>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            // Capture focus if right clicked
            if (!Focused && (e.Button & MouseButtons.Right) == MouseButtons.Right)
                Focus();

            navigationManager.MouseDown(e);
            base.OnMouseDown(e);
        }
        /// <summary>
        /// Handles the MouseUp event.
        /// </summary>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            navigationManager.MouseUp(e);
            base.OnMouseUp(e);
        }
        /// <summary>
        /// Handles the MouseMove event.
        /// </summary>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            navigationManager.MouseMove(e);
            base.OnMouseMove(e);
        }
        /// <summary>
        /// Handles the MouseWheel event.
        /// </summary>
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            mRenderer.SuspendPaint();

            if (ScrollOrientation == ScrollOrientation.VerticalScroll)
            {
                int newYOffset = mViewOffset.Y - (e.Delta / 120) * vScrollBar.SmallChange;
                if (newYOffset > vScrollBar.Maximum - vScrollBar.LargeChange + 1)
                    newYOffset = vScrollBar.Maximum - vScrollBar.LargeChange + 1;
                if (newYOffset < 0)
                    newYOffset = 0;
                int delta = newYOffset - mViewOffset.Y;
                if (newYOffset < vScrollBar.Minimum) newYOffset = vScrollBar.Minimum;
                if (newYOffset > vScrollBar.Maximum) newYOffset = vScrollBar.Maximum;
                mViewOffset.Y = newYOffset;
                vScrollBar.Value = newYOffset;
            }
            else
            {
                int newXOffset = mViewOffset.X - (e.Delta / 120) * hScrollBar.SmallChange;
                if (newXOffset > hScrollBar.Maximum - hScrollBar.LargeChange + 1)
                    newXOffset = hScrollBar.Maximum - hScrollBar.LargeChange + 1;
                if (newXOffset < 0)
                    newXOffset = 0;
                int delta = newXOffset - mViewOffset.X;
                if (newXOffset < hScrollBar.Minimum) newXOffset = hScrollBar.Minimum;
                if (newXOffset > hScrollBar.Maximum) newXOffset = hScrollBar.Maximum;
                mViewOffset.X = newXOffset;
                hScrollBar.Value = newXOffset;
            }

            Refresh();
            mRenderer.ResumePaint();

            base.OnMouseWheel(e);
        }
        /// <summary>
        /// Handles the MouseLeave event.
        /// </summary>
        protected override void OnMouseLeave(EventArgs e)
        {
            navigationManager.MouseLeave();
            base.OnMouseLeave(e);
        }
        /// <summary>
        /// Handles the MouseDoubleClick event.
        /// </summary>
        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            navigationManager.MouseDoubleClick(e);
            base.OnMouseDoubleClick(e);
        }
        /// <summary>
        /// Handles the IsInputKey event.
        /// </summary>
        protected override bool IsInputKey(Keys keyData)
        {
            if ((keyData & Keys.Left) == Keys.Left ||
                (keyData & Keys.Right) == Keys.Right ||
                (keyData & Keys.Up) == Keys.Up ||
                (keyData & Keys.Down) == Keys.Down)
                return true;
            else
                return base.IsInputKey(keyData);
        }
        /// <summary>
        /// Handles the KeyDown event.
        /// </summary>
        protected override void OnKeyDown(KeyEventArgs e)
        {
            navigationManager.KeyDown(e);
            base.OnKeyDown(e);
        }
        /// <summary>
        /// Handles the KeyUp event.
        /// </summary>
        protected override void OnKeyUp(KeyEventArgs e)
        {
            navigationManager.KeyUp(e);
            base.OnKeyUp(e);
        }
        /// <summary>
        /// Handles the GotFocus event.
        /// </summary>
        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            Refresh();
        }
        /// <summary>
        /// Handles the LostFocus event.
        /// </summary>
        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);
            Refresh();
        }
        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.HandleDestroyed"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnHandleDestroyed(EventArgs e)
        {
            cacheManager.Stop();
            itemCacheManager.Stop();
        }
        /// <summary>
        /// Releases the unmanaged resources used by the control and its child controls 
        /// and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; 
        /// false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    if (mRenderer != null)
                        mRenderer.Dispose();

                    if (mHeaderFont != null)
                        mHeaderFont.Dispose();

                    cacheManager.Dispose();
                    itemCacheManager.Dispose();
                    navigationManager.Dispose();
                }

                disposed = true;
            }

            base.Dispose(disposing);
        }
        #endregion

        #region Virtual Functions
        /// <summary>
        /// Raises the ColumnWidthChanged event.
        /// </summary>
        /// <param name="e">A ColumnEventArgs that contains event data.</param>
        protected virtual void OnColumnWidthChanged(ColumnEventArgs e)
        {
            if (ColumnWidthChanged != null)
                ColumnWidthChanged(this, e);
        }
        /// <summary>
        /// Raises the ColumnClick event.
        /// </summary>
        /// <param name="e">A ColumnClickEventArgs that contains event data.</param>
        protected virtual void OnColumnClick(ColumnClickEventArgs e)
        {
            if (ColumnClick != null)
                ColumnClick(this, e);
        }
        /// <summary>
        /// Raises the ItemClick event.
        /// </summary>
        /// <param name="e">A ItemClickEventArgs that contains event data.</param>
        protected virtual void OnItemClick(ItemClickEventArgs e)
        {
            if (ItemClick != null)
                ItemClick(this, e);
        }
        /// <summary>
        /// Raises the ItemDoubleClick event.
        /// </summary>
        /// <param name="e">A ItemClickEventArgs that contains event data.</param>
        protected virtual void OnItemDoubleClick(ItemClickEventArgs e)
        {
            if (ItemDoubleClick != null)
                ItemDoubleClick(this, e);
        }
        /// <summary>
        /// Raises the SelectionChanged event.
        /// </summary>
        /// <param name="e">A EventArgs that contains event data.</param>
        protected virtual void OnSelectionChanged(EventArgs e)
        {
            if (SelectionChanged != null)
                SelectionChanged(this, e);
        }
        /// <summary>
        /// Raises the SelectionChanged event.
        /// </summary>
        internal void OnSelectionChangedInternal()
        {
            OnSelectionChanged(new EventArgs());
        }
        /// <summary>
        /// Raises the ThumbnailCached event.
        /// </summary>
        /// <param name="e">A ItemEventArgs that contains event data.</param>
        protected virtual void OnThumbnailCached(ItemEventArgs e)
        {
            if (ThumbnailCached != null)
                ThumbnailCached(this, e);
        }
        /// <summary>
        /// Raises the ThumbnailCached event.
        /// This method is invoked from the thumbnail thread.
        /// </summary>
        /// <param name="guid">The guid of the item whose thumbnail is cached.</param>
        internal void OnThumbnailCachedInternal(Guid guid)
        {
            int itemIndex = Items.IndexOf(guid);
            if (itemIndex != -1)
                OnThumbnailCached(new ItemEventArgs(Items[itemIndex]));
        }
        /// <summary>
        /// Raises the refresh event.
        /// This method is invoked from the thumbnail thread.
        /// </summary>
        internal void OnRefreshInternal()
        {
            Refresh();
        }
        /// <summary>
        /// Updates item details.
        /// This method is invoked from the item cache thread.
        /// </summary>
        internal void UpdateItemDetailsInternal(ImageListViewItem item, Utility.ShellImageFileInfo info)
        {
            item.UpdateDetailsInternal(info);
        }
        /// <summary>
        /// Raises the ThumbnailCaching event.
        /// </summary>
        /// <param name="e">A ItemEventArgs that contains event data.</param>
        protected virtual void OnThumbnailCaching(ItemEventArgs e)
        {
            if (ThumbnailCaching != null)
                ThumbnailCaching(this, e);
        }
        #endregion

        #region Exposed Events
        /// <summary>
        /// Occurs after the user successfully resized a column header.
        /// </summary>
        [Category("Action"), Browsable(true), Description("Occurs after the user successfully resized a column header.")]
        public event ColumnWidthChangedEventHandler ColumnWidthChanged;
        /// <summary>
        /// Occurs when the user clicks a column header.
        /// </summary>
        [Category("Action"), Browsable(true), Description("Occurs when the user clicks a column header.")]
        public event ColumnClickEventHandler ColumnClick;
        /// <summary>
        /// Occurs when the user clicks an item.
        /// </summary>
        [Category("Action"), Browsable(true), Description("Occurs when the user clicks an item.")]
        public event ItemClickEventHandler ItemClick;
        /// <summary>
        /// Occurs when the user double-clicks an item.
        /// </summary>
        [Category("Action"), Browsable(true), Description("Occurs when the user double-clicks an item.")]
        public event ItemDoubleClickEventHandler ItemDoubleClick;
        /// <summary>
        /// Occurs when the selected items collection changes.
        /// </summary>
        [Category("Behavior"), Browsable(true), Description("Occurs when the selected items collection changes.")]
        public event EventHandler SelectionChanged;
        /// <summary>
        /// Occurs after an item thumbnail is cached.
        /// </summary>
        [Category("Behavior"), Browsable(true), Description("Occurs after an item thumbnail is cached.")]
        public event ThumbnailCachedEventHandler ThumbnailCached;
        /// <summary>
        /// Occurs before an item thumbnail is cached.
        /// </summary>
        [Category("Behavior"), Browsable(true), Description("Occurs before an item thumbnail is cached.")]
        public event ThumbnailCachingEventHandler ThumbnailCaching;
        #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 Apache License, Version 2.0


Written By
Turkey Turkey
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions