65.9K
CodeProject 正在变化。 阅读更多。
Home

使用新的 Windows 7 功能为 iTunes 任务栏应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (14投票s)

2010年1月31日

CPOL

14分钟阅读

viewsIcon

44748

downloadIcon

786

演示使用 .NET Framework 3.51 开发的 Windows Touch、Thumbnail、Task Dialogs、Thumbnail Toolbars、Overlay Icons 和 Progress bars 的 WPF C# 示例。

itunestaskbar1_-_toolbar_buttons.png

引言

.NET 4 Framework 和 Visual Studio 2010 将支持新的 Windows 7 功能,例如任务栏和多点触控。如果您使用的是 Visual Studio 2008 和 .NET 3.5 Framework,这些新功能不受原生支持,但您仍然可以通过包含一些外部程序集来使用这些功能。

本文将以 iTunes 任务栏应用程序为例,演示这些新的 Windows 7 功能。它还将展示在将这些功能集成到应用程序中时可能遇到的一些困难,以及如何解决这些问题,例如当您添加缩略图预览但缩略图工具栏消失时会发生什么,或者如何在不收到 NotSupportedException 的情况下使任务对话框显示在您的应用程序中。

顺便说一句,这**与 iPad 无关**!!:)

背景

此应用程序不是 iTunes 的替代品,也不能独立于 iTunes 运行。在启动此应用程序之前,您不需要运行 iTunes,因为当您实例化 iTunes COM 控件时,如果 iTunes 未运行,它将自动启动。目前没有办法绕过这一点。

iTunes 支持一些新的 Windows 7 功能,如缩略图工具提示和工具栏,但似乎没有其他功能。我想集成更多 Windows 7 功能,使其更具视觉效果。这是当您将鼠标悬停在任务栏上的图标时,当前 iTunes 的外观

itunestaskbar_-_itunes.png

您还可以将 iTunes 置于工具栏模式以方便访问控制按钮,但我不认为这很令人印象深刻。

itunestaskbar_-_itunes_toolbar.png

集成 iTunes COM

您可以下载 iTunes COM for Windows SDK 以获取有关如何使用 iTunes SDK 不同接口的更多信息,它提供了有关将 iTunes COM 控件与 C# 一起使用的一些信息。但是,如果您使用的是 .NET,则无需下载它。

要添加 iTunes COM 控件,请在“解决方案资源管理器”中,右键单击“引用”文件夹,然后选择“添加引用”。选择 COM 选项卡,找到 iTunes 类型库(目前是“iTunes 1.12 Type Library”),然后单击“确定”。

iTunesTaskbar

添加 iTunes COM 控件后,您应该会在“引用”文件夹中看到 iTunesLib

itunestaskbar_com_added.png

添加对 iTunesLib 的引用后,我在 iTunes 命名空间中创建了一个名为 WrapperITunes 的类来处理应用程序与 iTunes 的所有 COM 通信。

WrapperITunes 类包含五个方法

  • Play() - 播放当前曲目。
  • Pause() - 暂停当前曲目。
  • Rewind() - 快退当前曲目。设置为曲目开头。如果曲目开始不到 3 秒,则转到上一曲目。
  • PreviousTrack() - 转到上一曲目。
  • NextTrack() - 转到下一曲目。

它还包含三个属性

  • bool IsPlaying {get;} - 返回当前是否有曲目正在播放。
  • int PlayerPosition{get;set;} - 返回当前曲目的位置。
  • iTunesTrack CurrentTrack {get;} - 返回当前曲目,如果没有当前曲目则返回 null

以及四个事件

  • PlayerPlayNotification - 通知 iTunes 已开始播放。
  • PlayerStopNotification - 通知 iTunes 已停止播放。
  • PlayerTrackChangedNotification - 通知曲目已更改。
  • PlayerQuittingNotification - 通知 iTunes 正在退出。

这些方法和属性是从 NowPlaying 类调用的,并且所有这些事件都在该类中处理。

多点触控

.NET 3.5 Framework 不直接支持多点触控,但它将是 .NET 4 的一部分。有一种方法可以在您的 .NET 3.5 项目中集成多点触控支持。您可以从Windows Touch: Developer Resources下载 Windows 7 Multitouch .NET Interop Sample Library

在此项目中,我仅包含从示例库中编译的程序集 Windows7.Multitouch.dllWindows7.Multitouch.WPF.dll。您可以将这些编译后的程序集添加到您的项目中,或者下载示例并在同意 EULA 后将Wrapper\Windows7.MultitouchWrapper\Windows7.Multitouch.WPF 项目添加到您的解决方案中。如果您使用的是 WinForms 而不是 WPF,请将Wrapper\Windows7.Multitouch.WinForms 项目添加到您的解决方案中。

这些库不像 .NET 4 那样为您提供所有多点触控管理功能,但它们为您提供了原始的触摸消息作为触笔事件。该示例还为 WPF、WinForms 和 Win32 提供了触摸管理器示例,但我将演示如何使用我自己的更简单的管理器来管理这些事件以支持多点触控。他们的管理器有更多的功能,例如处理多个对象和惯性,所以如果您正在寻找这些功能,请查看他们的示例。

在此应用程序中,我将 Windows7.Multitouch.dllWindows7.Multitouch.WPF.dll 程序集添加到了项目中的引用。您也可以从示例库中添加这些项目,并在您的应用程序中为这些项目添加引用。

要初始化触摸事件,请检查系统是否支持触摸,然后调用一次 Factory.EnableStylusEvents。我在主窗口的 Loaded 事件中调用它

//
// Enable Stylus if capable
//
if (TouchHandler.DigitizerCapabilities.IsMultiTouchReady)
{
    Factory.EnableStylusEvents(this);
}

您可能会想,为什么他们不直接将触摸事件转换为鼠标事件。嗯,触笔事件和鼠标事件之间最大的区别在于触笔事件还包含设备 ID。这些设备 ID 允许我们跟踪和区分不同的触摸点。这是一个您使用多点触控接收的触笔消息示例

StylusMove ID:3 Location:118,239
StylusMove ID:2 Location:316,228
StylusMove ID:3 Location:118,239
StylusMove ID:2 Location:316,229
StylusMove ID:3 Location:118,239
StylusMove ID:2 Location:316,229
StylusMove ID:3 Location:118,240
StylusMove ID:2 Location:316,229
StylusMove ID:3 Location:118,239
StylusMove ID:2 Location:316,229
StylusMove ID:3 Location:119,239
StylusMove ID:3 Location:119,238
StylusMove ID:3 Location:125,232
StylusMove ID:3 Location:126,232

如您所见,消息来自两个 ID:2 和 3,并且它们有点分散。我需要提供一种更好的方法来管理事件,因此我创建了一个名为 MultiTouchManager 的类,该类管理触笔事件,并将触发单指左右滑动和多点触控缩放的事件。

MultiTouchManager 中有三个函数,您只需将触笔事件重定向到它们。下面将对它们进行更详细的解释

public void StylusDown(StylusDownEventArgs e)
public void StylusMove(StylusEventArgs e)
public void StylusUp(StylusEventArgs e)

并且 MultiTouchManager 会触发四个事件

  • OnStartGesture - 当发生第一个触摸事件时触发。
  • OnEndGesture - 当触摸事件结束时触发。
  • OnZoom - 当发生多点触控事件时触发。
  • OnFlick - 当触摸事件向左或向右移动 50 像素时触发。

StylusDown

StylusDown 将新的触摸项添加到触摸列表中,然后如果它是唯一的触摸项,则触发 OnStartGesture 事件,或者记录起始距离(如果是多点触控)。这将用于计算未来事件的缩放

/// <summary>
/// Handler for the Stylus Down Event
/// </summary>
/// <param name="e"></param>
public void StylusDown(StylusDownEventArgs e)
{
    TouchItem touchItem = GetTouchItem(e.StylusDevice.Id);
    if (touchItem == null)
    {
        //
        // The touch item does not exist,
        // this is usually what happens
        //
        _touchList.Add(new TouchItem(e.StylusDevice.Id, 
                                     e.GetPosition(_element)));
    }
    else
    {
        //
        // The touch item already exists, this could
        // happen if we missed the Stylus up event,
        // like if we went outside the app before we lifted up.
        //
        touchItem.StartingPoint = 
          touchItem.LastPoint = e.GetPosition(_element);
    }
    if (_touchList.Count == 1)
    {
        if (OnStartGesture != null)
        {
            OnStartGesture(this, new EventArgs());
        }
    }
    else if (_touchList.Count == 2)
    {
        _startingDistance = GetCurrentDistance();
    }
}

StylusMove

StylusMove 将从设备 ID 获取 TouchItem 对象。我必须对多点触控的此事件做一些事情,因为操作系统触发了大量的 StylusMove 事件,以至于窗口大小调整无法跟上所有消息。因此,我做的第一件事是检查 LastPoint 属性是否与当前点不同。有时我从同一个设备 ID 收到了 4 或 5 个具有完全相同点的 StylusMove 事件。这有点帮助,但我仍然收到比大小调整所能处理的更多的事件。因此,我做的下一件事是添加一个名为 _delayTimerDispatchTimer。其目的是当我在收到带有两个 TouchItem 的第一个 StylusMove 事件时,我记录两者之间的距离,并启动 DispatchTimer,它将在八分之一秒后触发。我一直收到 StylusMove 事件,直到 DispatchTimer 发送计时器滴答,然后我用两个点之间的最后一个距离触发 OnZoom 事件。这提供了显着帮助,并使窗口大小调整能够跟上多点触控事件。

如果 StylusMove 事件只发生了一个 TouchItem,我假设用户正在进行单指向左或向右拖动。我将当前触摸位置与起始位置进行比较,看它是否比起始位置向左或向右超过 50 像素。如果是,则触发 OnEnd 事件,然后是 OnEndEventArg

/// <summary>
/// Handler for the Stylus Move Event
/// </summary>
/// <param name="e"></param>
public void StylusMove(StylusEventArgs e)
{
    TouchItem touch = GetTouchItem(e.StylusDevice.Id);
    if (touch != null)
    {
        Point currentPosition = e.GetPosition(_element);
        if (currentPosition == touch.LastPoint)
        {
            return;
        }

        touch.LastPoint = currentPosition;

        if (_touchList.Count == 1)
        {
            if (OnFlick != null)
            {
                double diffX = touch.LastPoint.X - touch.StartingPoint.X;
                if ((diffX < -50) || (diffX > 50))
                {
                    OnFlick(this, new TouchFlickEventArgs(
                        (diffX < -50) ? FlickDirection.Left : 
                         FlickDirection.Right));
                    _touchList.Remove(touch);
                    if (OnEndGesture != null)
                    {
                        OnEndGesture(this, new EventArgs());
                    }
                }
            }
        }
        else if (_touchList.Count == 2)
        {
                _difference = GetCurrentDistance() - _startingDistance;

            //
            // Set the timer if it is not running already
            //
            if (!_delayTimer.IsEnabled)
            {
                _delayTimer.Start();
            }
        }
    }
}

StylusUp

StylusUp 将从列表中删除 TouchItem。如果列表中还有其他 TouchItem,它将把它们的 StartingPoint 属性设置为 LastPoint 属性的值,以防创建另一个 TouchItem。如果没有更多的 TouchItem,管理器将触发 OnEndGesture 事件。

/// <summary>
/// Handler for the Stylus Up Event
/// </summary>
/// <param name="e"></param>
public void StylusUp(StylusEventArgs e)
{
    if (RemoveTouchItem(e.StylusDevice.Id))
    {
        //
        // Set all remaining TouchItem object's starting points to their 
        // last point
        //
        if (_touchList.Count > 0)
        {
            foreach (var item in _touchList)
            {
                item.StartingPoint = item.LastPoint;
            }
        }
        else
        {
            if (OnEndGesture != null)
            {
                OnEndGesture(this, new EventArgs());
            }
        }
    }
}

处理应用程序中的触摸事件

我在 NowPlaying 类中处理触摸事件。我希望此应用程序与不支持多点触控的应用程序兼容,因此只有在检测到多点触控设备时,我才会初始化这些功能。我做的第一件事是注册触笔事件,但这些实际上是多点触控事件。我做的第二件事是初始化 MultiTouchManager 并注册其通知事件。

public NowPlaying()
{
    .
    .
    .
    if (Handler.DigitizerCapabilities.IsMultiTouchReady)
    {
        // If touch enabled, register for stylus events
        StylusDown += NowPlayingStylusDown;
        StylusUp += NowPlayingStylusUp;
        StylusMove += NowPlayingStylusMove;

        _multiTouchManager = new MultiTouchManager(this);
        _multiTouchManager.OnStartGesture += 
                           MultiTouchManagerOnStartGesture;
        _multiTouchManager.OnZoom += MultiTouchManagerOnZoom;
        _multiTouchManager.OnFlick += MultiTouchManagerOnFlick;
    }
    .
    .
    .
}

我接下来做的是将触笔事件重定向到 MultiTouchManager

/// <summary>
/// Stylus Down Event Handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void NowPlayingStylusDown(object sender, StylusDownEventArgs e)
{
    .
    .
    .
    //
    // Pass event to the Multi-Touch Manager
    //
    _multiTouchManager.StylusDown(e);
}

/// <summary>
/// Stylus Up Event Handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void NowPlayingStylusUp(object sender, StylusEventArgs e)
{
    //
    // Pass event to the Multi-Touch Manager
    //
    _multiTouchManager.StylusUp(e);
}

/// <summary>
/// Stylus Move Event Handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void NowPlayingStylusMove(object sender, StylusEventArgs e)
{
    //
    // Pass event to the Multi-Touch Manager
    //
    _multiTouchManager.StylusMove(e);
}

一旦多点触控管理器收到这些消息,我就等待它触发开始手势、缩放或轻扫事件。

多点触控缩放

当我在 NowPlaying 中收到 OnStartGesture 事件时,我会获取窗口的当前 HeightWidth

itunestaskbar_-_touch_small.png

/// <summary>
/// Handler for the MultiTouchManager's OnStartGesture Event 
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void MultiTouchManagerOnStartGesture(object sender, EventArgs e)
{
    _startingWidth = Width;
    _startingHeight = Height;
}

当我从多点触控管理器收到 OnZoom 事件时,我只需使用窗口的原始高度和宽度来调整窗口大小,并添加 TouchZoomEventArgs 提供的 Difference 值。这种将差值加到宽度和高度的方法对于此应用程序来说非常完美,因为专辑封面图形是完全正方形的。如果您要将其用于水平或垂直的矩形图片,则需要计算纵横比。从下面的屏幕截图可以看出,当您用手指缩放时,播放器的尺寸会增加。

itunestaskbar_-_touch_large.png

/// <summary>
/// Handler for the MultiTouchManager's OnZoom Event 
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void MultiTouchManagerOnZoom(object sender, TouchZoomEventArgs e)
{
    Width = _startingWidth + e.Difference;
    Height = _startingHeight + e.Difference;
}

轻扫

我还支持鼠标和触摸的左右轻扫。如果您在专辑封面上向左或向右滑动手指,它将切换到上一曲或下一曲

/// <summary>
/// Handler for the MultiTouchManager's OnFlick Event 
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void MultiTouchManagerOnFlick(object sender, TouchFlickEventArgs e)
{
    if (e.FlickDirection == FlickDirection.Left)
    {
        PreviousTrack();
    }
    else if (e.FlickDirection == FlickDirection.Right)
    {
        NextTrack();
    }
}

新的 Windows 7 任务栏功能

新的 Windows 7 任务栏功能要求您下载 Windows® API Code Pack for Microsoft® .NET Framework。在 Windows API Code Pack 中,有五个项目:Core、DirectX、ExtendedLinguisticServices、Sensors 和 Shell。在此项目中,我使用了 Core 和 Shell 项目。您可以将这些项目复制到您的解决方案中,并将项目作为引用添加到您的应用程序中。您也可以直接编译 Windows API Code Pack 解决方案,然后将程序集复制到您的项目中,然后添加对程序集的引用。

缩略图

Windows API Code Pack 提供了两种添加应用程序缩略图预览的方法。

第一种方法称为实时缩略图预览方法。它涉及创建一个 TabbedThumbnail 对象,然后调用 AddThumbnailPreview 方法

TabbedThumbnail preview = 
  new TabbedThumbnail(Application.Current.MainWindow, image, offset);
TaskbarManager.Instance.TabbedThumbnail.AddThumbnailPreview(preview);

底层的 Win32 API 的 AddThumbnailPreview 方法使用 RegisterTabSetTabOrderSetTabActive 方法在 ITaskbarList3 接口中。

第二种方法是使用 SetThumbnailClip 方法的缩略图剪辑方法

TaskbarManager.Instance.TabbedThumbnail.SetThumbnailClip(
               new WindowInteropHelper(this).Handle, rect);

SetThumbnailClip 使用 SetThumbnailClip 方法从 ITaskbarList3 接口设置要显示在缩略图中的屏幕区域的尺寸。

这两种方法都有其优缺点。

AddThumbnailPreview

优点
  • 添加缩略图标题
  • 添加缩略图工具提示
  • 窗口最小化时更新图像
缺点
  • 导致任务栏按钮消失(见下文)

注意:当调用 ITaskbarList3::SetTabOrder 方法时,任务栏按钮似乎会消失。

itunestaskbar1_-_no_toolbar_buttons.png

SetThumbnailClip

优点
  • 不会导致任务栏按钮消失
缺点
  • 无缩略图标题
  • 无缩略图工具提示
  • 最小化或窗口视图外时,图像不会更新

使用 AddThumbnailPreview 导致缩略图工具栏消失对我来说是一个太大的缺点。Windows 7 开发者培训工具包中的示例不允许我们使用 AddThumbnailPreview 方法和任务栏按钮。我修改了示例,以便能够看到该示例是否存在此问题,并确认了这一点。

因此,我需要使用 SetThumbnailClip 方法。

/// <summary>
/// Update the Taskbar's thumbnail preview
/// </summary>
private void UpdateThumbnailPreviewLocation()
{
    //
    // Get the bounds of the AlbumCoverImage Control
    //
    GeneralTransform transform = 
      nowPlaying.AlbumCoverImage.TransformToAncestor(this);
    System.Windows.Point rootPoint = 
      transform.Transform(new System.Windows.Point(0, 0));
    int width = (int)nowPlaying.AlbumCoverImage.ActualWidth;
    int height = (int)nowPlaying.AlbumCoverImage.ActualHeight;

    //
    // Set the Thumbnail Image
    //
    TaskbarManager.Instance.TabbedThumbnail.SetThumbnailClip(
        new WindowInteropHelper(this).Handle,
        new Rectangle((int)rootPoint.X, (int)rootPoint.Y, width, height));
}

使用缩略图剪辑方法添加标题和工具提示

由于没有任何 ITaskbarList 接口提供设置缩略图标题的方法,因此缩略图标题从窗口标题获取其文本。因此,如果您更改窗口标题,缩略图标题也会随之更改。

ITaskbarList3 接口有一个 SetThumbnailTooltip 方法,允许您设置工具提示文本,但 Windows API Code Pack 缺少调用此接口方法的代码。因此,一个简单的解决方法是找到 TabbedThumbnailManager.cs 文件,并将以下方法添加到 TabbedThumbnailManager 类中

/// <summary>
/// Specifies or updates the text of the tooltip that
/// is displayed when the mouse pointer rests on an
/// individual preview thumbnail in a taskbar button flyout.
/// </summary>
/// <param name="windowHandle">The handle to the window
/// whose thumbnail displays the tooltip</param>
/// <param name="tooltip">The pointer to the text
/// to be displayed in the tooltip. This value can be 
/// NULL, in which case the title of the window specified
/// by hwnd is used as the tooltip.</param>
public void SetThumbnailTooltip(IntPtr windowHandle, string tooltip)
{
    TaskbarManager.Instance.TaskbarList.SetThumbnailTooltip(windowHandle, tooltip);
}

现在您可以为缩略图半添加标题和工具提示了。

itunestaskbar_-_title_or_tooltips.png

/// <summary>
/// Handler for the OnUpdatePreview Event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void NowPlayingOnUpdatePreview(object sender, UpdatePreviewEventArgs e)
{
    //
    // Update the title and the tooltip
    //
    Title = e.Artist;
    TaskbarManager.Instance.TabbedThumbnail.SetThumbnailTooltip(
        new WindowInteropHelper(this).Handle,
        e.Artist + "\nTrack: " + 
        e.TrackName + "\nAlbum: " + e.Album);

    //
    // Update the Taskbar's thumbnail preview
    //
    UpdateThumbnailPreviewLocation();
    .
    .
    .
}

工具提示也可以是多行的。

缩略图工具栏

itunestaskbar_-_taskbar_buttons.png

缩略图工具栏是一项新功能,允许您在预览下方添加最多七个工具栏按钮。无法添加或删除工具栏按钮;但是,您可以更改图标和工具提示文本,以呈现按钮更改的外观。

我在 MainWindow.xaml.cs 中添加了三个按钮

/// <summary>
/// Create the Thumbnail Toolbars
/// 
/// </summary>
private void CreateThumbnailToolbarButtons()
{
    _buttonBackward = new ThumbnailToolbarButton(
        iTunesTaskbar.Resources.Backward, "Previous");
    _buttonBackward.Click += (s, e) => nowPlaying.Rewind();

    _buttonPlay = new ThumbnailToolbarButton(
        iTunesTaskbar.Resources.Play, "Play");
    _buttonPlay.Click += (s, e) => nowPlaying.Play();

    _buttonNext = new ThumbnailToolbarButton(
        iTunesTaskbar.Resources.Forward, "Next");
    _buttonNext.Click += (s, e) => nowPlaying.NextTrack(); 

    TaskbarManager.Instance.ThumbnailToolbars.AddButtons(
        new WindowInteropHelper(this).Handle,
        _buttonBackward, _buttonPlay, _buttonNext);

    UpdateThumbnailToolbarButtons();
}

UpdateThumbnailToolbarButtons 确定是显示“播放”还是“暂停”图标和工具提示。当我们从播放器收到 OnPlayOnStop 事件时,也会调用它。

private void UpdateThumbnailToolbarButtons()
{
    if (nowPlaying.IsTrackSelected)
    {
        _buttonPlay.Icon = nowPlaying.IsPlaying ? 
            iTunesTaskbar.Resources.Pause : 
            iTunesTaskbar.Resources.Play;
        _buttonPlay.Tooltip = 
          nowPlaying.IsPlaying ? "Pause" : "Play";

        _buttonPlay.Enabled = true;
        _buttonBackward.Enabled = true;
        _buttonNext.Enabled = true;
    }
    else
    {
        _buttonPlay.Enabled = false;
        _buttonBackward.Enabled = false;
        _buttonNext.Enabled = false;
    }
}

这是播放器的暂停状态,按钮上显示“播放”图标

itunestaskbar_-_taskbar_buttons_play.png

这是播放器的播放状态,按钮上显示“暂停”图标

itunestaskbar_-_taskbar_buttons_pause.png

任务对话框

itunestaskbar_taskdialog.png

Windows Vista 引入了新的增强型对话框,它提供了比标准消息框更多的功能。我添加了几个,但实际上并没有充分利用它们提供的所有功能。我真正想讨论的是人们在尝试添加此功能时遇到的问题。

using Microsoft.WindowsAPICodePack.Dialogs;

if (!nowPlaying.IsTrackSelected)
{
    TaskDialog taskDialog = new TaskDialog();
    taskDialog.Icon = TaskDialogStandardIcon.Information;
    taskDialog.Caption = "iTunes Taskbar";
    taskDialog.InstructionText = "There is no track playing in iTunes";
    taskDialog.Text = "Please go to iTunes " + 
                      "and select a playlist, then select a track";
    taskDialog.StandardButtons = TaskDialogStandardButtons.Ok;
    taskDialog.DetailsExpandedLabel = "Click for more details";
    taskDialog.DetailsExpandedText = "This is more detail";
    taskDialog.ExpansionMode = 
      TaskDialogExpandedDetailsLocation.ExpandFooter;

    taskDialog.Show();
}

当您第一次尝试显示 TaskDialog 时,很可能会收到一个 NotSupportedException,内容为“TaskDialog feature needs to load version 6 of comctl32.dll, but a different version is current loaded in memory”。

itunestaskbar_vshostloaded_exception.png

出现此错误的原因是,用于显示对话框的底层 Win32 API 是一个名为 TaskDialogIndirect 的函数,它仅在 comctl32.dll 版本 6 及更高版本中可用。

要加载 comctl32.dll 的版本 6,您需要创建一个名为 (appname).exe.manifest 的清单,并在清单的 /assembly/dependency 节点中添加以下内容

<dependentAssembly>
    <assemblyIdentity type="win32" 
      name="Microsoft.Windows.Common-Controls"
      version="6.0.0.0" 
      processorArchitecture="*" 
      publicKeyToken="6595b64144ccf1df"
      language="*" />
</dependentAssembly>

如果您在添加清单到程序集后重试,它可能仍然无法正常工作。原因是 Visual Studio 有一个托管进程,它使用该进程来运行您的调试会话以提高性能。停止调试会话时,此进程不会被卸载,并且已经加载了 comctl32.dll。因此,它在您再次启动调试会话时不会重新加载新的 comctl32.dll。托管进程的名称是 [appname].vshost.exe,因此对于 iTunesTaskbar,它的名称是 iTunesTaskbar.vshost.exe

您可以从 Process Monitor 中看到,即使未调试 iTunesTaskbar.vshost.exe,它仍处于加载状态,并且 comctl32.dll 的版本是 5.82。

itunestaskbar_vshostloaded_procexp.png

要解决此问题,请更新清单,然后关闭 Visual Studio,然后重新启动它。

仅供参考,即使在重新启动 Visual Studio 后,此问题仍会偶尔发生。如果发生,只需重新启动 Visual Studio 即可解决问题。您可以 禁用托管进程,但这也会导致调试期间性能下降。

任务栏叠加图标

我使用任务栏叠加图标来提供正在播放的曲目的专辑封面的**非常微小**的视图。我知道它非常小,在大多数情况下,只有熟悉专辑封面外观的人才会有帮助。我有一个示例,其中我颠倒了它,并将图标更改为专辑封面,并将音符设置为叠加图标,但这会挡住大部分进度条。对于那些对此行为感兴趣的人来说,很容易对其进行更改。

itunestaskbar_overlayicon.png

/// <summary>
/// Handler for the OnUpdatePreview Event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void NowPlayingOnUpdatePreview(object sender, UpdatePreviewEventArgs e)
{
    .
    .
    .
    //
    // Set the Taskbar's Overlay Icon
    //
    TaskbarManager.Instance.SetOverlayIcon(e.Icon, null);
    //Icon = icon;
}

任务栏进度条

itunestaskbar_normal_progress.png

我使用任务栏进度条来显示当前正在播放曲目的进度。当 NowPlaying 控件触发 OnProgressChanged 事件时,MainWindow 会收到事件并更新进度条

/// <summary>
/// Handler for the OnProgressChanged Event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void NowPlayingOnProgressChanged(object sender, TrackProgressEventArgs eventArgs)
{
    //
    // Update Taskbar Progress
    //
    TaskbarManager.Instance.SetProgressValue(
      eventArgs.CurrentPosition, eventArgs.MaximumPosition);
}

此外,您可以通过进度条的颜色在视觉上显示进度条的活动状态。正常状态是绿色,当 iTunes 正在播放曲目时,我显示正常状态。

/// <summary>
/// Handler for the OnPlay Event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void NowPlayingOnPlay(object sender, EventArgs e)
{
    //
    // Set the progress state to normal
    //
    TaskbarManager.Instance.SetProgressState(
                   TaskbarProgressBarState.Normal);
    .
    .
    .
}

当曲目暂停时,我将其显示为暂停状态,为黄色

itunestaskbar_paused_progress.png

/// <summary>
/// Handler for the OnStop Event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void NowPlayingOnStop(object sender, EventArgs e)
{
    //
    // Set the progress state to paused
    //
    TaskbarManager.Instance.SetProgressState(
                   TaskbarProgressBarState.Paused);
    .
    .
    .
}

历史

  • 2010 年 2 月 3 日:文章的初始版本。
© . All rights reserved.