笔交互和触觉反馈

Windows 长期以来一直都支持数字笔,使用户能够以自然、直接的方式与其设备交互,并使用数字墨迹的丰富书写和绘图体验来表达其创造力。

Windows 11 中引入了一项新功能,使数字笔体验变得更加自然且引人注目:使用支持“触觉反馈”的笔时,用户可以真实感受到他们的笔以触觉方式与应用的用户界面 (UI) 交互。

注意

提到此新功能时,整个开发人员 API 和相关文档中会使用“触觉”一词,而“触感”一词是为用户提供的易记名称,与在 Windows 设置中设置反馈首选项有关。

Windows 11 支持的触觉反馈体验包括墨迹反馈和交互反馈:

  • 墨迹反馈通过当笔与屏幕接触时的持续振动来模拟各种类型的书写或绘图工具(例如钢笔、记号笔、铅笔、荧光笔等)的使用感觉。 默认情况下,Windows Ink 平台支持所有绘图工具的触觉反馈(本主题介绍如何提供超出 Windows Ink 支持范围的自定义墨迹解决方案)。
  • 另一方面,交互反馈是基于关键用户操作(例如鼠标悬停或单击按钮、响应操作的完成)的直接反馈,或用于吸引用户的注意力。

通常,需要完成五个步骤才能完全支持触觉反馈:

  • 检测笔输入。
  • 确定当前的笔和设备是否支持触觉反馈,如果支持,它支持哪些触觉反馈功能。
  • 确定要发送的触觉反馈信号。
  • 发送触觉反馈。
  • 停止触觉反馈

检测笔输入

若要检测和隔离笔输入,必须先注册 PointerEntered 事件,然后检查 PointerDeviceType 是否为某种

以下代码演示如何检查 PointerEntered 事件中的指针设备类型。 对于此示例,如果输入不是来自笔,我们只需从事件处理程序返回即可。 否则,我们将检查笔功能并配置触觉反馈。


private void InputObserver_PointerEntered(object sender, PointerRoutedEventArgs e)
{
    ...
    
    // If the current Pointer device is not a pen, exit.
    if (e.Pointer.PointerDeviceType != PointerDeviceType.Pen) 
    {
       return;
    }
    
    ...    
}

确定对触觉反馈的支持

并非所有笔和数字化器都支持触觉反馈,而支持触觉反馈的笔也不一定支持本主题中所述的所有触觉反馈功能。 因此,以编程方式确认活动的笔支持哪些功能非常重要。

沿用前面的示例,我们将演示如何检查活动的笔是否支持触觉反馈。

我们首先尝试从当前 PointerId 中检索 PenDevice 对象。 如果无法获取 PenDevice,我们只需从事件处理程序返回即可。

如果获取了 PenDevice,我们将测试它是否支持 SimpleHapticsController 属性。 如果不支持,我们同样只需从事件处理程序返回。

// Attempt to retrieve the PenDevice from the current PointerId.
penDevice = PenDevice.GetFromPointerId(e.Pointer.PointerId);

// If a PenDevice cannot be retrieved based on the PointerId, it does not support 
// advanced pen features, such as haptic feedback. 
if (penDevice == null)
{
    return;
}

// Check to see if the current PenDevice supports haptic feedback by seeing if it 
// has a SimpleHapticsController.
hapticsController = penDevice.SimpleHapticsController;
if (hapticsController == null)
{
    return;
}

在前面的示例中检索到的 SimpleHapticsController 在后续示例中用于查询触觉功能和发送/停止触觉反馈。

注意

如果使用 Windows App SDK 预览版 1.0 生成应用,可以使用 PenDevice 互操作性 (PenDeviceInterop.FromPointerPoint(PointerPoint)) 来访问系统 PenDevice

private void InputObserver_PointerEntered(PointerInputObserver sender, PointerEventArgs args)
{
    var penDevice = PenDeviceInterop.PenDeviceFromPointerPoint(args.CurrentPoint);
}

以下部分介绍了触觉笔必须支持的反馈功能,以及可选的反馈功能。 必需的触觉反馈类型通常可以用作回退功能,而不是可选功能。

墨迹波形

当笔与屏幕接触时,墨迹波形将连续播放,并尝试模拟各种书写或绘图工具的使用感觉。

Feature 说明 必需/可选
InkContinous 波形 模拟使用物理圆珠笔进行墨迹书写的感觉。 当触觉笔不支持墨迹波形时,这是默认的回退功能。 必须
BrushContinuous 波形 当用户选择画笔作为墨迹工具时的连续触觉信号。 可选
ChiselMarkerContinuous 波形 当用户选择扁头记号笔/荧光笔作为墨迹工具时的连续触觉信号。 可选
EraserContinuous 波形 当用户选择橡皮擦作为墨迹工具时的连续触觉信号。 可选
GalaxyContinuous 波形
(HID 文档和实现指南将此波形称为 SparkleContinuous)
特殊墨迹工具(例如多色画笔)的连续触觉信号。 可选
MarkerContinuous 波形 当用户选择记号笔作为墨迹工具时的连续触觉信号。 可选
PencilContinuous 波形 当用户选择铅笔作为墨迹工具时的连续触觉信号。 可选

交互波形

交互波形是按需生成的、通常较短(下表中注明的情况除外)的直接反馈波形,以确认关键操作(例如鼠标悬停或单击按钮、响应操作的完成),或吸引用户的注意力。

Feature 说明 必需/可选
Click 波形 简短的“单击”反馈。 当应用选择的交互波形不受触觉笔支持时,这是默认的回退功能。 必须
Error 波形 提醒用户操作失败或发生错误的强信号。 可选
Hover 波形 指示用户已开始将鼠标悬停在交互式 UI 元素上。 可选
Press 波形 指示用户何时在增量操作中按下了交互式 UI 元素(请参阅“Release”)。 可选
Release 波形 指示用户何时在增量操作中释放了交互式 UI 元素(请参阅“Press”)。 可选
Success 波形 提醒用户操作成功的强信号。 可选
BuzzContinuous 波形 持续的嗡嗡声感觉。 可选
RumbleContinuous 波形 持续的隆隆声感觉。 可选

触觉反馈自定义

某些触觉笔支持以下自定义。

Feature 说明 必需/可选
强度 设置触觉信号的强度。 可选
播放计数 将触觉信号重复指定的次数。 可选
重播暂停间隔 设置每次重复播放触觉信号的间隔时间。 可选
播放持续时间 设置播放触觉信号的时间间隔。 可选

检查自定义设置支持

若要检查“强度”、“播放计数”、“重播暂停间隔”和“播放持续时间”支持,请使用 SimpleHapticsController 的以下属性:

发送和停止墨迹触觉反馈

使用 SimpleHapticsController 对象的 SendHapticFeedback 方法将墨迹波形传递给用户的笔。 此方法支持传入某种波形,或者同时传入波形和自定义强度值(请参阅自定义触觉反馈)。

调用 SendHapticFeedback 并传入一种墨迹波形,以将笔配置为在笔尖接触到屏幕上的任意位置时立即开始播放该波形。 该波形将持续播放,直到提起笔或调用 StopFeedback(以先发生的操作为准)。 我们建议在你要在其中播放触觉信号的元素的 PointerEntered 事件处理程序中执行此操作。 例如,具有自定义墨迹实现的应用将在其墨迹画布的 PointerEntered 方法中执行此操作。

若要检索所需的墨迹波形,必须迭代 SimpleHapticsControllerSupportedFeedback 集合,确保该波形受活动笔的支持。

如果不受支持,你可以选择根本不播放任何内容,或回退到 InkContinuous 波形,因为可以保证该波形受支持。

在以下示例中,我们尝试发送 BrushContinuous 波形(但如果 BrushContinuous 不受支持,则回退到 InkContinuous)。

SimpleHapticsControllerFeedback currentWaveform;

// Attempt to set the currentWaveform to BrushContinuous.
foreach (var waveform in hapticsController.SupportedFeedback)
{
    if (waveform.Waveform == KnownSimpleHapticsControllerWaveforms.BrushContinuous)
    {
        currentWaveform = waveform;
    }
} 

// If currentWaveform is null, it was not in the SupportedFeedback collection, so instead set 
// the waveform to InkContinuous.
if (currentWaveform == null)
{
    foreach (var waveform in hapticsController.SupportedFeedback)
    {
        if (waveform.Waveform == KnownSimpleHapticsControllerWaveforms.InkContinuous)
        {
            currentWaveform = waveform;
        }
    }
}

// Send the currentWaveform 
hapticsController.SendHapticFeedback(currentWaveform);

当关联的指针退出你为触觉反馈注册的元素时,也要停止触觉反馈,这一点非常重要。 否则,波形将继续尝试在活动的笔上播放。

注意

某些笔可以在其超出屏幕范围时有选择性地自行停止触觉反馈。 但是,并非所有笔都需要这样做,因此应用程序始终应显式停止触觉反馈,如此处所述。

若要在某个元素上停止触觉反馈,请在注册了 PointerEntered 处理程序(发送触觉信号)的同一元素上注册 PointerExited 事件。 在已退出的该事件处理程序中调用 StopFeedback,如下所示。

hapticsController.StopFeedback();

发送和停止交互反馈

发送交互反馈非常相似于发送墨迹反馈。

使用 SimpleHapticsController 对象的 SendHapticFeedback 方法将交互波形传递给用户的笔。 此方法支持传入某种波形,或者同时传入波形和自定义强度值(请参阅自定义触觉反馈)。

调用 SendHapticFeedback 并传入一种墨迹波形,以将笔配置为根据应用中的某种交互立即开始播放该波形(而不是在笔尖接触屏幕以获取墨迹反馈时播放)。

使用任何非连续交互波形时,无需进行相应的 StopFeedback 调用。 仍然需要为连续交互波形调用 StopFeedback

注意

当墨迹波形正在播放时发送交互波形会暂时中断墨迹波形。 当交互波形停止时,墨迹波形将会恢复。

若要检索所需的交互波形,必须迭代 SimpleHapticsControllerSupportedFeedback 集合,确保该波形受活动笔的支持。

如果不受支持,你可以选择根本不播放任何内容,或回退到 Click 波形,因为可以保证该波形受支持。

在以下示例中,我们尝试发送 Error 波形(但如果不支持 Error 波形,则回退到 Click 波形)。

SimpleHapticsControllerFeedback currentWaveform;  

// Attempt to set the currentWaveform to BrushContinuous.
foreach (var waveform in hapticsController.SupportedFeedback)
{
    if (waveform.Waveform == KnownSimpleHapticsControllerWaveforms.Error)
    {
        currentWaveform = waveform;
    }
} 

// If currentWaveform is null, it was not in the SupportedFeedback collection, so instead set 
// the waveform to Click.
if (currentWaveform == null)
{
    foreach (var waveform in hapticsController.SupportedFeedback)
    {
        if (waveform.Waveform == KnownSimpleHapticsControllerWaveforms.Click)
        {
            currentWaveform = waveform;
        }
    }
} 

// Send the currentWaveform.
hapticsController.SendHapticFeedback(currentWaveform); 

自定义触觉反馈

可通过三种方法自定义触觉反馈。 第一种方法受墨迹和交互反馈的支持,而第二和第三种方法仅受交互反馈的支持。

  1. 相对于最大系统强度设置调整反馈强度。 为此,首先必须检查以确保 SimpleHapticsController 支持设置强度,然后使用所需的 Intensity 值调用 SendHapticFeedback

    if (hapticsController.IsIntensitySupported) 
    {
        foreach (var waveform in hapticsController.SupportedFeedback)
        {
            if (waveform.Waveform == KnownSimpleHapticsControllerWaveforms.Click)
            {
                double intensity = 0.75;
                hapticsController.SendHapticFeedback(waveform, intensity);
            }
        }
    }
    
  2. 将触觉信号重复指定的次数。 为此,首先必须检查以确保 SimpleHapticsController 支持设置强度,然后使用所需的计数值调用 SendHapticFeedbackForPlayCount。 还可以设置强度和重播暂停间隔。

    注意

    如果 SimpleHapticsController 不支持设置强度或重放暂停间隔,则会忽略提供的值。

    if (hapticsController.IsPlayCountSupported && hapticsController.IsIntensitySupported && hapticsController.IsReplayPauseIntervalSupported)
    {
        foreach (var waveform in hapticsController.SupportedFeedback)
        {
            if (waveform.Waveform == KnownSimpleHapticsControllerWaveforms.Click)
            {
                double intensity = 0.75;
                int playCount = 3;
                System.TimeSpan pauseDuration = new System.TimeSpan(1000000);
                hapticsController.SendHapticFeedbackForPlayCount(currentWaveform, intensity, playCount, pauseDuration);
            }
        }
    }
    
  3. 设置触觉信号的持续时间。 为此,首先必须检查以确保 SimpleHapticsController 支持设置播放持续时间,然后使用所需的时间间隔值调用 SendHapticFeedbackForDuration。 还可以设置强度。

    注意

    如果 SimpleHapticsController 不支持设置强度,则会忽略提供的值。

    if (hapticsController.IsPlayDurationSupported && hapticsController.IsIntensitySupported)
    {
        foreach (var waveform in hapticsController.SupportedFeedback)
        {
            if (waveform.Waveform == KnownSimpleHapticsControllerWaveforms.RumbleContinuous)
            {
                double intensity = 0.75;
                System.TimeSpan playDuration = new System.TimeSpan(5000000);
                hapticsController.SendHapticFeedbackForDuration(currentWaveform, intensity, playDuration);
            }
        }
    }
    

示例

有关以下功能的有效示例,请参阅笔触觉示例