Click here to Skip to main content
15,896,912 members
Articles / Programming Languages / C#

Navigational history (go back/forward) for WinForms controls

Rate me:
Please Sign up or sign in to vote.
4.69/5 (15 votes)
28 Mar 2008CPOL1 min read 76.1K   1K   41  
A generic class and two ToolStripSplitButtons provide navigational history, like in web browsers.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;

// RightToCopy & PublishAndPerish: OrlandoCurioso 2006

namespace OC.Windows.Forms
{
    // Provides navigational history (go back/forward) in conjunction with 2 ToolStripSplitButtons
    public class History<T>
    {
        #region Events & Delegates
        
        // Occurs when user chose from menu or clicked button
        public event EventHandler<HistoryEventArgs<T>> GotoItem;

        // Represents the method which retrieves the menu text for an item.
        // If delegate is null, item's ToString method is used.
        public delegate string GetHistoryMenuText(T item);

        // Represents the method which retrieves the menu image for an item.
        // If delegate is null, none is used.
        public delegate Image GetHistoryMenuImage(T item);

        #endregion

        // forward button may be null
        // limitList == 0 for unlimited history
        public History(ToolStripSplitButton back, ToolStripSplitButton forward, uint limitList)
        { 
            list = new LinkedList<T>();

            cms = new ToolStripDropDownMenu();
            cms.Opening += new CancelEventHandler(cms_Opening);
            cms.Closed += new ToolStripDropDownClosedEventHandler(cms_Closed);
            cms.ItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            cms.RightToLeft = RightToLeft.No;   // if buttons are mirrored

            ensureOpeningOfMenu();

            tssbBack = back;
            tssbForward = forward;
            assignButton(back);
            assignButton(forward);

            limit = (int)limitList;
        }

        #region Fields

        private T _CurrentItem;
        private LinkedList<T> list;
        private LinkedListNode<T> current;

        private ToolStripDropDownMenu cms;
        private ToolStripSplitButton tssbBack;
        private ToolStripSplitButton tssbForward;

        private bool _AllowDuplicates;
        private bool inProc;
        private int limit;

        private GetHistoryMenuText _GetMenuText;
        private GetHistoryMenuImage _GetMenuImage;

        #endregion

        #region Public Interface

        public void Clear()
        {
            _CurrentItem = default(T);
            current = null;
            list.Clear();
            ensureOpeningOfMenu();
            enableButtons();
        }

        public bool AllowDuplicates
        {
            get { return _AllowDuplicates; }
            set { _AllowDuplicates = value; }
        }

        public GetHistoryMenuText MenuTexts
        {
            //get { return _GetMenuText; }
            set { _GetMenuText = value; }
        }

        public GetHistoryMenuImage MenuImages
        {
            //get { return _GetMenuImage; }
            set { _GetMenuImage = value; }
        }

        public T CurrentItem
        {
            get { return _CurrentItem; }
            set 
            { 
                _CurrentItem = value;

                if (!inProc && _CurrentItem != null)
                {
                    addCurrentItem(value);
                }
            }
        }

        #endregion

        #region Private Interface

        private void addCurrentItem( T item)
        {
            if (!_AllowDuplicates && list.Contains(item))
            {
                list.Remove(item);      
            }

            list.AddFirst(item);

            current = list.First;
            limitList();
            enableButtons();
        }

        private void tssb_ButtonClick(object sender, EventArgs e)
        {
            LinkedListNode<T> node = sender.Equals(tssbBack) ? current.Next : current.Previous;

            OnGotoItem(node);

            current = node;
            limitList();
            enableButtons();
        }

        private void cms_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            LinkedListNode<T> node = (LinkedListNode<T>)e.ClickedItem.Tag;

            OnGotoItem(node);

            current = node;
            limitList();
            enableButtons();
        }
        
        // dynamic filling of menu
        private void cms_Opening(object sender, CancelEventArgs e)
        {
            Debug.Assert(list.Count != 0 && current != null);

            cms.Items.Clear();

            ToolStripItem tssb = ((ToolStripDropDownMenu)sender).OwnerItem;
            bool isBackMenu = tssb.Equals(tssbBack);

            LinkedListNode<T> node = isBackMenu ? current.Next : current.Previous;

            while (node != null)
            {
                ToolStripMenuItem tsmi = new ToolStripMenuItem();
                tsmi.Tag = node;

                if (_GetMenuText != null)
                    tsmi.Text = _GetMenuText(node.Value);
                else
                    tsmi.Text = node.Value.ToString();

                if (_GetMenuImage != null)
                    tsmi.Image = _GetMenuImage(node.Value);

                cms.Items.Add(tsmi);

                node = isBackMenu ? node.Next : node.Previous;
            }
        }

        private void cms_Closed(object sender, ToolStripDropDownClosedEventArgs e)
        {
            ensureOpeningOfMenu();
        }

        private void ensureOpeningOfMenu()
        {
            cms.Items.Clear();
            cms.Items.Add("dummy");
        }

        // limiting Back history is sufficient (by design Forward is less or equal to Back)
        private void limitList()
        {
            if (limit != 0 && list.Count > limit)
            {
                LinkedListNode<T> node = current.Next;
                int count = 0;

                while (node != null)
                {
                    if (++count > limit)
                    {
                        list.RemoveLast();
                    }
                    node = node.Next;
                }
            }
        }

        private void assignButton(ToolStripSplitButton tssb)
        {
            if (tssb != null)
            {
                tssb.ButtonClick += new EventHandler(tssb_ButtonClick);
                tssb.DropDown = cms;
                tssb.Enabled = false;
            }
        }

        private void enableButtons()
        {
            if (tssbBack != null) 
                tssbBack.Enabled = (current != null && current.Next != null);
            if (tssbForward != null)
                tssbForward.Enabled = (current != null && current.Previous != null);
        }

        private void OnGotoItem(LinkedListNode<T> node)
        {
            Debug.Assert(node != null && node.Value != null);

            T item = node.Value;

            // block client setting CurrentItem
            inProc = true;

            if (GotoItem != null)
            {
                GotoItem(this, new HistoryEventArgs<T>(item));
            }
            inProc = false;
        }

        #endregion
    }

    // Provides data for the History<T>.GotoItem event
    public class HistoryEventArgs<T> : EventArgs
    {
        public HistoryEventArgs(T item) : base()
        {
            _Item = item;
        }

        private T _Item;

        public T Item { get { return _Item; } }
    }
}

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 Code Project Open License (CPOL)


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

Comments and Discussions