Pull to refresh

Интеграция .NET-приложения с таскбаром Windows 7

Intro


Прежде всего хочу отметить, что в .NET 4.0 есть поддержка некоторых вещей, о которых будет идти речь. Но проект, на котором пришлось использовать данные вещи, начинался более 2-ух лет назад на .NET 2.0 и позже был переведен на 3.5. В целях совместимости системы расширений и инсталятора решили на 4.0 не переходить, т. к. ничего от этого мы бы не получили (интеграция с Windows 7 уже давно разрабатывалась).

image

Отображение прогресса в таскбаре 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
  }


* This source code was highlighted with Source Code Highlighter.

Теперь через свойство 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 );
  }


* This source code was highlighted with Source Code Highlighter.

Чтобы установить нужное состояние и значение прогресса операции нужно вызвать методы SetProgressState и SetProgressValue, передав им хендл окна приложения и желаемые значения состояния индикатора и прогресса.

image

Метод 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
      };
  }

* This source code was highlighted with Source Code Highlighter.


А вот и код, который создает кнопки:

  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 );

* This source code was highlighted with Source Code Highlighter.


Передаем хендл окна и массив кнопок.

image

Чтобы наше приложение могло реагировать на эти кнопки, нужно добавить обработчик:

  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;
      }
    }
  }


* This source code was highlighted with Source Code Highlighter.

При получении 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
  }


* This source code was highlighted with Source Code Highlighter.

Как бонус, методы GetQuickLaunchItems и GetStartMenuItems вернут пути к файлам закрепленным на панели Quick Launch и в меню по кнопке Start соответственно.

To be continued


Этот краткий экскурс является лишь небольшой выдержкой из того, с чем мне пришлось поработать на одном десктопном проекте. Если читатели найдутся, то таких статей будет много и не только по Windows 7 API. Список технологий, используемых на этом проекте, в которых мне пришлось "порытся" или столкнутся с "костылями" довольно широк: это проблемы микса WinForms и WPF, подводные камни WinForms, Window 7 API, API прошлых версий Windows, RSS, FTP, ID3Tags и Vorbis Comment, и многое другое.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.