|
#region Using Statements
#region .NET Namespace
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
#endregion
#endregion
namespace AddressBarExt.Controls
{
/*
* There is some extra error handling in most of the methods that (in theory) is redundant.
* For performance reasons, you could remove alot of this information.
*
* Additionally, the event handling code is still a bit messy. Particularly NodeButtonClicked.
*
* Finally, a big performance bottleneck is present in the AddToolStripItemUpdate method.
* When it first generates the drop down menus, it hits a bottleneck in creating the collecting of items.
*
* Not sure how to resolve this, as I'm still unsure of how this control meets with the rest of Shinobi :)
*
* -James
*/
/// <summary>
/// Custom Control that acts as a Vista Style Address bar.
///
/// Author : James Strain
/// Email : leon_STARS@hotmail.com
/// Tested Platforms : Windows Vista Ultimate x64 / WinXP Pro 32-bit
///
/// Additional Work Needed :
///
/// Optimization for UI Performance
/// Re-factoring of code based on any optimization
/// Possible removal of 'redundant' error coding to speed up method calls
/// Change caching system to reduce un-used DropDownMenus after period of time (currently retains them forever)
///
/// </summary>
public partial class AddressBarExt : UserControl
{
#region Delegates
/// <summary>
/// Delegate for handling when a new node is selected
/// </summary>
/// <param name="sender">Sender of this event</param>
/// <param name="nca">Event Arguments</param>
public delegate void SelectionChanged(object sender, NodeChangedArgs nca);
/// <summary>
/// Delegate for handling a node double click event.
/// </summary>
/// <param name="sender">Sender of this event</param>
/// <param name="nca">Event arguments</param>
public delegate void NodeDoubleClicked(object sender, NodeChangedArgs nca);
#endregion
#region Event Declaration
/// <summary>
/// Stores the callback function for when the selected node changes
/// </summary>
public event SelectionChanged SelectionChange = null;
/// <summary>
/// Stores the callback function for when the selected node changes
/// </summary>
public event NodeDoubleClicked NodeDoubleClick = null;
#endregion
#region Class Variables
/// <summary>
/// Stores the root node.
/// </summary>
IAddressNode rootNode = null;
/// <summary>
/// Node contains the currently selected node (e.g. previous node)
/// </summary>
IAddressNode currentNode = null;
/// <summary>
/// Stores the default font for this control
/// </summary>
Font baseFont = null;
/// <summary>
/// Holds the font style that is used when an item is selected
/// </summary>
FontStyle selectedStyle = (FontStyle.Bold | FontStyle.Underline);
/// <summary>
/// Drop down menu that contains the overflow menu
/// </summary>
ToolStripDropDownMenu tsddOverflow = null;
#endregion
#region Properties
/// <summary>
/// Gets/Sets the font style for when a node is selected
/// </summary>
public FontStyle SelectedStyle
{
get { return this.selectedStyle; }
set { this.selectedStyle = value; }
}
/// <summary>
/// Gets/Sets the currently selected node. Validates upon set and updates the bar
/// </summary>
public IAddressNode CurrentNode
{
get { return this.currentNode; }
set
{
this.currentNode = value;
UpdateBar();
//fire the change event
if (SelectionChange != null)
{
NodeChangedArgs nca = new NodeChangedArgs(currentNode.UniqueID);
SelectionChange(this, nca);
}
}
}
/// <summary>
/// Gets/Sets the root node. Upon setting the root node, it resets the hierarchy.
/// </summary>
public IAddressNode RootNode
{
get { return this.rootNode; }
set { InitializeRoot(value); }
}
#endregion
#region Constructor/s
/// <summary>
/// Base contructor for the AddressBarExt Control.
/// </summary>
public AddressBarExt()
{
//Windows Forms code
InitializeComponent();
//get the basic font
this.baseFont = this.Font;
}
#endregion
#region Initialization
/// <summary>
/// Initializes this AddressBarExt with a given root node
/// </summary>
/// <param name="rootNode"></param>
public void InitializeRoot(IAddressNode rootNode)
{
//remove all items
ts_bar.Items.Clear();
this.rootNode = null;
if (rootNode != null)
{
//create the root node
this.rootNode = rootNode.Clone();
//force update the node
this.rootNode.UpdateNode();
//set the current node to be the root
this.currentNode = this.rootNode;
//update the address bar
UpdateBar();
}
}
#endregion
#region Event Handlers
/// <summary>
/// Method to handle when a child has been selected from a node
/// </summary>
/// <param name="sender">Sender of this Event</param>
/// <param name="e">Event Arguments</param>
private void NodeButtonClicked(Object sender, EventArgs e)
{
if (sender.GetType() == typeof(ToolStripMenuItem) || sender.GetType() == typeof(ToolStripButton))
{
//get the button
ToolStripItem tsb = (ToolStripItem)sender;
//check we have the right tag type
if (tsb.Tag.GetType() == rootNode.GetType())
{
//set the current node
currentNode = (IAddressNode)tsb.Tag;
//update the address bar
UpdateBar();
//if the selection changed event is handled
if (SelectionChange != null)
{
//throw the event
NodeChangedArgs nca = new NodeChangedArgs(currentNode.UniqueID);
SelectionChange(this, nca);
}
}
return;
}
}
/// <summary>
/// Method to handle when a mode is double clicked
/// </summary>
/// <param name="sender">Sender of this event</param>
/// <param name="e">Event arguments</param>
private void NodeDoubleClickHandler(Object sender, EventArgs e)
{
//check we are handlign the double click event
if (NodeDoubleClick != null && sender.GetType() == typeof(ToolStripButton))
{
//get the node from the tag
currentNode = (IAddressNode)((ToolStripButton)sender).Tag;
//create the node changed event arguments
NodeChangedArgs nca = new NodeChangedArgs(currentNode.UniqueID);
//fire the event
NodeDoubleClick(this, nca);
}
}
/// <summary>
/// Handles when the overflow menu should be entirely destroyed
/// </summary>
/// <param name="sender">Sender of this Event</param>
/// <param name="e">Event arguments</param>
private void OverflowDestroyed(Object sender, EventArgs e)
{
//check we have the right type
if (sender.GetType() == typeof(ToolStripDropDownButton))
{
//get the button as the right type
ToolStripDropDownButton tsddb = (ToolStripDropDownButton)sender;
//if the button is no longer visible
if (!tsddb.Visible)
//clear all items from the overflow
this.tsddOverflow.Items.Clear();
}
}
/// <summary>
/// Method handler using the middle mouse wheel to scroll the drop down menus
/// </summary>
/// <param name="sender">Sender of this event</param>
/// <param name="e">Event arguments</param>
private void ScrollDropDownMenu(Object sender, MouseEventArgs e)
{
//if we have the right type
if (sender.GetType() == typeof(ToolStripDropDownMenu))
{
//This doesn't work :(
Point prev = ((ToolStripDropDownMenu)sender).AutoScrollOffset;
prev.Y += (e.Delta);
((ToolStripDropDownMenu)sender).AutoScrollOffset = prev;
}
}
/// <summary>
/// Method that puts focus onto a given ToolStripDropDownMenu
/// </summary>
/// <param name="sender">Sender of this event</param>
/// <param name="e">Event Arguments</param>
private void GiveToolStripDropDownMenuFocus(Object sender, EventArgs e)
{
if (sender.GetType() == typeof(ToolStripDropDownMenu))
{
//focus on the item
((ToolStripDropDownMenu)sender).Focus();
}
}
#endregion
#region Update
/// <summary>
/// Creates a new tool strip menu item based on a given node
/// </summary>
/// <param name="node">The node to base the item on</param>
/// <returns>Built ToolStripItem. Returns Null if method failed</returns>
private void AddToolStripItemUpdate(ref IAddressNode node, int position)
{
//variables needed for our toolstrip
ToolStripButton tsButton = null;
ToolStripDropDownButton tsddButton = null;
ToolStripDropDownMenu tsDropDown = null;
//update the node
node.UpdateNode();
if (node.Icon != null)
tsButton = new ToolStripButton(node.DisplayName, node.Icon.ToBitmap(), NodeButtonClicked);
else
tsButton = new ToolStripButton(node.DisplayName, null, NodeButtonClicked);
//attach the node as the tag
tsButton.Tag = node;
//align the image
tsButton.ImageAlign = ContentAlignment.TopCenter;
//enable double clicks
tsButton.DoubleClickEnabled = true;
//add the double click handler
tsButton.DoubleClick += new EventHandler(NodeDoubleClickHandler);
if (position < 0)
ts_bar.Items.Add(tsButton);
else
ts_bar.Items.Insert(0, tsButton);
try
{
//if we have any children
if (node.Children.Length > 0)
{
//create the drop down button
tsddButton = new ToolStripDropDownButton("");
//check if we have any tag data (we cache already built drop down items in the node TAG data.
if (node.Tag == null)
{
IAddressNode curNode = null;
//create the drop down menu
tsDropDown = new ToolStripDropDownMenu();
//Some variables to let the drawing happen smoothly
tsDropDown.LayoutStyle = ToolStripLayoutStyle.Table;
tsDropDown.MaximumSize = new Size(1000, 400);
/*
* This is the primary bottleneck for this app, creating all the necessary drop-down menu items.
*
* To optimize performance of this control, this is the main area that needs tuning.
*/
//for all child nodes
for (int i = 0; i < node.Children.Length; i++)
{
curNode = (IAddressNode)node.Children.GetValue(i);
ToolStripMenuItem tsb = null;
//create the button with icon or not
if (curNode.Icon != null)
tsb = new ToolStripMenuItem(curNode.DisplayName, curNode.Icon.ToBitmap(), NodeButtonClicked);
else
tsb = new ToolStripMenuItem(curNode.DisplayName, null, NodeButtonClicked);
//assign the child node as the tag
tsb.Tag = curNode;
//if the node we are working on is the current node
if (curNode == currentNode)
//set the font to indicate it is selected
tsb.Font = new Font(tsb.Font, this.selectedStyle);
//enable overflow on the buttons
tsb.Overflow = ToolStripItemOverflow.AsNeeded;
//add the item to the drop down list
tsDropDown.Items.Add(tsb); //THIS IS THE BIGGEST BOTTLENECK. LOTS OF TIME SPENT IN/CALLING THIS METHOD!
}
//assign the tag
node.Tag = tsDropDown;
//Some variables to let the drawing happen smoothly
tsDropDown.LayoutStyle = ToolStripLayoutStyle.Table;
tsDropDown.MaximumSize = new Size(1000, 400);
//assign the parent
tsDropDown.Tag = tsddButton;
//add the method handler for the mouse wheel scrolling
tsDropDown.MouseWheel += new MouseEventHandler(ScrollDropDownMenu);
//handle the mouse entering/leaving the control
tsDropDown.MouseEnter += new EventHandler(GiveToolStripDropDownMenuFocus);
}
else
{
if(node.Tag.GetType() == typeof(ToolStripDropDownMenu))
{
tsDropDown = (ToolStripDropDownMenu)node.Tag;
foreach (ToolStripMenuItem tsmi in tsDropDown.Items)
{
if (tsmi.Font.Style != baseFont.Style)
tsmi.Font = baseFont;
}
}
}
//assign the drop down list
tsddButton.DropDown = tsDropDown;
//set it to ignore text rendering
tsddButton.DisplayStyle = ToolStripItemDisplayStyle.None;
//align the image
tsddButton.ImageAlign = ContentAlignment.TopCenter;
//add it to the bar
if (position < 0)
ts_bar.Items.Add(tsddButton);
else
ts_bar.Items.Insert(1, tsddButton);
}
}
catch (System.NullReferenceException nrex)
{
System.Console.Error.WriteLine(nrex.Message);
}
}
/// <summary>
/// Updates the address bar
/// </summary>
private void UpdateBar()
{
//check we have a valid root
if (rootNode != null)
{
//update the current node, if it doesn't exist
if (currentNode == null)
currentNode = rootNode;
//now we know we have a root node, we need to build a relationship all the way from the current node, to the root.
IAddressNode tempNode = currentNode;
int steps = 1;
//we step up through each parent until we hit a root node
while (true)
{
//check we aren't root
if(tempNode.Parent != null)
{
tempNode = tempNode.Parent;
steps++;
}else
break;
}
//check we have a valid traversal
if (tempNode != rootNode)
{
//traversal was invalid, so we set the current node to be the root node, and we traverse again
currentNode = rootNode;
//recursive call
UpdateBar();
}
else
{
//set the current node
tempNode = currentNode;
//remove all the items
ts_bar.Items.Clear();
//we had a valid node, so we can start adding elements to the view
for (int i = 0; i < steps; i++)
{
//add it to the beginning of the bar ;)
AddToolStripItemUpdate(ref tempNode, 0);
if (tempNode.Parent == null)
rootNode = tempNode;
tempNode = tempNode.Parent;
}
//check if we have too many nodes
if (TooManyNodes())
{
//create the drop down menu for the overflow
CreateOverflowDropDown();
AddOverflow();
}
}
}
}
/// <summary>
/// Method to create the overflow element for handling
/// </summary>
private void CreateOverflowDropDown()
{
if (ts_bar != null)
{
//ensure we have an overflow
if (this.tsddOverflow == null)
{
tsddOverflow = new ToolStripDropDownMenu();
tsddOverflow.MaximumSize = new Size(1000, 400);
}
ToolStripMenuItem currentDrop = null, tsi = null;
ToolStripItem currentItem = null;
//for each item in the bar
for(int i= ts_bar.Items.Count -1; i>-1; i--)
{
tsi = new ToolStripMenuItem(ts_bar.Items[i].Text,ts_bar.Items[i].Image,NodeButtonClicked);
tsi.Tag = ts_bar.Items[i].Tag;
//if we have a valid button
if (ts_bar.Items[i].GetType() == typeof(ToolStripButton))
//if we have a valid tag
if (tsi.Tag.GetType() == currentNode.GetType())
//for all nodes but the current node
if (((IAddressNode)tsi.Tag) != currentNode)
{
//if our overflow doesn't already contain this item
if(!tsddOverflow.Items.Contains(tsi))
//add the item to the overflow
tsddOverflow.Items.Add(tsi);
}
else
{
currentItem = ts_bar.Items[i];
if (currentNode.Children.Length > 0)
{
currentDrop = new ToolStripMenuItem(ts_bar.Items[i + 1].Text, ts_bar.Items[i + 1].Image, NodeButtonClicked);
tsi.Tag = ts_bar.Tag;
}
}
}
//clear all items from the bar
ts_bar.Items.Clear();
//add the current item
ts_bar.Items.Add(currentItem);
//add the current drop down button, if applicable
if(currentDrop != null)
ts_bar.Items.Add(currentDrop);
}
}
/// <summary>
/// Method adds the overflow menu to the drop down list
/// </summary>
private void AddOverflow()
{
//if we have a valid overflow
if (tsddOverflow != null)
{
//create the overflow button
ToolStripDropDownButton tsd = new ToolStripDropDownButton("..");
//add the drop down
tsd.DropDown = tsddOverflow;
//add that to the bar
ts_bar.Items.Insert(0,tsd);
//add the event handler for destruction
tsd.OwnerChanged += new EventHandler(OverflowDestroyed);
}
}
/// <summary>
/// Method that calculates if we have too many nodes in our address bar
/// </summary>
/// <returns>Boolean indicating </returns>
private bool TooManyNodes()
{
//check if the last item has overflowed
return ts_bar.Items[ts_bar.Items.Count - 1].IsOnOverflow;
}
#endregion
}
/// <summary>
/// Custom Event Arguments for when a node has been changed
/// </summary>
public class NodeChangedArgs : EventArgs
{
#region Class Variables
/// <summary>
/// Stores the Unique ID of the newly opened node
/// </summary>
private object oUniqueId = null;
#endregion
#region Properties
/// <summary>
/// Gets the UniqueID from the newly opened node
/// </summary>
public object OUniqueID
{
get { return this.oUniqueId; }
}
#endregion
#region Constructor
/// <summary>
/// Base constructor for when a node selection is changed
/// </summary>
/// <param name="uniqueId">Unique Identifier for this node. Controled by IAddressNode implementation used.</param>
public NodeChangedArgs(object uniqueId)
{
//set the values for the args
this.oUniqueId = uniqueId;
}
#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.