/Application/GUI/Utilities.cs
C# | 792 lines | 416 code | 129 blank | 247 comment | 96 complexity | 5adac6eafb4d56feb561359897f2415f MD5 | raw file
1/** 2 * Utilities.cs 3 * 4 * An extension of the Utilities class in Core that provides 5 * various GUI related utilities for the Windows 7 GUI. 6 * 7 * * * * * * * * * 8 * 9 * This code is part of the Stoffi Music Player Project. 10 * Visit our website at: stoffiplayer.com 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 15 * 3 of the License, or (at your option) any later version. 16 * 17 * See stoffiplayer.com/license for more information. 18 **/ 19 20using System; 21using System.Collections.Generic; 22using System.Windows.Controls; 23using System.Diagnostics; 24using System.Globalization; 25using System.Linq; 26using System.Text; 27using System.Runtime.InteropServices; 28using System.Runtime.CompilerServices; 29using System.Windows; 30using System.Windows.Data; 31using System.Windows.Input; 32using System.Windows.Media; 33using System.Windows.Media.Imaging; 34using System.Media; 35 36namespace Stoffi 37{ 38 /// <summary> 39 /// A utilities class with helper method for GUI related stuff 40 /// </summary> 41 static partial class Utilities 42 { 43 /// <summary> 44 /// Extracts a specific image from an ico 45 /// file given it's size. 46 /// If no exact size is matched, the largest 47 /// image will be returned. 48 /// </summary> 49 /// <param name="path">The path to the ico</param> 50 /// <param name="width">The prefered width</param> 51 /// <param name="height">The prefered height</param> 52 /// <returns>An image from inside the ico file</returns> 53 public static BitmapFrame GetIcoImage(string path, int width, int height) 54 { 55 var iconUri = new Uri(path, UriKind.RelativeOrAbsolute); 56 try 57 { 58 59 var iconDecoder = new IconBitmapDecoder(iconUri, 60 BitmapCreateOptions.None, BitmapCacheOption.Default); 61 62 // no image found 63 if (iconDecoder.Frames.Count == 0) return null; 64 65 BitmapFrame largest = iconDecoder.Frames[0]; 66 foreach (BitmapFrame frame in iconDecoder.Frames) 67 { 68 if (frame.PixelHeight == height && 69 frame.PixelWidth == width) 70 { 71 return frame; 72 } 73 74 if (frame.PixelWidth * frame.PixelHeight > 75 largest.PixelWidth * frame.PixelHeight) 76 largest = frame; 77 } 78 79 return largest; 80 } 81 catch (Exception) 82 { 83 return null; 84 } 85 } 86 87 /// <summary>Finds a parent of a given item on the visual tree.</summary> 88 /// <typeparam name="T">The type of the queried item.</typeparam> 89 /// <param name="iChild">A direct or indirect child of the queried item.</param> 90 /// <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> 91 public static T TryFindParent<T>(this DependencyObject iChild) 92 where T : DependencyObject 93 { 94 // Get parent item. 95 DependencyObject parentObject = GetParentObject(iChild); 96 97 // We've reached the end of the tree. 98 if (parentObject == null) 99 return null; 100 101 // Check if the parent matches the type we're looking for. 102 // Else use recursion to proceed with next level. 103 T parent = parentObject as T; 104 return parent ?? TryFindParent<T>(parentObject); 105 } 106 107 /// <summary> 108 /// This method is an alternative to WPF's <see cref="VisualTreeHelper.GetParent"/> method, which also 109 /// supports content elements. Keep in mind that for content element, this method falls back to the logical tree of the element! 110 /// </summary> 111 /// <param name="iChild">The item to be processed.</param> 112 /// <returns>The submitted item's parent, if available. Otherwise null.</returns> 113 public static DependencyObject GetParentObject(this DependencyObject iChild) 114 { 115 if (iChild == null) 116 { 117 return null; 118 } 119 120 // Handle content elements separately. 121 ContentElement contentElement = iChild as ContentElement; 122 if (contentElement != null) 123 { 124 DependencyObject parent = ContentOperations.GetParent(contentElement); 125 if (parent != null) return parent; 126 127 FrameworkContentElement frameworkContentElement = contentElement as FrameworkContentElement; 128 return frameworkContentElement != null ? frameworkContentElement.Parent : null; 129 } 130 131 // Also try searching for parent in framework elements (such as DockPanel, etc). 132 FrameworkElement frameworkElement = iChild as FrameworkElement; 133 if (frameworkElement != null) 134 { 135 DependencyObject parent = frameworkElement.Parent; 136 if (parent != null) return parent; 137 } 138 139 // If it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper. 140 return VisualTreeHelper.GetParent(iChild); 141 } 142 143 /// <summary>Tries to locate a given item within the visual tree, starting with the dependency object at a given position.</summary> 144 /// <typeparam name="T">The type of the element to be found on the visual tree of the element at the given location.</typeparam> 145 /// <param name="iReference">The main element which is used to perform hit testing.</param> 146 /// <param name="iPoint">The position to be evaluated on the origin.</param> 147 public static T TryFindFromPoint<T>(this UIElement iReference, Point iPoint) where T : DependencyObject 148 { 149 DependencyObject element = iReference.InputHitTest(iPoint) as DependencyObject; 150 if (element == null) 151 { 152 return null; 153 } 154 else if (element is T) 155 return (T)element; 156 else 157 return TryFindParent<T>(element); 158 } 159 160 /// <summary> 161 /// Tries to locate a child of a given item within the visual tree 162 /// </summary> 163 /// <typeparam name="T">The type of the element to be found on the visual tree of the parent to the element</typeparam> 164 /// <param name="referenceVisual">A direct or indirect parent of the element to be found</param> 165 /// <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> 166 public static T GetVisualChild<T>(Visual referenceVisual) where T : Visual 167 { 168 Visual child = null; 169 for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++) 170 { 171 child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual; 172 if (child != null && (child.GetType() == typeof(T))) 173 { 174 break; 175 } 176 else if (child != null) 177 { 178 child = GetVisualChild<T>(child); 179 if (child != null && (child.GetType() == typeof(T))) 180 { 181 break; 182 } 183 } 184 } 185 return child as T; 186 } 187 } 188 189 #region Description converters 190 191 /// <summary> 192 /// 193 /// </summary> 194 class OpenFileAddDescriptionConverter : IValueConverter 195 { 196 private string OpenFile_NoAdd = U.T("GeneralAddPolicyDont", "Content"); 197 private string OpenFile_Add2Lib = U.T("GeneralAddPolicyLibrary", "Content"); 198 private string OpenFile_Add2Pl = U.T("GeneralAddPolicyBoth", "Content"); 199 200 /// <summary> 201 /// 202 /// </summary> 203 /// <param name="value"></param> 204 /// <param name="targetType"></param> 205 /// <param name="parameter"></param> 206 /// <param name="culture"></param> 207 /// <returns></returns> 208 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 209 { 210 if (value == null) 211 return null; 212 213 OpenAddPolicy source = (OpenAddPolicy)Enum.Parse(typeof(OpenAddPolicy), value.ToString()); 214 string target; 215 216 switch (source) 217 { 218 case OpenAddPolicy.DoNotAdd: 219 target = OpenFile_NoAdd; 220 break; 221 222 case OpenAddPolicy.Library: 223 default: 224 target = OpenFile_Add2Lib; 225 break; 226 227 case OpenAddPolicy.LibraryAndPlaylist: 228 target = OpenFile_Add2Pl; 229 break; 230 } 231 232 return target; 233 } 234 235 /// <summary> 236 /// 237 /// </summary> 238 /// <param name="value"></param> 239 /// <param name="targetType"></param> 240 /// <param name="parameter"></param> 241 /// <param name="culture"></param> 242 /// <returns></returns> 243 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 244 { 245 if (value == null) 246 return null; 247 248 string source = value.ToString(); 249 250 if (source == OpenFile_NoAdd) 251 return OpenAddPolicy.DoNotAdd; 252 253 else if (source == OpenFile_Add2Pl) 254 return OpenAddPolicy.LibraryAndPlaylist; 255 256 else 257 return OpenAddPolicy.Library; 258 } 259 } 260 261 /// <summary> 262 /// 263 /// </summary> 264 class OpenFilePlayDescriptionConverter : IValueConverter 265 { 266 private string OpenFile_Play = U.T("GeneralPlayPolicyPlay", "Content"); 267 private string OpenFile_DoNotPlay = U.T("GeneralPlayPolicyDont", "Content"); 268 private string OpenFile_BackOfQueue = U.T("GeneralPlayPolicyBack", "Content"); 269 private string OpenFile_FrontOfQueue = U.T("GeneralPlayPolicyFront", "Content"); 270 271 /// <summary> 272 /// 273 /// </summary> 274 /// <param name="value"></param> 275 /// <param name="targetType"></param> 276 /// <param name="parameter"></param> 277 /// <param name="culture"></param> 278 /// <returns></returns> 279 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 280 { 281 if (value == null) 282 return null; 283 284 OpenPlayPolicy source = (OpenPlayPolicy)Enum.Parse(typeof(OpenPlayPolicy), value.ToString()); 285 string target; 286 287 switch (source) 288 { 289 case OpenPlayPolicy.BackOfQueue: 290 default: 291 target = OpenFile_BackOfQueue; 292 break; 293 294 case OpenPlayPolicy.FrontOfQueue: 295 target = OpenFile_FrontOfQueue; 296 break; 297 298 case OpenPlayPolicy.DoNotPlay: 299 target = OpenFile_DoNotPlay; 300 break; 301 302 case OpenPlayPolicy.Play: 303 target = OpenFile_Play; 304 break; 305 } 306 307 return target; 308 } 309 310 /// <summary> 311 /// 312 /// </summary> 313 /// <param name="value"></param> 314 /// <param name="targetType"></param> 315 /// <param name="parameter"></param> 316 /// <param name="culture"></param> 317 /// <returns></returns> 318 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 319 { 320 if (value == null) 321 return null; 322 323 string source = value.ToString(); 324 325 if (source == OpenFile_Play) 326 return OpenPlayPolicy.Play; 327 328 else if (source == OpenFile_FrontOfQueue) 329 return OpenPlayPolicy.FrontOfQueue; 330 331 else if (source == OpenFile_DoNotPlay) 332 return OpenPlayPolicy.DoNotPlay; 333 334 else 335 return OpenPlayPolicy.BackOfQueue; 336 } 337 } 338 339 /// <summary> 340 /// 341 /// </summary> 342 class UpgradePolicyDescriptionConverter : IValueConverter 343 { 344 private string UpgradePolicy_Automatic = U.T("GeneralUpgradePolicyAuto", "Content"); 345 private string UpgradePolicy_Notify = U.T("GeneralUpgradePolicyNotify", "Content"); 346 private string UpgradePolicy_Manual = U.T("GeneralUpgradePolicyManual", "Content"); 347 348 /// <summary> 349 /// 350 /// </summary> 351 /// <param name="value"></param> 352 /// <param name="targetType"></param> 353 /// <param name="parameter"></param> 354 /// <param name="culture"></param> 355 /// <returns></returns> 356 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 357 { 358 if (value == null) 359 return null; 360 361 UpgradePolicy source = (UpgradePolicy)Enum.Parse(typeof(UpgradePolicy), value.ToString()); 362 string target; 363 364 switch (source) 365 { 366 case UpgradePolicy.Automatic: 367 default: 368 target = UpgradePolicy_Automatic; 369 break; 370 371 case UpgradePolicy.Notify: 372 target = UpgradePolicy_Notify; 373 break; 374 375 case UpgradePolicy.Manual: 376 target = UpgradePolicy_Manual; 377 break; 378 } 379 380 return target; 381 } 382 383 /// <summary> 384 /// 385 /// </summary> 386 /// <param name="value"></param> 387 /// <param name="targetType"></param> 388 /// <param name="parameter"></param> 389 /// <param name="culture"></param> 390 /// <returns></returns> 391 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 392 { 393 if (value == null) 394 return null; 395 396 string source = value.ToString(); 397 398 if (source == UpgradePolicy_Notify) 399 return UpgradePolicy.Notify; 400 401 else if (source == UpgradePolicy_Manual) 402 return UpgradePolicy.Manual; 403 404 else 405 return UpgradePolicy.Automatic; 406 } 407 } 408 409 /// <summary> 410 /// 411 /// </summary> 412 class SearchPolicyDescriptionConverter : IValueConverter 413 { 414 private string SearchPolicy_Global = U.T("GeneralSearchPolicyGlobal", "Content"); 415 private string SearchPolicy_Partial = U.T("GeneralSearchPolicyPartial", "Content"); 416 private string SearchPolicy_Individual = U.T("GeneralSearchPolicyIndividual", "Content"); 417 418 /// <summary> 419 /// 420 /// </summary> 421 /// <param name="value"></param> 422 /// <param name="targetType"></param> 423 /// <param name="parameter"></param> 424 /// <param name="culture"></param> 425 /// <returns></returns> 426 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 427 { 428 if (value == null) 429 return null; 430 431 SearchPolicy source = (SearchPolicy)Enum.Parse(typeof(SearchPolicy), value.ToString()); 432 string target; 433 434 switch (source) 435 { 436 case SearchPolicy.Global: 437 default: 438 target = SearchPolicy_Global; 439 break; 440 441 case SearchPolicy.Partial: 442 target = SearchPolicy_Partial; 443 break; 444 445 case SearchPolicy.Individual: 446 target = SearchPolicy_Individual; 447 break; 448 } 449 450 return target; 451 } 452 453 /// <summary> 454 /// 455 /// </summary> 456 /// <param name="value"></param> 457 /// <param name="targetType"></param> 458 /// <param name="parameter"></param> 459 /// <param name="culture"></param> 460 /// <returns></returns> 461 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 462 { 463 if (value == null) 464 return null; 465 466 string source = value.ToString(); 467 468 if (source == SearchPolicy_Partial) 469 return SearchPolicy.Partial; 470 471 else if (source == SearchPolicy_Individual) 472 return SearchPolicy.Individual; 473 474 else 475 return SearchPolicy.Global; 476 } 477 } 478 479 #endregion 480 481 /// <summary> 482 /// Listens keyboard globally. 483 /// 484 /// <remarks>Uses WH_KEYBOARD_LL.</remarks> 485 /// </summary> 486 public class KeyboardListener : IDisposable 487 { 488 #region Constructor 489 490 /// <summary> 491 /// Creates global keyboard listener. 492 /// </summary> 493 public KeyboardListener() 494 { 495 // We have to store the HookCallback, so that it is not garbage collected runtime 496 hookedLowLevelKeyboardProc = (InterceptKeys.LowLevelKeyboardProc)LowLevelKeyboardProc; 497 498 // Set the hook 499 hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc); 500 501 // Assign the asynchronous callback event 502 hookedKeyboardCallbackAsync = new KeyboardCallbackAsync(KeyboardListener_KeyboardCallbackAsync); 503 } 504 505 #endregion 506 507 #region Destructor 508 509 /// <summary> 510 /// Destroys global keyboard listener. 511 /// </summary> 512 ~KeyboardListener() 513 { 514 Dispose(); 515 } 516 517 #endregion 518 519 #region Methods 520 /// <summary> 521 /// Check if the handlers are present 522 /// </summary> 523 /// <returns>true if both handlers are not null, otherwise false</returns> 524 public bool CheckHandlers() 525 { 526 return (KeyDown != null && KeyUp != null); 527 } 528 #endregion 529 530 #region Events 531 532 /// <summary> 533 /// Fired when any of the keys is pressed down. 534 /// </summary> 535 public event RawKeyEventHandler KeyDown; 536 537 /// <summary> 538 /// Fired when any of the keys is released. 539 /// </summary> 540 public event RawKeyEventHandler KeyUp; 541 542 #endregion 543 544 #region Inner workings 545 546 /// <summary> 547 /// Hook ID 548 /// </summary> 549 private IntPtr hookId = IntPtr.Zero; 550 551 /// <summary> 552 /// Asynchronous callback hook. 553 /// </summary> 554 /// <param name="keyEvent"></param> 555 /// <param name="vkCode"></param> 556 private delegate void KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode); 557 558 /// <summary> 559 /// Actual callback hook. 560 /// 561 /// <remarks>Calls asynchronously the asyncCallback.</remarks> 562 /// </summary> 563 /// <param name="nCode"></param> 564 /// <param name="wParam"></param> 565 /// <param name="lParam"></param> 566 /// <returns></returns> 567 [MethodImpl(MethodImplOptions.NoInlining)] 568 private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam) 569 { 570 if (nCode >= 0) 571 if (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN || 572 wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYUP || 573 wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN || 574 wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYUP) 575 hookedKeyboardCallbackAsync.BeginInvoke((InterceptKeys.KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), null, null); 576 577 return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam); 578 } 579 580 /// <summary> 581 /// Event to be invoked asynchronously (BeginInvoke) each time key is pressed. 582 /// </summary> 583 private KeyboardCallbackAsync hookedKeyboardCallbackAsync; 584 585 /// <summary> 586 /// Contains the hooked callback in runtime. 587 /// </summary> 588 private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc; 589 590 /// <summary> 591 /// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events. 592 /// </summary> 593 /// <param name="keyEvent">Keyboard event</param> 594 /// <param name="vkCode">vkCode</param> 595 void KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode) 596 { 597 switch (keyEvent) 598 { 599 // KeyDown events 600 case InterceptKeys.KeyEvent.WM_KEYDOWN: 601 if (KeyDown != null) 602 KeyDown(this, new RawKeyEventArgs(vkCode, false)); 603 break; 604 case InterceptKeys.KeyEvent.WM_SYSKEYDOWN: 605 if (KeyDown != null) 606 KeyDown(this, new RawKeyEventArgs(vkCode, true)); 607 break; 608 609 // KeyUp events 610 case InterceptKeys.KeyEvent.WM_KEYUP: 611 if (KeyUp != null) 612 KeyUp(this, new RawKeyEventArgs(vkCode, false)); 613 break; 614 case InterceptKeys.KeyEvent.WM_SYSKEYUP: 615 if (KeyUp != null) 616 KeyUp(this, new RawKeyEventArgs(vkCode, true)); 617 break; 618 619 default: 620 break; 621 } 622 } 623 624 #endregion 625 626 #region IDisposable Members 627 628 /// <summary> 629 /// Disposes the hook. 630 /// <remarks>This call is required as it calls the UnhookWindowsHookEx.</remarks> 631 /// </summary> 632 public void Dispose() 633 { 634 InterceptKeys.UnhookWindowsHookEx(hookId); 635 } 636 637 #endregion 638 } 639 640 /// <summary> 641 /// Raw KeyEvent arguments. 642 /// </summary> 643 public class RawKeyEventArgs : EventArgs 644 { 645 #region Members 646 647 /// <summary> 648 /// vkCode of the key. 649 /// </summary> 650 public int vkCode; 651 652 /// <summary> 653 /// WPF key of the key. 654 /// </summary> 655 public Key key; 656 657 /// <summary> 658 /// Is the hitted key system key. 659 /// </summary> 660 public bool isSysKey; 661 662 #endregion 663 664 #region Constructor 665 666 /// <summary> 667 /// Create raw keyevent arguments. 668 /// </summary> 669 /// <param name="vkCode"></param> 670 /// <param name="isSysKey"></param> 671 public RawKeyEventArgs(int VKCode, bool isSysKey) 672 { 673 this.vkCode = VKCode; 674 this.isSysKey = isSysKey; 675 this.key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode); 676 } 677 678 #endregion 679 } 680 681 /// <summary> 682 /// Raw keyevent handler. 683 /// </summary> 684 /// <param name="sender">sender</param> 685 /// <param name="args">raw keyevent arguments</param> 686 public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args); 687 688 /// <summary> 689 /// Exception for an unknown column 690 /// </summary> 691 class UnknownColumnException : ApplicationException 692 { 693 #region Constructor 694 695 /// <summary> 696 /// 697 /// </summary> 698 /// <param name="name"></param> 699 public UnknownColumnException(string name) 700 : base("The list view doesn't contain the column '" + name + "'") 701 { 702 } 703 704 #endregion 705 } 706 707 #region WINAPI Helper class 708 709 /// <summary> 710 /// Winapi key interception helper class. 711 /// </summary> 712 internal static class InterceptKeys 713 { 714 #region Members 715 716 public static int WH_KEYBOARD_LL = 13; 717 718 #endregion 719 720 #region Delegates 721 722 /// <summary> 723 /// 724 /// </summary> 725 /// <param name="nCode"></param> 726 /// <param name="wParam"></param> 727 /// <param name="lParam"></param> 728 /// <returns></returns> 729 public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam); 730 731 #endregion 732 733 #region Enums 734 735 /// <summary> 736 /// 737 /// </summary> 738 public enum KeyEvent : int 739 { 740 /// <summary> 741 /// 742 /// </summary> 743 WM_KEYDOWN = 256, 744 745 /// <summary> 746 /// 747 /// </summary> 748 WM_KEYUP = 257, 749 750 /// <summary> 751 /// 752 /// </summary> 753 WM_SYSKEYUP = 261, 754 755 /// <summary> 756 /// 757 /// </summary> 758 WM_SYSKEYDOWN = 260 759 } 760 761 #endregion 762 763 #region Statics 764 765 public static IntPtr SetHook(LowLevelKeyboardProc proc) 766 { 767 using (Process curProcess = Process.GetCurrentProcess()) 768 using (ProcessModule curModule = curProcess.MainModule) 769 { 770 return SetWindowsHookEx(WH_KEYBOARD_LL, proc, 771 GetModuleHandle(curModule.ModuleName), 0); 772 } 773 } 774 775 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 776 public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); 777 778 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 779 [return: MarshalAs(UnmanagedType.Bool)] 780 public static extern bool UnhookWindowsHookEx(IntPtr hhk); 781 782 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 783 public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam); 784 785 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 786 public static extern IntPtr GetModuleHandle(string lpModuleName); 787 788 #endregion 789 } 790 791 #endregion 792}