Интеграция .NET-приложения с таскбаром Windows 7
Intro
Прежде всего хочу отметить, что в .NET 4.0 есть поддержка некоторых вещей, о которых будет идти речь. Но проект, на котором пришлось использовать данные вещи, начинался более 2-ух лет назад на .NET 2.0 и позже был переведен на 3.5. В целях совместимости системы расширений и инсталятора решили на 4.0 не переходить, т. к. ничего от этого мы бы не получили (интеграция с Windows 7 уже давно разрабатывалась).
Отображение прогресса в таскбаре Windows 7
Гугл сразу объяснил, что нужно использовать системный интерфейс ITaskbarList3 и класс CTaskbarList. После штудирования MSDN и интернета была готова обертка для таскбара:
public static class Taskbar { #region Types public enum ProgressState { NoProgress = 0, Indeterminate = 1, // Marquee Normal = 2, // Green Error = 4, // Red Paused = 8 // Yellow } [StructLayout( LayoutKind.Sequential )] private struct Rectangle { public int Left; public int Top; public int Right; public int Bottom; public Rectangle( int left, int top, int right, int bottom ) { Left = left; Top = top; Right = right; Bottom = bottom; } } private enum ThumbTabFlags { UseMdiThumbnail = 0x1, UseMdiLivePreview = 0x2 } private enum ThumbBtnMask { Bitmap = 0x1, Icon = 0x2, Tooltip = 0x4, Flags = 0x8 } private enum ThumbBtnFlags { Enabled = 0, Disabled = 0x1, DismissOnClick = 0x2, NoBackground = 0x4, Hidden = 0x8 } [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Auto )] private struct ThumbBtn { [MarshalAs( UnmanagedType.U4 )] public ThumbBtnMask Mask; public uint Id; public uint Bitmap; public IntPtr Icon; [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 260 )] public string Tip; [MarshalAs( UnmanagedType.U4 )] public ThumbBtnFlags Flags; } [ComImportAttribute] [GuidAttribute( "ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf" )] [InterfaceTypeAttribute( ComInterfaceType.InterfaceIsIUnknown )] private interface ITaskbarList3 { [PreserveSig] void HrInit(); [PreserveSig] void AddTab( IntPtr hwnd ); [PreserveSig] void DeleteTab( IntPtr hwnd ); [PreserveSig] void ActivateTab( IntPtr hwnd ); [PreserveSig] void SetActiveAlt( IntPtr hwnd ); [PreserveSig] void MarkFullscreenWindow( IntPtr hwnd, [MarshalAs( UnmanagedType.Bool )] bool fFullscreen ); void SetProgressValue( IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal ); void SetProgressState( IntPtr hwnd, ProgressState tbpFlags ); void RegisterTab( IntPtr hwndTab, IntPtr hwndMDI ); void UnregisterTab( IntPtr hwndTab ); void SetTabOrder( IntPtr hwndTab, IntPtr hwndInsertBefore ); void SetTabActive( IntPtr hwndTab, IntPtr hwndMDI, ThumbTabFlags tbatFlags ); void ThumbBarAddButtons( IntPtr hwnd, uint cButtons, [MarshalAs( UnmanagedType.LPArray )] ThumbBtn[] pButtons ); void ThumbBarUpdateButtons( IntPtr hwnd, uint cButtons, [MarshalAs( UnmanagedType.LPArray )] ThumbBtn[] pButtons ); void ThumbBarSetImageList( IntPtr hwnd, IntPtr himl ); void SetOverlayIcon( IntPtr hwnd, IntPtr hIcon, [MarshalAs( UnmanagedType.LPWStr )] string pszDescription ); void SetThumbnailTooltip( IntPtr hwnd, [MarshalAs( UnmanagedType.LPWStr )] string pszTip ); void SetThumbnailClip( IntPtr hwnd, ref Rectangle prcClip ); } [GuidAttribute( "56FDF344-FD6D-11d0-958A-006097C9A090" )] [ClassInterfaceAttribute( ClassInterfaceType.None )] [ComImportAttribute] private class CTaskbarList { } #endregion #region Fields private static ITaskbarList3 taskbarList; #endregion #region Properties private static ITaskbarList3 TaskbarList { get { if ( taskbarList == null ) { taskbarList = (ITaskbarList3)new CTaskbarList(); taskbarList.HrInit(); } return taskbarList; } } #endregion }
Теперь через свойство TaskbarList мы имеем доступ к API таскбара.
Отдельно стоит напомнить, что при обращении к свойству стоит проверить версию ОС, которая должна быть не ниже 6.1. У нас эта проверка выполнялась в Platform API Manager (класс, который отвечал за дергание API или его замещении при выполнении на ОС низших версий).
Дальше были реализованы методы для работы с таскбаром:
public static void SetProgressState( IntPtr handle, ProgressState state ) { TaskbarList.SetProgressState( handle, state ); } public static void SetProgressValue( IntPtr handle, ulong current, ulong maximum ) { TaskbarList.SetProgressValue( handle, current, maximum ); } public static void SetOverlayIcon( IntPtr handle, Icon icon, string description ) { TaskbarList.SetOverlayIcon( handle, icon == null ? IntPtr.Zero : icon.Handle, description ); } public static void SetTaskbarTooltip( IntPtr handle, string tooltip ) { TaskbarList.SetThumbnailTooltip( handle, tooltip ); }
Чтобы установить нужное состояние и значение прогресса операции нужно вызвать методы SetProgressState и SetProgressValue, передав им хендл окна приложения и желаемые значения состояния индикатора и прогресса.
Метод SetOverlayIcon позволяет отобразить небольшую иконку на кнопке приложения на таскбаре.
Метод SetTaskbarTooltip позволяет установить тултип который будет отображен при показе превью окна.
Отображение кнопок в превью окне на таскбаре Windows 7
Кнопке на превью окне можно присвоить ID, иконку и тултип. Реализуем методы для работы с кнопками в превью окне на таскбаре Windows 7:
public static void SetTaskbarButtons( IntPtr handle, List buttons ) { TaskbarList.ThumbBarAddButtons( handle, (uint)buttons.Count, buttons.ToArray() ); } public static ThumbBtn CreateTaskbarButton( uint id, Bitmap icon, string tooltip ) { return new ThumbBtn { Id = id, Icon = icon.GetHicon(), Tip = tooltip, Flags = ThumbBtnFlags.Enabled, Mask = Taskbar.ThumbBtnMask.Icon | Taskbar.ThumbBtnMask.Tooltip | Taskbar.ThumbBtnMask.Flags }; }
А вот и код, который создает кнопки:
List buttons = new List(); buttons.Add( CreateTaskbarButton( 1901, Resources.Icons.Prev, Resources.Strings.Prev ) ); buttons.Add( CreateTaskbarButton( 1902, Resources.Icons.Play, Resources.Strings.PlayPause ) ); buttons.Add( CreateTaskbarButton( 1903, Resources.Icons.Next, Resources.Strings.Next ) ); Taskbar.SetTaskbarButtons( handle, buttons );
Передаем хендл окна и массив кнопок.
***
Чтобы наше приложение могло реагировать на эти кнопки, нужно добавить обработчик:
protected override void WndProc( ref Forms.Message message ) { if ( message.Msg == 0x111 ) // WM_COMMAND { uint buttonID = (uint)message.WParam - 402653184; switch ( buttonID ) { case 1901: WindowsMediaPlayer.Prev(); return; case 1902: WindowsMediaPlayer.Play(); return; case 1903: WindowsMediaPlayer.Next(); return; } } }
При получении WM_COMMAND выдираем ID кнопки и определяем реакцию. Замечу: если обработчик ресурсоемкий, выполняйте обработку в другом потоке.
Получение закрепленных на таскбаре Windows 7 приложений
Вызвав метод GetTaskBarItems (см. ниже) мы получим список путей к файлам, которые закреплены на таскбаре Windows 7. Замечу, что метот вернет только те приложения, которые закреплены через прямые пути к файлу на диске.
public static class QuickLaunch { public static readonly Regex PathExpression = new Regex( @"[a-zA-Z]{1}:\\([a-zA-Z0-9 \.\(\)]+\\)+[a-zA-Z0-9 \.\(\)]+.(\w)+" ); #region Properties public static string PathToQuickLaunchShortcuts { get { return Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData ), @"Microsoft\Internet Explorer\Quick Launch" ); } } public static string PathToStartMenuShortcuts { get { return Path.Combine( PathToQuickLaunchShortcuts, @"User Pinned\StartMenu" ); } } public static string PathToTaskBarShortcuts { get { return Path.Combine( PathToQuickLaunchShortcuts, @"User Pinned\TaskBar" ); } } #endregion #region Methods public static string GetTargetPath( string lnkFile ) { try { return PathExpression.Match( File.ReadAllText( lnkFile ) ).Value; } catch { return null; } } public static List<string> GetItems( string folder ) { return (from file in Directory.GetFiles( folder, "*.lnk" ) let path = GetTargetPath( file ) where !string.IsNullOrEmpty( path ) select path) .ToList(); } public static List<string> GetQuickLaunchItems() { return GetItems( PathToQuickLaunchShortcuts ); } public static List<string> GetStartMenuItems() { return GetItems( PathToStartMenuShortcuts ); } public static List<string> GetTaskBarItems() { return GetItems( PathToTaskBarShortcuts ); } public static List<string> GetAllKnownItems() { List<string> items = GetTaskBarItems(); foreach ( var item in GetStartMenuItems() ) { if ( !items.Any( i => string.Compare( i, item, true ) == 0 ) ) items.Add( item ); } foreach ( var item in GetQuickLaunchItems() ) { if ( !items.Any( i => string.Compare( i, item, true ) == 0 ) ) items.Add( item ); } return items.ToList(); } #endregion }
Как бонус, методы GetQuickLaunchItems и GetStartMenuItems вернут пути к файлам закрепленным на панели Quick Launch и в меню по кнопке Start соответственно.