PageRenderTime 79ms CodeModel.GetById 22ms app.highlight 45ms RepoModel.GetById 1ms app.codeStats 1ms

/Application/GUI/Controls/ViewDetails.xaml.cs

http://yet-another-music-application.googlecode.com/
C# | 2107 lines | 1265 code | 281 blank | 561 comment | 282 complexity | c1422cf63faa1f0cbaab6c72063f5805 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/**
   2 * ViewDetails.cs
   3 * 
   4 * A modified ListView that looks extremely sexy.
   5 * 
   6 * Features:
   7 *    Drag-n-Drop
   8 *    Column sort
   9 *    Column toggle
  10 *    Icons
  11 *    Strikethrough
  12 *    Active items (graphical highlight)
  13 *    Explorer-like look
  14 *    
  15 * It also sports a convenient storage structure used to
  16 * import and export of the configuration in order to allow 
  17 * easy saving of the configuration between different sessions.
  18 * 
  19 * * * * * * * * *
  20 * 
  21 * This code is part of the Stoffi Music Player Project.
  22 * Visit our website at: stoffiplayer.com
  23 *
  24 * This program is free software; you can redistribute it and/or
  25 * modify it under the terms of the GNU General Public License
  26 * as published by the Free Software Foundation; either version
  27 * 3 of the License, or (at your option) any later version.
  28 * 
  29 * See stoffiplayer.com/license for more information.
  30 **/
  31
  32using System;
  33using System.Collections;
  34using System.Collections.Generic;
  35using System.Collections.Specialized;
  36using System.Collections.ObjectModel;
  37using System.ComponentModel;
  38using System.Linq;
  39using System.Text;
  40using System.Windows;
  41using System.Windows.Controls;
  42using System.Windows.Data;
  43using System.Windows.Documents;
  44using System.Windows.Input;
  45using System.Windows.Media;
  46using System.Windows.Media.Imaging;
  47using System.Windows.Navigation;
  48using System.Windows.Shapes;
  49using System.Windows.Threading;
  50using System.IO;
  51
  52namespace Stoffi
  53{
  54	/// <summary>
  55	/// A modified ListView that looks extremely sexy.
  56	/// 
  57	/// Features:
  58	///     Drag-n-Drop
  59	///     Column sort
  60	///     Column toggle
  61	///     Icons
  62	///     Strikethrough
  63	///     Active items (graphical highlight)
  64	///     Explorer-like look
  65	///     
  66	/// It also sports a convenient storage structure used to
  67	/// import and export of the configuration in order to allow 
  68	/// easy saving of the configuration between different sessions.
  69	/// </summary>
  70	public partial class ViewDetails : ListView
  71	{
  72		#region Members
  73
  74		private ContextMenu headerMenu = new ContextMenu();
  75		private ContextMenu itemMenu = new ContextMenu();
  76		private Hashtable columns = new Hashtable();
  77		private Hashtable columnTable = new Hashtable();
  78		private Hashtable headerMenuTable = new Hashtable();
  79		private GridView columnGrid = new GridView();
  80		private GridViewColumnHeader currentSortColumn = null;
  81		private ListSortDirection currentSortDirection = ListSortDirection.Ascending;
  82		private ViewDetailsDropTarget dropTarget;
  83		private ViewDetailsColumn numberColumn = new ViewDetailsColumn();
  84		private double lastScroll = 0;
  85		private bool hasNumber = false;
  86		private bool isNumberVisible = false;
  87		private int numberIndex = 0;
  88		private bool lockSortOnNumber = false;
  89		private ViewDetailsConfig config = null;
  90		private string filter = "";
  91		private int focusItemIndex = -1;
  92		private bool useAeroHeaders = true;
  93
  94		#endregion Members
  95
  96		#region Properties
  97
  98		/// <summary>
  99		/// Gets or sets whether the list can be sorted by clicking.
 100		/// </summary>
 101		public bool IsClickSortable { get; set; }
 102
 103		/// <summary>
 104		/// Gets or sets whether the list can be sorted by dragging.
 105		/// Will be ignored if LockSortOnNumber is turned on.
 106		/// </summary>
 107		public bool IsDragSortable { get; set; }
 108
 109		/// <summary>
 110		/// Gets or sets whether files can be dropped onto the list.
 111		/// </summary>
 112		public bool AcceptFileDrops { get; set; }
 113
 114		/// <summary>
 115		/// Gets or sets whether to use icons or not (requires an Icon property on the sources).
 116		/// </summary>
 117		public bool UseIcons { get; set; }
 118		
 119		/// <summary>
 120		/// Gets or sets whether the number column is visible (requires the HasNumber property).
 121		/// </summary>
 122		public bool IsNumberVisible
 123		{
 124			get { return isNumberVisible; }
 125			set
 126			{
 127				if (HasNumber)
 128				{
 129					ToggleColumn("#", value);
 130					isNumberVisible = value;
 131					if (config != null)
 132					{
 133						config.IsNumberVisible = value;
 134						config.NumberColumn.IsVisible = value;
 135					}
 136				}
 137			}
 138		}
 139
 140		/// <summary>
 141		/// Gets or sets the position of the number column.
 142		/// </summary>
 143		public int NumberIndex
 144		{
 145			get { return numberIndex; }
 146			set
 147			{
 148				if (HasNumber && numberIndex >= 0)
 149				{
 150					GridViewColumn gvc = columnGrid.Columns[numberIndex];
 151					columnGrid.Columns.RemoveAt(numberIndex);
 152					MenuItem mi = (MenuItem)headerMenu.Items[numberIndex];
 153					headerMenu.Items.RemoveAt(numberIndex);
 154
 155					if (value >= 0)
 156					{
 157						headerMenu.Items.Insert(value, mi);
 158						columnGrid.Columns.Insert(value, gvc);
 159					}
 160				}
 161				numberIndex = value;
 162				if (config != null)
 163					config.NumberIndex = value;
 164			}
 165		}
 166
 167		/// <summary>
 168		/// Gets or sets whether to use a number column (requires a Number property on the sources).
 169		/// </summary>
 170		public bool HasNumber
 171		{
 172			get { return hasNumber; }
 173			set
 174			{
 175				numberColumn.IsVisible = value;
 176				numberColumn.IsAlwaysVisible = value && lockSortOnNumber;
 177				hasNumber = value;
 178				if (value)
 179					AddColumn(numberColumn, NumberIndex, false);
 180				else
 181					RemoveColumn(numberColumn, false);
 182
 183				if (config != null)
 184				{
 185					config.HasNumber = value;
 186					config.NumberColumn.IsVisible = value;
 187					config.NumberColumn.IsAlwaysVisible = value && lockSortOnNumber;
 188				}
 189			}
 190		}
 191
 192		/// <summary>
 193		/// Gets or sets whether to only allow sorting on the number column.
 194		/// Requires HasNumber and IsClickSortable.
 195		/// </summary>
 196		public bool LockSortOnNumber
 197		{
 198			get { return lockSortOnNumber; }
 199			set
 200			{
 201				numberColumn.IsAlwaysVisible = value && hasNumber;
 202				lockSortOnNumber = value;
 203				if (value)
 204				{
 205					if (Items.SortDescriptions.Count > 0)
 206						Items.SortDescriptions.Clear();
 207					Sort(numberColumn, ListSortDirection.Ascending);
 208				}
 209				if (config != null)
 210					config.LockSortOnNumber = value;
 211			}
 212		}
 213
 214		/// <summary>
 215		/// Gets or sets the configuration of the ViewDetails class.
 216		/// This will erase all current configuration.
 217		/// </summary>
 218		public ViewDetailsConfig Config
 219		{
 220			get { return config; }
 221			set
 222			{
 223				if (value != null)
 224				{
 225					// clear current columns
 226					columns.Clear();
 227					columnGrid.Columns.Clear();
 228					columnTable.Clear();
 229					headerMenuTable.Clear();
 230					headerMenu.Items.Clear();
 231
 232					// copy configuration
 233					numberColumn = value.NumberColumn;
 234					IsClickSortable = value.IsClickSortable;
 235					IsDragSortable = value.IsDragSortable;
 236					UseIcons = value.UseIcons;
 237					Filter = value.Filter;
 238					AcceptFileDrops = value.AcceptFileDrops;
 239					SelectIndices(value.SelectedIndices);
 240
 241					value.Columns.CollectionChanged += new NotifyCollectionChangedEventHandler(Columns_CollectionChanged);
 242
 243					// add columns
 244					foreach (ViewDetailsColumn vdc in value.Columns)
 245						AddColumn(vdc, -1, false);
 246
 247					NumberIndex = value.NumberIndex;
 248					HasNumber = value.HasNumber;
 249					IsNumberVisible = value.IsNumberVisible;
 250					LockSortOnNumber = value.LockSortOnNumber;
 251
 252					// apply sorting
 253					if (value.Sorts != null)
 254					{
 255						foreach (string sort in value.Sorts)
 256						{
 257							ListSortDirection dir = sort.Substring(0, 3) == "asc" ? ListSortDirection.Ascending : ListSortDirection.Descending;
 258							string name = sort.Substring(4);
 259							if (name == "Number") name = "#";
 260							Sort(columns[name] as ViewDetailsColumn, dir);
 261						}
 262					}
 263
 264					config = value;
 265					config.PropertyChanged += new PropertyChangedEventHandler(Config_PropertyChanged);
 266				}
 267			}
 268		}
 269
 270		/// <summary>
 271		/// Sets the method that will be used to determine whether a specific item matches
 272		/// a specific string or not.
 273		/// </summary>
 274		public ViewDetailsSearchDelegate FilterMatch { get; set; }
 275
 276		/// <summary>
 277		/// Gets or sets the string that is used to filter items.
 278		/// </summary>
 279		public string Filter
 280		{
 281			get { return filter; }
 282			set
 283			{
 284				filter = value;
 285
 286				if (value == "")
 287					Items.Filter = null;
 288
 289				else if (FilterMatch != null)
 290				{
 291					Items.Filter = delegate(object item)
 292					{
 293						return FilterMatch((ViewDetailsItemData)item, value);
 294					};
 295				}
 296
 297				if (config != null && config.Filter != value)
 298					config.Filter = value;
 299			}
 300		}
 301
 302		/// <summary>
 303		/// Gets or sets whether or not to use Aero styled headers
 304		/// </summary>
 305		public bool UseAeroHeaders
 306		{
 307			get { return useAeroHeaders; }
 308			set
 309			{
 310				useAeroHeaders = value;
 311				columnGrid.ColumnHeaderContainerStyle = value ? (Style)FindResource("AeroHeaderStyle") : null;
 312			}
 313		}
 314
 315		#endregion PropertiesWindow
 316
 317		#region Constructor
 318
 319		/// <summary>
 320		/// Creates an instance of the ViewDetails class.
 321		/// </summary>
 322		public ViewDetails()
 323		{
 324			U.L(LogLevel.Debug, "VIEW DETAILS", "Initialize");
 325			InitializeComponent();
 326			U.L(LogLevel.Debug, "VIEW DETAILS", "Initialized");
 327
 328			// create column headers
 329			UseAeroHeaders = System.Windows.Forms.VisualStyles.VisualStyleInformation.DisplayName != "";
 330			columnGrid.ColumnHeaderContextMenu = headerMenu;
 331			View = columnGrid;
 332
 333			numberColumn.Alignment = System.Windows.HorizontalAlignment.Right;
 334			numberColumn.Binding = "Number";
 335			numberColumn.IsVisible = false;
 336			numberColumn.SortField = "Number";
 337			numberColumn.Text = "#";
 338			numberColumn.Name = "#";
 339			numberColumn.Width = 60;
 340			numberColumn.IsSortable = true;
 341
 342			IsClickSortable = true;
 343			IsDragSortable = true;
 344			AllowDrop = true;
 345			UseIcons = true;
 346			HasNumber = false;
 347			LockSortOnNumber = false;
 348			FilterMatch = null;
 349			AcceptFileDrops = true;
 350
 351			columnGrid.Columns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Columns_CollectionChanged);
 352
 353			if (System.Windows.Forms.VisualStyles.VisualStyleInformation.DisplayName != "")
 354				ItemContainerStyle = (Style)TryFindResource("AeroRowStyle");
 355			else
 356				ItemContainerStyle = (Style)TryFindResource("ClassicRowStyle");
 357		}
 358
 359		#endregion Constructor
 360
 361		#region Methods
 362
 363		#region Public
 364
 365		/// <summary>
 366		/// Adds a column to the list
 367		/// </summary>
 368		/// <param name="column">The column to be added</param>
 369		/// <param name="index">The index to insert at (-1 means last)</param>
 370		/// <param name="addToConfig">Whether the column should be added to the config</param>
 371		public void AddColumn(ViewDetailsColumn column, int index = -1, bool addToConfig = true)
 372		{
 373			// create header
 374			GridViewColumnHeader gvch = new GridViewColumnHeader();
 375			gvch.Tag = column.Binding;
 376			gvch.Content = column.Text;
 377			gvch.HorizontalAlignment = column.Alignment;
 378			gvch.Click += Column_Clicked;
 379			gvch.SizeChanged += Column_SizeChanged;
 380			gvch.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
 381
 382			if (System.Windows.Forms.VisualStyles.VisualStyleInformation.DisplayName != "")
 383			{
 384				gvch.SetResourceReference(GridViewColumnHeader.TemplateProperty, "AeroHeaderTemplate");
 385				gvch.ContentTemplate = (DataTemplate)FindResource("HeaderTemplate");
 386			}
 387
 388			// create column
 389			GridViewColumn gvc = new GridViewColumn();
 390			gvc.Header = gvch;
 391			gvc.CellTemplate = CreateDataTemplate(column.Binding, column.Alignment, false, (UseIcons && columnGrid.Columns.Count == 0));
 392			gvc.Width = column.Width;
 393
 394			// create header menu item
 395			MenuItem mi = new MenuItem();
 396			mi.Header = column.Text;
 397			mi.IsCheckable = !column.IsAlwaysVisible;
 398			mi.Click += new RoutedEventHandler(HeaderMenu_Click);
 399			mi.IsChecked = column.IsVisible;
 400			mi.Tag = column.Name;
 401
 402			columns.Add(column.Name, column);
 403			columnTable.Add(column.Name, gvc);
 404			headerMenuTable.Add(column.Name, mi);
 405
 406			if (index >= 0)
 407				headerMenu.Items.Insert(index, mi);
 408			else
 409				headerMenu.Items.Add(mi);
 410
 411			if (column.IsVisible && index >= 0)
 412				columnGrid.Columns.Insert(index, gvc);
 413			else if (column.IsVisible)
 414				columnGrid.Columns.Add(gvc);
 415
 416			if (config != null && addToConfig)
 417			{
 418				if (config.Columns == null)
 419					config.Columns = new ObservableCollection<ViewDetailsColumn>();
 420				config.Columns.Add(column);
 421			}
 422
 423			RefreshHeaderMenu();
 424
 425			column.PropertyChanged += new PropertyChangedEventHandler(ConfigColumn_PropertyChanged);
 426		}
 427
 428		/// <summary>
 429		/// Adds a column to the list
 430		/// </summary>
 431		/// <param name="name">The name of the column</param>
 432		/// <param name="text">The text to be displayed</param>
 433		/// <param name="binding">The value to bind the column to</param>
 434		/// <param name="sortField">The value to sort on when clicked</param>
 435		/// <param name="width">The width of the column</param>
 436		/// <param name="isSortable">Whether the column is sortable</param>
 437		/// <param name="isAlwaysVisible">Whether the column is always visible</param>
 438		/// <param name="isVisible">Whether the column is visible (only effective if isAlwaysVisible is false)</param>
 439		public void AddColumn(string name, string text, string binding, string sortField, double width, bool isSortable = true, bool isAlwaysVisible = false, bool isVisible = true)
 440		{
 441			ViewDetailsColumn vdc = new ViewDetailsColumn();
 442			vdc.Name = name;
 443			vdc.Text = text;
 444			vdc.Binding = binding;
 445			vdc.SortField = sortField;
 446			vdc.IsAlwaysVisible = isAlwaysVisible;
 447			vdc.Width = width;
 448			vdc.IsVisible = (isVisible || isAlwaysVisible);
 449			vdc.IsSortable = isSortable;
 450			AddColumn(vdc);
 451		}
 452
 453		/// <summary>
 454		/// Adds a column to the list
 455		/// </summary>
 456		/// <param name="name">The name of the column</param>
 457		/// <param name="text">The text to be displayed</param>
 458		/// <param name="binding">The value to bind the column to</param>
 459		/// <param name="width">The width of the column</param>
 460		/// <param name="isSortable">Whether the column is sortable</param>
 461		/// <param name="isAlwaysVisible">Whether the column is always visible</param>
 462		/// <param name="isVisible">Whether the column is visible (only effective if isAlwaysVisible is false)</param>
 463		public void AddColumn(string name, string text, string binding, double width, bool isSortable = true, bool isAlwaysVisible = false, bool isVisible = true)
 464		{
 465			AddColumn(name, text, binding, binding, width, isSortable, isAlwaysVisible, isVisible);
 466		}
 467
 468		/// <summary>
 469		/// Removes a column from the list
 470		/// </summary>
 471		/// <param name="column">The column to remove</param>
 472		/// <param name="removeFromConfig">Whether the column should be removed from the config</param>
 473		public void RemoveColumn(ViewDetailsColumn column, bool removeFromConfig = true)
 474		{
 475			if (headerMenuTable.ContainsKey(column.Text))
 476			{
 477				MenuItem mi = (MenuItem)headerMenuTable[column.Text];
 478				headerMenu.Items.Remove(mi);
 479				headerMenuTable.Remove(column.Text);
 480			}
 481
 482			if (columnTable.ContainsKey(column.Text))
 483			{
 484				GridViewColumn gvc = (GridViewColumn)columnTable[column.Text];
 485				columnGrid.Columns.Remove(gvc);
 486				columnTable.Remove(column.Text);
 487			}
 488
 489			if (columns.ContainsKey(column.Text))
 490			{
 491				ViewDetailsColumn vdc = (ViewDetailsColumn)columns[column.Text];
 492				if (config != null && config.Columns != null && removeFromConfig)
 493					config.Columns.Remove(vdc);
 494				columns.Remove(column.Text);
 495			}
 496		}
 497
 498		/// <summary>
 499		/// Selects a given list of indices of items
 500		/// </summary>
 501		/// <param name="indices">The indices of the items to select</param>
 502		public void SelectIndices(List<int> indices)
 503		{
 504			List<object> itemsToSelect = new List<object>();
 505			foreach (int index in indices)
 506				if (0 <= index && index < Items.Count)
 507					itemsToSelect.Add(Items[index]);
 508			SetSelectedItems(itemsToSelect);
 509			if (itemsToSelect.Count > 0)
 510				ScrollIntoView(itemsToSelect.First<object>());
 511
 512		}
 513
 514		/// <summary>
 515		/// Selects an item, gives it focus and scrolls it into view
 516		/// </summary>
 517		/// <param name="item">The item inside the list</param>
 518		public void SelectItem(ViewDetailsItemData item)
 519		{
 520			if (item == null)
 521				return;
 522
 523			if (Items.Contains(item))
 524				SelectedItem = item;
 525
 526			else
 527			{
 528				foreach (ViewDetailsItemData i in Items)
 529				{
 530					if (i == item)
 531					{
 532						SelectedItem = i;
 533						break;
 534					}
 535				}
 536			}
 537
 538			focusItemIndex = SelectedIndex;
 539			ItemContainerGenerator.ItemsChanged += FocusItem; // in case we have to wait...
 540			FocusItem(null, null);
 541		}
 542
 543		/// <summary>
 544		/// Removes the current sorting
 545		/// </summary>
 546		/// <param name="keepPositions">Whether or not to keep all items at their current position</param>
 547		public void ClearSort(bool keepPositions = false)
 548		{
 549			if (Items.SortDescriptions.Count > 0)
 550			{
 551				// move items in the source so they are in the same
 552				// order as the gui items (which are order by sort conditions)
 553				ObservableCollection<object> items = ItemsSource as ObservableCollection<object>;
 554				for (int j = 0; j < Items.Count; j++)
 555					DispatchMoveItem(Items[j], j);
 556
 557				// remove sort indicators
 558				if (!LockSortOnNumber)
 559				{
 560					Items.SortDescriptions.Clear();
 561					config.Sorts.Clear();
 562					foreach (DictionaryEntry c in columnTable)
 563					{
 564						ViewDetailsColumn vdc = (ViewDetailsColumn)columns[c.Key];
 565						GridViewColumn gvc = (GridViewColumn)c.Value;
 566						gvc.CellTemplate = CreateDataTemplate(vdc.Binding, vdc.Alignment, false, (UseIcons && columnGrid.Columns.IndexOf(gvc) == 0));
 567						((GridViewColumnHeader)((GridViewColumn)c.Value).Header).ContentTemplate = (DataTemplate)FindResource("HeaderTemplate");
 568					}
 569					currentSortColumn = null;
 570					if (SelectedItems.Count > 0)
 571						ScrollIntoView(SelectedItems[0]);
 572				}
 573			}
 574		}
 575
 576		/// <summary>
 577		/// Gets an item source at a given index in the graphical list
 578		/// </summary>
 579		/// <param name="index">The graphical index of the item</param>
 580		/// <returns>The item source</returns>
 581		public ViewDetailsItemData GetItemAt(int index)
 582		{
 583			return Items[index] as ViewDetailsItemData;
 584		}
 585
 586		/// <summary>
 587		/// Returns the graphical index of an item source
 588		/// </summary>
 589		/// <param name="logicalObject">The item source</param>
 590		/// <returns>The graphical index of <paramref name="logicalObject"/></returns>
 591		public int IndexOf(ViewDetailsItemData logicalObject)
 592		{
 593			return Items.IndexOf(logicalObject);
 594		}
 595
 596		/// <summary> 
 597		/// Request the focus to be set on the specified list view item 
 598		/// </summary> 
 599		/// <param name="itemIndex">index of item to receive the initial focus</param>
 600		public void FocusAndSelectItem(int itemIndex)
 601		{
 602			Dispatcher.BeginInvoke(new FocusAndSelectItemDelegate(TryFocusAndSelectItem),
 603				DispatcherPriority.ApplicationIdle, itemIndex);
 604		}
 605
 606		/// <summary>
 607		/// Places focus on the list and the selected items in the list.
 608		/// </summary>
 609		public void Focus()
 610		{
 611			base.Focus();
 612			ListViewItem lvi = ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as ListViewItem;
 613			if (lvi != null)
 614			{
 615				this.ScrollIntoView(lvi);
 616				lvi.IsSelected = true;
 617				Keyboard.ClearFocus();
 618				Keyboard.Focus(lvi);
 619			}
 620		}
 621
 622		#endregion Public
 623
 624		#region Private
 625
 626		/// <summary> 
 627		/// Make sure a list view item is within the visible area of the list view 
 628		/// and then select and set focus to it. 
 629		/// </summary> 
 630		/// <param name="itemIndex">index of item</param> 
 631		private void TryFocusAndSelectItem(int itemIndex)
 632		{
 633			ListViewItem lvi = ItemContainerGenerator.ContainerFromIndex(itemIndex) as ListViewItem;
 634			if (lvi != null)
 635			{
 636				this.ScrollIntoView(lvi);
 637				lvi.IsSelected = true;
 638				Keyboard.ClearFocus();
 639				Keyboard.Focus(lvi);
 640			}
 641		}
 642
 643		/// <summary>
 644		/// Toggles a columns visibility
 645		/// </summary>
 646		/// <param name="name">The name of the column</param>
 647		/// <param name="visible">Whether the column should be visible</param>
 648		private void ToggleColumn(string name, bool visible)
 649		{
 650			MenuItem item = headerMenuTable[name] as MenuItem;
 651			GridViewColumn column = columnTable[name] as GridViewColumn;
 652			ViewDetailsColumn vdc = columns[name] as ViewDetailsColumn;
 653			item.IsChecked = visible;
 654			if (visible)
 655			{
 656				// calculate the position to insert the column based on the position in the context menu
 657				int pos = 0;
 658				foreach (MenuItem mi in columnGrid.ColumnHeaderContextMenu.Items)
 659				{
 660					ViewDetailsColumn c = columns[mi.Tag] as ViewDetailsColumn;
 661					if (c == vdc)
 662						break;
 663					if (c.IsVisible) // only count visible columns
 664						pos++;
 665				}
 666				if (columnGrid.Columns.Contains(column))
 667					columnGrid.Columns.Remove(column);
 668				columnGrid.Columns.Insert(pos, column);
 669			}
 670			else
 671				columnGrid.Columns.Remove(column);
 672
 673			int i = 0;
 674			foreach (GridViewColumn gvc in columnGrid.Columns)
 675			{
 676				string n = (string)((GridViewColumnHeader)gvc.Header).Content;
 677				ViewDetailsColumn col = FindColumn(n);
 678				gvc.CellTemplate = CreateDataTemplate(col.Binding, col.Alignment, (currentSortColumn == (GridViewColumnHeader)gvc.Header), i < 1);
 679				i++;
 680			}
 681
 682			if (HasNumber)
 683			{
 684				numberIndex = columnGrid.Columns.IndexOf((GridViewColumn)columnTable["#"]);
 685				if (config != null)
 686					config.NumberIndex = numberIndex;
 687			}
 688
 689			vdc.IsVisible = item.IsChecked;
 690			RefreshHeaderMenu();
 691		}
 692
 693		/// <summary>
 694		/// Goes through all items in the header menu, if only
 695		/// one column is visible it is disabled, preventing
 696		/// the user from hiding all columns.
 697		/// </summary>
 698		private void RefreshHeaderMenu()
 699		{
 700			// look for a single visible column (if there is any)
 701			ViewDetailsColumn onlyVisible = numberColumn != null && numberColumn.IsVisible ? numberColumn : null;
 702			foreach (ViewDetailsColumn column in columns.Values)
 703				if (column.IsVisible && onlyVisible == null)
 704					onlyVisible = column;
 705				else if (column.IsVisible)
 706				{
 707					onlyVisible = null;
 708					break;
 709				}
 710
 711			// by default allow any column to be toggled
 712			foreach (MenuItem mi in headerMenu.Items)
 713				mi.IsEnabled = true;
 714
 715			// if there's only one single column visible we need
 716			// to disable the ability to hide it
 717			if (onlyVisible != null && headerMenuTable.ContainsKey(onlyVisible.Name))
 718			{
 719				MenuItem mi = headerMenuTable[onlyVisible.Name] as MenuItem;
 720				mi.IsEnabled = false;
 721			}
 722		}
 723
 724		/// <summary>
 725		/// Gives an item focus and scrolls it into view
 726		/// </summary>
 727		/// <param name="sender">The sender of the event</param>
 728		/// <param name="e">The event data</param>
 729		private void FocusItem(object sender, EventArgs e)
 730		{
 731			if (ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
 732			{
 733				ItemContainerGenerator.StatusChanged -= FocusItem;
 734				Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input, new Action(delegate
 735				{
 736					if (focusItemIndex > Items.Count || focusItemIndex < 0) return;
 737					ScrollIntoView(Items.GetItemAt(focusItemIndex));
 738					ListBoxItem item = ItemContainerGenerator.ContainerFromIndex(focusItemIndex) as ListBoxItem;
 739					if (item != null)
 740					{
 741						item.Focus();
 742						focusItemIndex = -1;
 743					}
 744				}));
 745			}
 746		}
 747
 748		/// <summary>
 749		/// Sorts the list
 750		/// </summary>
 751		/// <param name="vdc">The column to sort on</param>
 752		/// <param name="direction">The sort direction</param>
 753		private void Sort(ViewDetailsColumn vdc, ListSortDirection direction)
 754		{
 755			// try to find the corresponding column header
 756			GridViewColumn column = (GridViewColumn)columnTable[vdc.Name];
 757			GridViewColumnHeader header = (GridViewColumnHeader)column.Header;
 758
 759			foreach (DictionaryEntry c in columnTable)
 760			{
 761				string key = c.Key as string;
 762				GridViewColumn gvc = c.Value as GridViewColumn;
 763				GridViewColumnHeader gvch = gvc.Header as GridViewColumnHeader;
 764				ViewDetailsColumn vdc_ = (ViewDetailsColumn)columns[key];
 765				bool active = (key == vdc.Name);
 766				bool rightMost = columnGrid.Columns.IndexOf(gvc) == 0;
 767				gvc.CellTemplate = CreateDataTemplate(vdc_.Binding, vdc_.Alignment, active, rightMost);
 768				string headerTemplate = "HeaderTemplate" + (active ? direction == ListSortDirection.Ascending ? "ArrowUp" : "ArrowDown" : "");
 769				if (System.Windows.Forms.VisualStyles.VisualStyleInformation.DisplayName != "")
 770					gvch.ContentTemplate = (DataTemplate)TryFindResource(headerTemplate);
 771			}
 772
 773			// apply sorting
 774			Items.SortDescriptions.Insert(0, new SortDescription(vdc.SortField, direction));
 775
 776			currentSortColumn = header;
 777			currentSortDirection = direction;
 778
 779			if (SelectedItems.Count > 0)
 780				ScrollIntoView(SelectedItems[0]);
 781		}
 782
 783		/// <summary>
 784		/// Uses certain parameters to create a DataTemplate which can be used as a CellTemplate for a
 785		/// specific column in the list.
 786		/// </summary>
 787		/// <param name="binding">The value to bind to</param>
 788		/// <param name="alignment">Horizontal alignment of the content</param>
 789		/// <param name="active">Whether the column is active or not</param>
 790		/// <param name="rightMost">Whether the column is the right most</param>
 791		/// <returns>DataTemplate to use as a CellTemplate for a column</returns>
 792		private DataTemplate CreateDataTemplate(string binding, HorizontalAlignment alignment, bool active, bool rightMost)
 793		{
 794			FrameworkElementFactory dp = new FrameworkElementFactory(typeof(DockPanel));
 795			dp.SetValue(DockPanel.LastChildFillProperty, true);
 796
 797
 798			if (rightMost && UseIcons)
 799			{
 800				double iconSize = 16.0;
 801				FrameworkElementFactory icon = new FrameworkElementFactory(typeof(Image));
 802				icon.SetBinding(Image.SourceProperty, new Binding("Icon") { Converter = new StringToBitmapImageConverter() });
 803				icon.SetValue(Image.WidthProperty, iconSize);
 804				icon.SetValue(Image.HeightProperty, iconSize);
 805				icon.SetValue(Image.MarginProperty, new Thickness(15, 0, 5, 0));
 806				icon.SetValue(Grid.ColumnProperty, 0);
 807				dp.AppendChild(icon);
 808			}
 809
 810			FrameworkElementFactory tb = new FrameworkElementFactory(typeof(TextBlock));
 811			tb.SetBinding(TextBlock.TextProperty, new Binding(binding));
 812			tb.SetValue(TextBlock.TextTrimmingProperty, TextTrimming.CharacterEllipsis);
 813			tb.SetValue(TextBlock.HorizontalAlignmentProperty, alignment);
 814			tb.SetValue(Grid.ColumnProperty, 1);
 815			if (rightMost && !UseIcons)
 816				tb.SetValue(TextBlock.MarginProperty, new Thickness(15, 0, 5, 0));
 817
 818
 819			if (System.Windows.Forms.VisualStyles.VisualStyleInformation.DisplayName != "")
 820				tb.SetValue(TextBlock.ForegroundProperty, (active ? Brushes.Black : Brushes.Gray));
 821
 822			DataTemplate dt = new DataTemplate();
 823
 824			dp.AppendChild(tb);
 825
 826			dt.VisualTree = dp;
 827			return dt;
 828		}
 829
 830		/// <summary>
 831		/// Find the corresponding column configuration given the content of the column
 832		/// </summary>
 833		/// <param name="content">The displayed text on the column</param>
 834		/// <returns>The column configuration for the column</returns>
 835		private ViewDetailsColumn FindColumn(string content)
 836		{
 837			if (content == "#")
 838				return numberColumn;
 839
 840			foreach (DictionaryEntry i in columns)
 841			{
 842				if (((ViewDetailsColumn)i.Value).Text == content)
 843					return (ViewDetailsColumn)i.Value;
 844			}
 845			return null;
 846		}
 847
 848		#endregion Private
 849
 850		#region Overrides
 851
 852		/// <summary>
 853		/// Creates and return a ViewDetailsItem container.
 854		/// </summary>
 855		/// <returns>A ViewDetailsItem container</returns>
 856		protected override DependencyObject GetContainerForItemOverride()
 857		{
 858			return new ViewDetailsItem();
 859		}
 860
 861		/// <summary>
 862		/// Invoked when the context menu is opening
 863		/// </summary>
 864		/// <param name="e">The event data</param>
 865		protected override void OnContextMenuOpening(ContextMenuEventArgs e)
 866		{
 867			// prevent the context menu from opening if the item under the mouse is not an item
 868			ListViewItem lvi = ViewDetailsUtilities.TryFindParent<ListViewItem>((DependencyObject)e.OriginalSource);
 869			GridViewColumnHeader gvch = ViewDetailsUtilities.TryFindParent<GridViewColumnHeader>((DependencyObject)e.OriginalSource);
 870			if (lvi == null && gvch == null)
 871				e.Handled = true;
 872			else
 873			{
 874				base.OnContextMenuOpening(e);
 875			}
 876		}
 877
 878		/// <summary>
 879		/// Invoked when the ItemsSource property is changed.
 880		/// </summary>
 881		/// <param name="oldValue">The old source</param>
 882		/// <param name="newValue">The new source</param>
 883		/// <remarks>Will enumerate all items and set Number</remarks>
 884		protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
 885		{
 886			// if all numbers are zero we fix the numbers
 887			bool allZero = true;
 888			foreach (ViewDetailsItemData item in newValue)
 889				if (item.Number != 0)
 890				{
 891					allZero = false;
 892					break;
 893				}
 894			int i = 1;
 895			if (allZero)
 896				foreach (ViewDetailsItemData item in newValue)
 897					item.Number = i++;
 898
 899			base.OnItemsSourceChanged(oldValue, newValue);
 900		}
 901
 902		/// <summary>
 903		/// Invoked when the user double-clicks the list
 904		/// </summary>
 905		/// <param name="e">The event data</param>
 906		protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
 907		{
 908			// prevent the context menu from opening if the item under the mouse is not an item
 909			ListViewItem lvi = ViewDetailsUtilities.TryFindParent<ListViewItem>((DependencyObject)e.OriginalSource);
 910			if (lvi == null)
 911				e.Handled = true;
 912			else
 913				base.OnMouseDoubleClick(e);
 914		}
 915
 916		/// <summary>
 917		/// Invoked when something is dropped on the list
 918		/// </summary>
 919		/// <param name="e">The event data</param>
 920		protected override void OnDrop(DragEventArgs e)
 921		{
 922			if (!(e.Data.GetDataPresent(DataFormats.FileDrop) && AcceptFileDrops) &&
 923				!(e.Data.GetDataPresent(typeof(List<object>).FullName) && IsDragSortable))
 924			{
 925				e.Effects = DragDropEffects.None;
 926				return;
 927			}
 928
 929			if (e.Data.GetDataPresent(DataFormats.FileDrop))
 930			{
 931				string[] paths = e.Data.GetData(DataFormats.FileDrop, true) as string[];
 932				ListBoxItem lvi = ViewDetailsUtilities.TryFindFromPoint<ListBoxItem>(this, e.GetPosition(this));
 933				if (lvi != null)
 934				{
 935					int i = this.ItemContainerGenerator.IndexFromContainer(lvi);
 936					if (e.GetPosition(lvi).Y > lvi.RenderSize.Height / 2) i++;
 937					DispatchFilesDropped(paths, i);
 938				}
 939				else
 940					DispatchFilesDropped(paths, Items.Count);
 941			}
 942
 943			else if (e.Data.GetDataPresent(typeof(List<object>).FullName))
 944			{
 945				ListBoxItem lvi = ViewDetailsUtilities.TryFindFromPoint<ListBoxItem>(this, e.GetPosition(this));
 946				if (lvi != null)
 947				{
 948					List<object> items = e.Data.GetData(typeof(List<object>).FullName) as List<object>;
 949					int i = this.ItemContainerGenerator.IndexFromContainer(lvi);
 950					if (e.GetPosition(lvi).Y > lvi.RenderSize.Height / 2) i++;
 951
 952					// items may be out of order so we sort them
 953					List<int> indices = new List<int>();
 954					foreach (object t in items) // put all indices in a list
 955						indices.Add(Items.IndexOf(t));
 956					indices.Sort(); // sort the list
 957					items.Clear();
 958					foreach (int j in indices) // put back all items according to the sorted list
 959						items.Add(Items.GetItemAt(j) as object);
 960
 961					// reorder source and remove GUI sorting
 962					ClearSort(true);
 963
 964					foreach (object t in items)
 965					{
 966						int j = i;
 967						if (Items.IndexOf(t) > i) j++;
 968						DispatchMoveItem(t, i);
 969						i = j;
 970					}
 971
 972					// change number value if we have a number column
 973					if (HasNumber)
 974					{
 975						foreach (ViewDetailsItemData o in Items)
 976						{
 977							o.Number = Items.IndexOf(o) + 1;
 978						}
 979					}
 980
 981					SetSelectedItems(items);
 982				}
 983			}
 984			dropTarget.Visibility = System.Windows.Visibility.Collapsed;
 985		}
 986
 987		/// <summary>
 988		/// Invoked when an item is dragged over the list
 989		/// </summary>
 990		/// <param name="e">The event data</param>
 991		protected override void OnDragOver(DragEventArgs e)
 992		{
 993			if (!(e.Data.GetDataPresent(DataFormats.FileDrop) && AcceptFileDrops) &&
 994				!(e.Data.GetDataPresent(typeof(List<object>).FullName) && IsDragSortable))
 995			{
 996				e.Effects = DragDropEffects.None;
 997				return;
 998			}
 999
1000			ListBoxItem lvi = ViewDetailsUtilities.TryFindFromPoint<ListBoxItem>(this, e.GetPosition(this));
1001			if (lvi != null)
1002			{
1003				ScrollViewer sv = ViewDetailsUtilities.GetVisualChild<ScrollViewer>(this);
1004				if (sv != null && sv.ComputedVerticalScrollBarVisibility == System.Windows.Visibility.Visible)
1005					dropTarget.ScrollBar = true;
1006				else
1007					dropTarget.ScrollBar = false;
1008
1009				dropTarget.Visibility = System.Windows.Visibility.Visible;
1010				Point p = lvi.TranslatePoint(new Point(0, 0), this);
1011				if (e.GetPosition(this).Y < p.Y + (lvi.RenderSize.Height / 2))
1012					dropTarget.Position = p.Y;
1013				else
1014					dropTarget.Position = p.Y + lvi.RenderSize.Height + 1;
1015
1016				double scrollMargin = 50.0;
1017				double scrollStep = 1;
1018				double scrollSpeed = 0.05;
1019				if (sv != null && sv.CanContentScroll && ((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds) - lastScroll > scrollSpeed)
1020				{
1021					if (e.GetPosition(this).Y > this.RenderSize.Height - scrollMargin)
1022					{
1023						sv.ScrollToVerticalOffset(sv.VerticalOffset + scrollStep);
1024						lastScroll = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
1025					}
1026
1027					else if (e.GetPosition(this).Y < scrollMargin + 20.0)
1028					{
1029						sv.ScrollToVerticalOffset(sv.VerticalOffset - scrollStep);
1030						lastScroll = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
1031					}
1032				}
1033			}
1034			else
1035				dropTarget.Visibility = System.Windows.Visibility.Collapsed;
1036		}
1037
1038		/// <summary>
1039		/// Invoked when an unhandled DragLeave attached event reaches an element
1040		/// in its route that is derived from this class
1041		/// </summary>
1042		/// <param name="e">The event data</param>
1043		protected override void OnDragLeave(DragEventArgs e)
1044		{
1045			base.OnDragLeave(e);
1046			Point p = e.GetPosition(this);
1047			double x = p.X / ActualWidth;
1048			double y = p.Y / ActualHeight;
1049			if (x < 0.1 || 0.9 < x || y < 0.1 || 0.9 < y)
1050				dropTarget.Visibility = System.Windows.Visibility.Collapsed;
1051		}
1052
1053		/// <summary>
1054		/// Responds to a list box selection change by raising a SelectionChanged event and
1055		/// saving the selection to the config structure is such as structure is set.
1056		/// </summary>
1057		/// <param name="e">The event data</param>
1058		protected override void OnSelectionChanged(SelectionChangedEventArgs e)
1059		{
1060			if (config != null)
1061			{
1062				config.SelectedIndices.Clear();
1063				foreach (object o in SelectedItems)
1064					config.SelectedIndices.Add(Items.IndexOf(o));
1065			}
1066			base.OnSelectionChanged(e);
1067		}
1068
1069		#endregion Overrides
1070
1071		#region Event handlers
1072
1073		/// <summary>
1074		/// Invoked when the source collection changes
1075		/// </summary>
1076		/// <param name="sender">The sender of the event</param>
1077		/// <param name="e">The event data</param>
1078		public void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
1079		{
1080			switch (e.Action)
1081			{
1082				case NotifyCollectionChangedAction.Remove:
1083				case NotifyCollectionChangedAction.Add:
1084					foreach (ViewDetailsItemData o in Items.SourceCollection)
1085						o.Number = Items.IndexOf(o) + 1;
1086					break;
1087
1088				default:
1089				case NotifyCollectionChangedAction.Reset:
1090					break;
1091			}
1092		}
1093
1094		/// <summary>
1095		/// Invoked when the list is initialized
1096		/// </summary>
1097		/// <param name="sender">The sender of the event</param>
1098		/// <param name="e">The event data</param>
1099		private void ListView_Loaded(object sender, RoutedEventArgs e)
1100		{
1101			dropTarget = new ViewDetailsDropTarget(this);
1102			dropTarget.Visibility = System.Windows.Visibility.Hidden;
1103			AdornerLayer al = AdornerLayer.GetAdornerLayer(this);
1104			if (al != null)
1105				al.Add(dropTarget);
1106		}
1107
1108		/// <summary>
1109		/// Invoked when a property of the config changes
1110		/// </summary>
1111		/// <param name="sender">The sender of the event</param>
1112		/// <param name="e">The event data</param>
1113		private void Config_PropertyChanged(object sender, PropertyChangedEventArgs e)
1114		{
1115			if (e.PropertyName == "Filter")
1116				Filter = config.Filter;
1117		}
1118
1119		/// <summary>
1120		/// Invoked when a menu item in the header context menu is clicked
1121		/// </summary>
1122		/// <param name="sender">The sender of the event</param>
1123		/// <param name="e">The event data</param>
1124		private void HeaderMenu_Click(object sender, RoutedEventArgs e)
1125		{
1126			MenuItem item = sender as MenuItem;
1127			String name = (string)item.Tag;
1128
1129			if (name == "#")
1130				IsNumberVisible = item.IsChecked;
1131			else
1132				ToggleColumn(name, item.IsChecked);
1133
1134			RefreshHeaderMenu();
1135		}
1136
1137		/// <summary>
1138		/// Invoked when the size of a column is changed
1139		/// </summary>
1140		/// <param name="sender">The sender of the event</param>
1141		/// <param name="e">The event data</param>
1142		private void Column_SizeChanged(object sender, SizeChangedEventArgs e)
1143		{
1144			GridViewColumnHeader gvch = sender as GridViewColumnHeader;
1145			ViewDetailsColumn vdc = FindColumn((string)gvch.Content);
1146			if (vdc != null)
1147				vdc.Width = gvch.ActualWidth;
1148		}
1149
1150		/// <summary>
1151		/// Invoked when a column is clicked
1152		/// </summary>
1153		/// <param name="sender">The sender of the event</param>
1154		/// <param name="e">The event data</param>
1155		private void Column_Clicked(object sender, RoutedEventArgs e)
1156		{
1157			GridViewColumnHeader column = sender as GridViewColumnHeader;
1158			ListSortDirection direction;
1159			ViewDetailsColumn vdc = FindColumn((string)column.Content);
1160			if (vdc == null) return;
1161
1162			if (!IsClickSortable || !vdc.IsSortable || (vdc != numberColumn && LockSortOnNumber))
1163				return;
1164
1165			// get direction
1166			if (column != currentSortColumn) direction = ListSortDirection.Ascending;
1167			else if (currentSortDirection == ListSortDirection.Ascending) direction = ListSortDirection.Descending;
1168			else direction = ListSortDirection.Ascending;
1169
1170			// apply sorting
1171			Sort(vdc, direction);
1172
1173			if (config != null)
1174			{
1175				if (config.Sorts == null)
1176					config.Sorts = new List<string>();
1177
1178				// remove previous sorts on this column
1179				string str1 = "asc:" + vdc.Name;
1180				if (config.Sorts.Contains(str1))
1181					config.Sorts.Remove(str1);
1182				string str2 = "dsc:" + vdc.Name;
1183				if (config.Sorts.Contains(str2))
1184					config.Sorts.Remove(str2);
1185
1186				config.Sorts.Add((direction == ListSortDirection.Ascending ? "asc:" : "dsc:") + vdc.Name);
1187
1188			}
1189		}
1190
1191		/// <summary>
1192		/// Invoked when the configuration of columns changed
1193		/// </summary>
1194		/// <param name="sender">The sender of the event</param>
1195		/// <param name="e">The event data</param>
1196		private void ConfigColumns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
1197		{
1198			// TODO: add and remove new columns
1199		}
1200
1201		/// <summary>
1202		/// Invoked when the property of a column changes
1203		/// </summary>
1204		/// <param name="sender">The sender of the event</param>
1205		/// <param name="e">The event data</param>
1206		private void ConfigColumn_PropertyChanged(object sender, PropertyChangedEventArgs e)
1207		{
1208			ViewDetailsColumn vdc = sender as ViewDetailsColumn;
1209
1210			GridViewColumn gvc = columnTable[vdc.Name] as GridViewColumn;
1211			if (gvc == null) return;
1212
1213			// rename headers and menu items
1214			if (e.PropertyName == "Text")
1215			{
1216				GridViewColumnHeader gvch = gvc.Header as GridViewColumnHeader;
1217				gvch.Content = vdc.Text;
1218
1219				MenuItem mi = headerMenuTable[vdc.Name] as MenuItem;
1220				mi.Header = vdc.Text;
1221			}
1222
1223			// TODO: implement other properties
1224		}
1225
1226		/// <summary>
1227		/// Invoked when the columns are reordered
1228		/// </summary>
1229		/// <param name="sender">The sender of the event</param>
1230		/// <param name="e">The event data</param>
1231		private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
1232		{
1233			if (e.Action == NotifyCollectionChangedAction.Move)
1234			{
1235				// move the icon to the far left
1236				if ((e.OldStartingIndex == 0 || e.NewStartingIndex == 0) && UseIcons)
1237				{
1238					int oldIndex = e.OldStartingIndex == 0 ? e.NewStartingIndex : 1;
1239					GridViewColumn oldFirst = columnGrid.Columns[oldIndex];
1240					GridViewColumn newFirst = columnGrid.Columns[0];
1241
1242					ViewDetailsColumn oldVdc = FindColumn((string)((GridViewColumnHeader)oldFirst.Header).Content);
1243					ViewDetailsColumn newVdc = FindColumn((string)((GridViewColumnHeader)newFirst.Header).Content);
1244
1245					bool oldIsActive = IsClickSortable && ((GridViewColumnHeader)oldFirst.Header) == currentSortColumn;
1246					bool newIsActive = IsClickSortable && ((GridViewColumnHeader)newFirst.Header) == currentSortColumn;
1247
1248					oldFirst.CellTemplate = CreateDataTemplate(oldVdc.Binding, oldVdc.Alignment, oldIsActive, false);
1249					newFirst.CellTemplate = CreateDataTemplate(newVdc.Binding, newVdc.Alignment, newIsActive, true);
1250				}
1251
1252				// we may need to rearrange the column order in the config as well
1253				if (config != null)
1254				{
1255					// since we may have a number column in the menu but not in the column
1256					// list we have to compensate the indices accordingly
1257					bool wasNumber = false;
1258					int newAdjust = 0;
1259					int oldAdjust = 0;
1260					if (HasNumber)
1261					{
1262						if (NumberIndex == e.OldStartingIndex)
1263						{
1264							numberIndex = e.NewStartingIndex;
1265							config.NumberIndex = numberIndex;
1266							wasNumber = true;
1267						}
1268						else // adjust indices
1269						{
1270							if (NumberIndex < e.OldStartingIndex && IsNumberVisible) oldAdjust = 1;
1271
1272							// adjust number index
1273							if (e.OldStartingIndex <= NumberIndex && NumberIndex < e.NewStartingIndex && IsNumberVisible) numberIndex--;
1274							else if (e.NewStartingIndex <= NumberIndex && NumberIndex < e.OldStartingIndex && IsNumberVisible) numberIndex++;
1275
1276							if (NumberIndex <= e.NewStartingIndex && IsNumberVisible) newAdjust = 1;
1277						}
1278					}
1279
1280					if (!wasNumber) // the number column is special, not in the list we rearrange
1281					{
1282						ViewDetailsColumn vdc = config.Columns[e.OldStartingIndex - oldAdjust];
1283						config.Columns.Remove(vdc);
1284						config.Columns.Insert(e.NewStartingIndex - newAdjust, vdc);
1285					}
1286				}
1287
1288				MenuItem mi = headerMenu.Items[e.OldStartingIndex] as MenuItem;
1289				headerMenu.Items.Remove(mi);
1290				headerMenu.Items.Insert(e.NewStartingIndex, mi);
1291			}
1292		}
1293
1294		#endregion Event handlers
1295
1296		#region Dispatchers
1297
1298		/// <summary>
1299		/// The dispatcher of the <see cref="ViewDetails.FilesDropped"/> event
1300		/// </summary>
1301		/// <param name="paths">The track that was either added or removed</param>
1302		/// <param name="position">The index where the files where dropped</param>
1303		private void DispatchFilesDropped(string[] paths, int position)
1304		{
1305			if (FilesDropped != null)
1306				FilesDropped(this, new FileDropEventArgs(paths, position));
1307		}
1308
1309		/// <summary>
1310		/// The dispatcher of the <see cref="ViewDetails.MoveItem"/> event
1311		/// </summary>
1312		/// <param name="item">The item that is to be moved</param>
1313		/// <param name="position">The index that the item is to be moved to</param>
1314		private void DispatchMoveItem(object item, int position)
1315		{
1316			if (MoveItem != null)
1317				MoveItem(this, new MoveItemEventArgs(item, position));
1318		}
1319
1320		#endregion
1321
1322		#endregion Methods
1323
1324		#region Events
1325
1326		/// <summary>
1327		/// Occurs when files are dropped on the list.
1328		/// </summary>
1329		public event FileDropEventHandler FilesDropped;
1330
1331		/// <summary>
1332		/// Occurs when an item needs to be moved
1333		/// </summary>
1334		public event MoveItemEventHandler MoveItem;
1335
1336		#endregion
1337	}
1338
1339	#region Delegates
1340
1341	/// <summary>
1342	/// Represents the method that will handle the <see cref="ViewDetails.FilesDropped"/> event.
1343	/// </summary>
1344	/// <param name="sender">The sender of the event</param>
1345	/// <param name="e">The event data</param>
1346	public delegate void FileDropEventHandler(object sender, FileDropEventArgs e);
1347
1348	/// <summary>
1349	/// Represents the method that will handle the <see cref="ViewDetails.MoveItem"/> event.
1350	/// </summary>
1351	/// <param name="sender">The sender of the event</param>
1352	/// <param name="e">The event data</param>
1353	public delegate void MoveItemEventHandler(object sender, MoveItemEventArgs e);
1354
1355	/// <summary>
1356	/// Represents the method that will determine whether an item matches a filter string or not
1357	/// </summary>
1358	/// <param name="item">The item which should be examined</param>
1359	/// <param name="filterString">The string which should be matched</param>
1360	/// <returns>True if the item matches the string, otherwise False</returns>
1361	public delegate bool ViewDetailsSearchDelegate(ViewDetailsItemData item, string filterString);
1362
1363	/// <summary>
1364	/// Represents the method that is called to focus and select an item of the ListView.
1365	/// </summary>
1366	/// <param name="itemIndex">The index of the item to focus and select</param>
1367	public delegate void FocusAndSelectItemDelegate(int itemIndex);
1368
1369	#endregion
1370
1371	#region Event arguments
1372
1373	/// <summary>
1374	/// Provides data for the <see cref="ViewDetails.FilesDropped"/> event
1375	/// </summary>
1376	public class FileDropEventArgs
1377	{
1378		#region Properties
1379		
1380		/// <summary>
1381		/// Gets the paths of the files that were dropped
1382		/// </summary>
1383		public string[] Paths { get; private set; }
1384
1385		/// <summary>
1386		/// Gets the index where the files were dropped
1387		/// </summary>
1388		public int Position { get; private set; }
1389
1390		#endregion
1391
1392		#region Constructor
1393
1394		/// <summary>
1395		/// Initializes a new instance of the <see cref="FileDropEventArgs"/> class
1396		/// </summary>
1397		/// <param name="paths">The paths that was dropped</param>
1398		/// <param name="position">The index where the files where dropped</param>
1399		public FileDropEventArgs(string[] paths, int position)
1400		{
1401			Paths = paths;
1402			Position = position;
1403		}
1404
1405		#endregion
1406	}
1407
1408	/// <summary>
1409	/// Provides data for the <see cref="ViewDetails.MoveItem"/> event
1410	/// </summary>
1411	public class MoveItemEventArgs
1412	{
1413		#region Properties
1414
1415		/// <summary>
1416		/// Gets the paths of the item that is to be moved
1417		/// </summary>
1418		public object Item { get; private set; }
1419
1420		/// <summary>
1421		/// Gets the index that the item is to be moved to
1422		/// </summary>
1423		public int Position { get; private set; }
1424
1425		#endregion
1426
1427		#region Constructor
1428
1429		/// <summary>
1430		/// Initializes a new instance of the <see cref="MoveItemEventArgs"/> class
1431		/// </summary>
1432		/// <param name="item">The item that is to be moved</param>
1433		/// <param name="position">The index that the item is to be moved to</param>
1434		public MoveItemEventArgs(object item, int position)
1435		{
1436			Item = item;
1437			Position = position;
1438		}
1439
1440		#endregion
1441	}
1442
1443	#endregion
1444
1445	#region Data structures
1446
1447	/// <summary>
1448	/// Describes the data source of an item inside the ViewDetails list
1449	/// </summary>
1450	public class ViewDetailsItemData : INotifyPropertyChanged
1451	{
1452		#region Members
1453
1454		private int number;
1455		private bool isActive;
1456		private string icon;
1457		private bool strike;
1458
1459		#endregion
1460
1461		#region Properties
1462
1463		/// <summary>
1464		/// Gets or sets the index number of the item
1465		/// </summary>
1466		public int Number
1467		{
1468			get { return number; }
1469			set { number = value; OnPropertyChanged("Number"); }
1470		}
1471
1472		/// <summary>
1473		/// Gets or sets whether the item is marked as active or not
1474		/// </summary>
1475		public bool IsActive
1476		{
1477			get { return isActive; }
1478			set { isActive = value; OnPropertyChanged("IsActive"); }
1479		}
1480
1481		/// <summary>
1482		/// Gets or sets the icon of the item
1483		/// </summary>
1484		public string Icon
1485		{
1486			get { return icon; }
1487			set { icon = value; OnPropertyChanged("Icon"); }
1488		}
1489
1490		/// <summary>
1491		/// Gets or sets whether the items should feature a strikethrough
1492		/// </summary>
1493		public bool Strike
1494		{
1495			get { return strike; }
1496			set { strike = value; OnPropertyChanged("Strike"); }
1497		}
1498
1499		#endregion
1500
1501		#region INotifyPropertyChanged Members
1502
1503		/// <summary>
1504		/// Occurs when the property of the item is changed
1505		/// </summary>
1506		public event PropertyChangedEventHandler PropertyChanged;
1507
1508		/// <summary>
1509		/// Dispatches the PropertyChanged event
1510		/// </summary>
1511		/// <param name="name">The name of the property that was changed</param>
1512		public void OnPropertyChanged(string name)
1513		{
1514			if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name));
1515		}
1516
1517		#endregion
1518	}
1519
1520	/// <summary>
1521	/// Describes a configuration for the ViewDetails class
1522	/// </summary>
1523	public class ViewDetailsConfig : INotifyPropertyChanged
1524	{
1525		#region Members
1526
1527		string filter = "";
1528
1529		#endregion
1530
1531		#region Properties
1532
1533		/// <summary>
1534		/// Gets or sets the columns
1535		/// </summary>
1536		public ObservableCollection<ViewDetailsColumn> Columns { get; set; }
1537
1538		/// <summary>
1539		/// Gets or sets the number column configuration
1540		/// </summary>
1541		public ViewDetailsColumn NumberColumn { get; set; }
1542
1543		/// <summary>
1544		/// Gets or sets the indices of the selected items
1545		/// </summary>
1546		public List<int> SelectedIndices { get; set; }
1547
1548		/// <summary>
1549		/// Gets or sets the the sort orders
1550		/// Each sort is represented as a string on the format
1551		/// "asc/dsc:ColumnName"
1552		/// </summary>
1553		public List<string> Sorts { get; set; }
1554
1555		/// <summary>
1556		/// Gets or sets text used to filter the list
1557		/// </summary>
1558		public string Filter
1559		{
1560			get { return filter; }
1561			set
1562			{
1563				filter = value;
1564				OnPropertyChanged("Filter");
1565			}
1566		}
1567
1568		/// <summary>
1569		/// Gets or sets whether the number column should be enabled
1570		/// </summary>
1571		public bool HasNumber { get; set; }
1572
1573		/// <summary>
1574		/// Gets or sets whether the number column should be visible
1575		/// </summary…

Large files files are truncated, but you can click here to view the full file