2017-01-16 20:59:50 +01:00
/ * Copyright ( c ) Citrix Systems , Inc .
2013-06-24 13:41:48 +02:00
* All rights reserved .
*
* Redistribution and use in source and binary forms ,
* with or without modification , are permitted provided
* that the following conditions are met :
*
* * Redistributions of source code must retain the above
* copyright notice , this list of conditions and the
* following disclaimer .
* * Redistributions in binary form must reproduce the above
* copyright notice , this list of conditions and the
* following disclaimer in the documentation and / or other
* materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES ,
* INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING ,
* BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS
* INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ,
* WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING
* NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE .
* /
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Drawing ;
using System.Data ;
using System.Text ;
using System.Windows.Forms ;
using System.Windows.Forms.VisualStyles ;
using XenAdmin.Core ;
using XenAPI ;
namespace XenAdmin.Controls
{
public partial class CustomTreeView : FlickerFreeListBox
{
private static readonly log4net . ILog log = log4net . LogManager . GetLogger ( System . Reflection . MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
/// <summary>
/// SURGEON GENERAL'S WARNING: This collection contains the infamous 'secret node'.
/// To iterate only through items that you have explicity added to the treeview, use
/// the Items collection instead.
/// </summary>
public readonly List < CustomTreeNode > Nodes = new List < CustomTreeNode > ( ) ;
private VisualStyleRenderer plusRenderer ;
private VisualStyleRenderer minusRenderer ;
private CustomTreeNode lastSelected ;
private bool _inUpdate = false ;
/// <summary>
/// If you want to make this into a regular listbox, set this to a smaller value, like 5 or something
/// </summary>
private int _nodeIndent = 19 ;
[Browsable(true)]
2018-10-09 12:03:36 +02:00
public virtual int NodeIndent
2013-06-24 13:41:48 +02:00
{
get { return _nodeIndent ; }
set { _nodeIndent = value ; }
}
public CustomTreeNode SecretNode = new CustomTreeNode ( ) ;
private bool _showCheckboxes = true ;
[Browsable(true)]
2018-10-09 12:03:36 +02:00
public virtual bool ShowCheckboxes
2013-06-24 13:41:48 +02:00
{
get { return _showCheckboxes ; }
set { _showCheckboxes = value ; }
}
private bool _showDescription = true ;
[Browsable(true)]
2018-10-09 12:03:36 +02:00
public virtual bool ShowDescription
2013-06-24 13:41:48 +02:00
{
get { return _showDescription ; }
set { _showDescription = value ; }
}
private bool _showImages = false ;
[Browsable(true)]
2018-10-09 12:03:36 +02:00
public virtual bool ShowImages
2013-06-24 13:41:48 +02:00
{
get { return _showImages ; }
set { _showImages = value ; }
}
/// <summary>
/// The font used in descriptions.
/// </summary>
private Font _descriptionFont = null ;
public override Font Font
{
get
{
return base . Font ;
}
set
{
base . Font = value ;
if ( _descriptionFont ! = null )
_descriptionFont . Dispose ( ) ;
_descriptionFont = new Font ( value . FontFamily , value . Size - 1 ) ;
2013-09-26 15:37:47 +02:00
RecalculateWidth ( ) ;
2013-06-24 13:41:48 +02:00
}
}
private bool _showRootLines = true ;
[Browsable(true)]
public bool ShowRootLines
{
get { return _showRootLines ; }
set { _showRootLines = value ; }
}
private bool _rootAlwaysExpanded = false ;
[Browsable(true)]
public bool RootAlwaysExpanded
{
get { return _rootAlwaysExpanded ; }
set { _rootAlwaysExpanded = value ; }
}
public override int ItemHeight { get { return 17 ; } }
public CustomTreeView ( )
{
InitializeComponent ( ) ;
_descriptionFont = new Font ( base . Font . FontFamily , base . Font . Size - 2 ) ;
if ( Application . RenderWithVisualStyles )
{
plusRenderer = new VisualStyleRenderer ( VisualStyleElement . TreeView . Glyph . Closed ) ;
minusRenderer = new VisualStyleRenderer ( VisualStyleElement . TreeView . Glyph . Opened ) ;
}
}
public new void BeginUpdate ( )
{
_inUpdate = true ;
base . BeginUpdate ( ) ;
}
public new void EndUpdate ( )
{
_inUpdate = false ;
base . EndUpdate ( ) ;
RecalculateWidth ( ) ;
Resort ( ) ;
Refresh ( ) ;
}
public new void Invalidate ( )
{
RecalculateWidth ( ) ;
base . Invalidate ( ) ;
}
protected override void OnDrawItem ( DrawItemEventArgs e )
{
base . OnDrawItem ( e ) ;
if ( Enabled )
{
using ( SolidBrush backBrush = new SolidBrush ( BackColor ) )
{
e . Graphics . FillRectangle ( backBrush , e . Bounds ) ;
}
}
else
{
e . Graphics . FillRectangle ( SystemBrushes . Control , e . Bounds ) ;
}
if ( e . Index = = - 1 | | Items . Count < = e . Index )
return ;
CustomTreeNode node = this . Items [ e . Index ] as CustomTreeNode ;
if ( node = = null )
return ;
//int indent = (node.Level + 1) * NodeIndent;
int indent = node . Level * NodeIndent + ( ShowRootLines ? NodeIndent : 2 ) ;
int TextLength = Drawing . MeasureText ( node . ToString ( ) , e . Font ) . Width + 2 ;
int TextLeft = indent + ( ShowCheckboxes & & ! node . HideCheckbox ? ItemHeight : 0 ) + ( ShowImages ? ItemHeight : 0 ) ;
//CA-59618: add top margin to the items except the first one when rendering with
//visual styles because in this case there is already one pixel of margin.
int topMargin = Application . RenderWithVisualStyles & & e . Index = = 0 ? 0 : 1 ;
if ( Enabled & & node . Selectable )
{
Color nodeBackColor = node . Enabled
? e . BackColor
: ( e . BackColor = = BackColor ? BackColor : SystemColors . ControlLight ) ;
using ( SolidBrush backBrush = new SolidBrush ( nodeBackColor ) )
{
e . Graphics . FillRectangle ( backBrush , new Rectangle ( e . Bounds . Left + TextLeft + 1 , e . Bounds . Top + topMargin , TextLength - 4 , e . Bounds . Height ) ) ;
}
}
//draw expander
if ( node . ChildNodes . Count > 0 & & ( ShowRootLines | | node . Level > 0 ) )
{
if ( ! node . Expanded )
{
if ( Application . RenderWithVisualStyles )
plusRenderer . DrawBackground ( e . Graphics , new Rectangle ( e . Bounds . Left + indent - ItemHeight , e . Bounds . Top + 3 + topMargin , 9 , 9 ) ) ;
else
e . Graphics . DrawImage ( Properties . Resources . tree_plus , new Rectangle ( e . Bounds . Left + indent - ItemHeight , e . Bounds . Top + 3 + topMargin , 9 , 9 ) ) ;
}
else
{
if ( Application . RenderWithVisualStyles )
minusRenderer . DrawBackground ( e . Graphics , new Rectangle ( e . Bounds . Left + indent - ItemHeight , e . Bounds . Top + 3 + topMargin , 9 , 9 ) ) ;
else
e . Graphics . DrawImage ( Properties . Resources . tree_minus , new Rectangle ( e . Bounds . Left + indent - ItemHeight , e . Bounds . Top + 3 + topMargin , 9 , 9 ) ) ;
}
}
//draw checkboxes
if ( ShowCheckboxes & & ! node . HideCheckbox )
{
var checkedState = CheckBoxState . UncheckedDisabled ;
if ( node . State = = CheckState . Checked )
{
if ( node . Enabled & & Enabled )
checkedState = CheckBoxState . CheckedNormal ;
else if ( node . CheckedIfdisabled )
checkedState = CheckBoxState . CheckedDisabled ;
}
else if ( node . State = = CheckState . Indeterminate )
{
checkedState = node . Enabled & & Enabled
? CheckBoxState . MixedNormal
: CheckBoxState . MixedDisabled ;
}
else if ( node . State = = CheckState . Unchecked )
{
checkedState = node . Enabled & & Enabled
? CheckBoxState . UncheckedNormal
: CheckBoxState . UncheckedDisabled ;
}
CheckBoxRenderer . DrawCheckBox ( e . Graphics , new Point ( e . Bounds . Left + indent , e . Bounds . Top + 1 + topMargin ) , checkedState ) ;
indent + = ItemHeight ;
}
//draw images
if ( ShowImages & & node . Image ! = null )
{
var rectangle = new Rectangle ( e . Bounds . Left + indent , e . Bounds . Top + topMargin , node . Image . Width , node . Image . Height ) ;
if ( node . Enabled & & Enabled )
e . Graphics . DrawImage ( node . Image , rectangle ) ;
else
e . Graphics . DrawImage ( node . Image , rectangle , 0 , 0 , node . Image . Width , node . Image . Height , GraphicsUnit . Pixel , Drawing . GreyScaleAttributes ) ;
indent + = ItemHeight ;
}
//draw item's main text
Color textColor = node . Enabled & & Enabled
? ( node . Selectable ? e . ForeColor : ForeColor )
: SystemColors . GrayText ;
Drawing . DrawText ( e . Graphics , node . ToString ( ) , e . Font , new Point ( e . Bounds . Left + indent , e . Bounds . Top + topMargin ) , textColor ) ;
indent + = TextLength ;
//draw item's description
if ( ShowDescription )
{
Drawing . DrawText ( e . Graphics , node . Description , _descriptionFont , new Point ( e . Bounds . Left + indent , e . Bounds . Top + 1 + topMargin ) , SystemColors . GrayText ) ;
}
}
public List < CustomTreeNode > CheckedItems ( )
{
List < CustomTreeNode > nodes = new List < CustomTreeNode > ( ) ;
foreach ( CustomTreeNode node in Nodes )
if ( node . Level > = 0 & & node . State = = CheckState . Checked & & node . Enabled )
nodes . Add ( node ) ;
return nodes ;
}
public List < CustomTreeNode > CheckableItems ( )
{
List < CustomTreeNode > nodes = new List < CustomTreeNode > ( ) ;
foreach ( CustomTreeNode node in Nodes )
if ( node . Level > = 0 & & node . State ! = CheckState . Checked & & node . Enabled )
nodes . Add ( node ) ;
return nodes ;
}
public void AddNode ( CustomTreeNode node )
{
if ( Nodes . Count = = 0 )
Nodes . Add ( SecretNode ) ;
SecretNode . AddChild ( node ) ;
Nodes . Add ( node ) ;
if ( ! _inUpdate )
{
RecalculateWidth ( ) ;
Resort ( ) ;
Refresh ( ) ;
}
}
public void RemoveNode ( CustomTreeNode node )
{
Nodes . Remove ( node ) ;
if ( ! _inUpdate )
{
RecalculateWidth ( ) ;
Resort ( ) ;
Refresh ( ) ;
}
}
public void AddChildNode ( CustomTreeNode parent , CustomTreeNode child )
{
parent . AddChild ( child ) ;
Nodes . Add ( child ) ;
if ( ! _inUpdate )
{
RecalculateWidth ( ) ;
Resort ( ) ;
Refresh ( ) ;
}
}
public void ClearAllNodes ( )
{
Nodes . Clear ( ) ;
if ( ! _inUpdate )
{
RecalculateWidth ( ) ;
Resort ( ) ;
Refresh ( ) ;
}
}
public void Resort ( )
{
try
{
lastSelected = SelectedItem as CustomTreeNode ;
}
catch ( IndexOutOfRangeException )
{
// Accessing ListBox.SelectedItem sometimes throws an IndexOutOfRangeException (See CA-24396)
log . Warn ( "IndexOutOfRangeException in ListBox.SelectedItem" ) ;
lastSelected = null ;
}
Nodes . Sort ( ) ;
Items . Clear ( ) ;
foreach ( CustomTreeNode node in Nodes )
{
if ( node . Level ! = - 1 & & node . ParentNode . Expanded )
Items . Add ( node ) ;
}
SelectedItem = lastSelected ;
// I've yet to come across the above assignement working. If we fail to restore the selection, select something so the user can see focus feedback
// (the color of the selected item is the only indication as to whether it is focused or not)
// Iterating through and using CustomTreeNode.equals is useless here as it compares based on index, which I think is why the above call almost never works
if ( SelectedItem = = null & & lastSelected ! = null & & Items . Count > 0 )
{
SelectedItem = Items [ 0 ] ;
}
}
// Adjusts the width of the control to that of the widest row
private void RecalculateWidth ( )
{
int maxWidth = 0 ;
foreach ( CustomTreeNode node in this . Nodes )
{
int indent = ( node . Level + 1 ) * NodeIndent ;
int checkbox = ShowCheckboxes & & ! node . HideCheckbox ? ItemHeight : 0 ;
int image = ShowImages ? ItemHeight : 0 ;
int text = Drawing . MeasureText ( node . ToString ( ) , this . Font ) . Width + 2 ;
int desc = ShowDescription ? Drawing . MeasureText ( node . Description , _descriptionFont ) . Width : 0 ;
int itemWidth = indent + checkbox + image + text + desc + 10 ;
maxWidth = Math . Max ( itemWidth , maxWidth ) ;
}
// Set horizontal extent and enable scrollbar if necessary
this . HorizontalExtent = maxWidth ;
this . HorizontalScrollbar = this . HorizontalExtent > this . Width & & Enabled ;
}
/// <summary>
/// Finds next/previous node in Items collection.
/// </summary>
/// <param name="currentNode">Node where the search for next/previous node will start.</param>
/// <param name="searchForward">Determines direction of search (search for next or previous node).</param>
/// <returns></returns>
protected CustomTreeNode GetNextNode ( CustomTreeNode currentNode , bool searchForward )
{
if ( currentNode = = null )
return null ;
int index = Items . IndexOf ( currentNode ) ;
if ( searchForward )
{
index + + ;
if ( index > = Items . Count )
index = - 1 ;
}
else
index - - ;
if ( index < 0 )
return null ;
return ( CustomTreeNode ) Items [ index ] ;
}
/// <summary>
/// Finds next/previous enabled node in Items collection.
/// </summary>
/// <param name="currentNode">Node where the search for next/previous enabled node will start.</param>
/// <param name="searchForward">Determines direction of search (search for next or previous node).</param>
/// <returns></returns>
protected CustomTreeNode GetNextEnabledNode ( CustomTreeNode currentNode , bool searchForward )
{
if ( currentNode = = null )
return null ;
CustomTreeNode nextNode = GetNextNode ( currentNode , searchForward ) ;
if ( nextNode = = null )
return null ;
if ( nextNode . Enabled )
return nextNode ;
return GetNextEnabledNode ( nextNode , searchForward ) ;
}
protected override void OnMouseUp ( MouseEventArgs e )
{
bool anythingChanged = false ;
bool orderChanged = false ;
Point loc = this . PointToClient ( MousePosition ) ;
int index = this . IndexFromPoint ( loc ) ;
if ( index < 0 | | index > Items . Count )
return ;
CustomTreeNode node = this . Items [ index ] as CustomTreeNode ;
if ( node = = null )
return ;
int indent = node . Level * NodeIndent + ( ShowRootLines ? NodeIndent : 2 ) ;
if ( node . ChildNodes . Count > 0 & & loc . X < indent - ( ItemHeight - 9 ) & & loc . X > indent - ItemHeight & &
( ShowRootLines | | node . Level > 0 ) )
{
node . Expanded = ! node . Expanded ;
node . PreferredExpanded = node . Expanded ;
anythingChanged = true ;
orderChanged = true ;
}
else if ( ShowCheckboxes & & ! node . HideCheckbox & & node . Enabled & & loc . X > indent & & loc . X < indent + ItemHeight )
{
if ( node . State = = CheckState . Unchecked | | node . State = = CheckState . Indeterminate )
node . State = CheckState . Checked ;
else
node . State = CheckState . Unchecked ;
anythingChanged = true ;
}
if ( orderChanged )
Resort ( ) ;
if ( anythingChanged )
{
if ( ItemCheckChanged ! = null )
ItemCheckChanged ( node , new EventArgs ( ) ) ;
Refresh ( ) ;
}
base . OnMouseUp ( e ) ;
}
public event EventHandler < EventArgs > ItemCheckChanged ;
2016-04-18 17:17:01 +02:00
public event EventHandler DoubleClickOnRow ;
2013-06-24 13:41:48 +02:00
protected override void OnMouseDoubleClick ( MouseEventArgs e )
{
2016-04-18 17:17:01 +02:00
base . OnMouseDoubleClick ( e ) ;
2013-06-24 13:41:48 +02:00
bool anythingChanged = false ;
Point loc = this . PointToClient ( MousePosition ) ;
int index = this . IndexFromPoint ( loc ) ;
if ( index < 0 | | index > Items . Count )
return ;
CustomTreeNode node = this . Items [ index ] as CustomTreeNode ;
if ( node = = null )
return ;
int indent = node . Level * NodeIndent + ( ShowRootLines ? NodeIndent : 2 ) ;
if ( node . ChildNodes . Count > 0 & & loc . X < indent - ( ItemHeight - 9 ) & & loc . X > indent - ItemHeight & &
( ShowRootLines | | node . Level > 0 ) )
{
return ;
}
else if ( ShowCheckboxes & & ! node . HideCheckbox & & loc . X > indent & & loc . X < indent + ItemHeight )
{
return ;
}
else if ( node . ChildNodes . Count > 0 & & ( node . Level > 0 | | ! _rootAlwaysExpanded ) )
{
node . Expanded = ! node . Expanded ;
node . PreferredExpanded = node . Expanded ;
anythingChanged = true ;
}
if ( anythingChanged )
{
Resort ( ) ;
Refresh ( ) ;
}
2016-04-18 17:17:01 +02:00
2016-04-20 18:18:46 +02:00
if ( DoubleClickOnRow ! = null )
DoubleClickOnRow ( this , e ) ;
2013-06-24 13:41:48 +02:00
}
protected override void OnKeyUp ( KeyEventArgs e )
{
var node = SelectedItem as CustomTreeNode ;
switch ( e . KeyCode )
{
case Keys . Space :
{
if ( ! ShowCheckboxes )
break ;
if ( node = = null | | node . HideCheckbox | | ! node . Enabled )
break ;
//checked => uncheck it; unchecked or indeterminate => check it
node . State = node . State = = CheckState . Checked ? CheckState . Unchecked : CheckState . Checked ;
Refresh ( ) ;
if ( ItemCheckChanged ! = null )
ItemCheckChanged ( node , new EventArgs ( ) ) ;
break ;
}
}
base . OnKeyUp ( e ) ;
}
protected override void OnKeyDown ( KeyEventArgs e )
{
var node = SelectedItem as CustomTreeNode ;
switch ( e . KeyCode )
{
case Keys . Right :
{
if ( node ! = null & & node . ChildNodes . Count > 0 & & ( node . Level > 0 | | ! _rootAlwaysExpanded ) & & ! node . Expanded )
{
node . Expanded = true ;
Resort ( ) ;
Refresh ( ) ;
e . Handled = true ;
}
break ;
}
case Keys . Left :
{
if ( node ! = null & & node . ChildNodes . Count > 0 & & ( node . Level > 0 | | ! _rootAlwaysExpanded ) & & node . Expanded )
{
node . Expanded = false ;
Resort ( ) ;
Refresh ( ) ;
e . Handled = true ;
}
break ;
}
}
base . OnKeyDown ( e ) ;
}
}
}