PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

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