通过增强的鼠标滚轮改进缩放功能






4.65/5 (7投票s)
如何为用户提供更好的高分辨率鼠标滚轮缩放体验。
引言
本文介绍了应用程序中需要进行的更改,以使其能够与增强型高分辨率鼠标滚轮正确缩放。这是 Logitech 为 CodeProject 撰写的第三篇帮助开发人员实现卓越的增强型鼠标滚轮支持的文章。在与 Tanvi Shah 合著的第一篇文章《在应用程序中处理增强型鼠标滚轮》中,我们讨论了滚动问题。第二篇文章《改进 WPF 鼠标滚轮处理》由 Roger Vuistiner 撰写,其中包含一个出色的库,可以轻松地为 WPF 应用程序添加增强型鼠标滚轮支持。
增强鼠标滚轮的缩放问题
增强型鼠标滚轮支持是在 Microsoft 推出 Vista 时添加的。对于固定的旋转量,增强型鼠标滚轮会比标准鼠标滚轮产生更多的滚轮事件。每次事件,标准鼠标滚轮的 delta 值均为 +/- 120。增强型鼠标滚轮的值会更小——通常可以被 120 整除。Windows Vista 及更高版本会自动为支持平滑滚动的任何设备启用该功能。如今,许多应用程序在使用增强型鼠标滚轮时,要么无法正常工作,要么没有针对高分辨率功能进行编写,而该功能可以利用滚轮移动的粒度。缩放最常见的问题是:
- 应用程序经常忽略绝对滚轮 delta 的幅度。这会导致应用程序缩放过快。
其他问题包括:
- 应用程序根本不缩放。如果 delta 值不够大,导致应用程序无法明显缩放,就会发生这种情况。应用程序也未能累积滚轮 delta,直到达到缩放所需的最小量。
- 如果用户从放大到缩小(或反之),鼠标滚轮需要转动不止一个档位。如果应用程序累积了 delta(或缩放后的余数),但在缩放反向时未将其清除,就会发生这种情况。
- 缩放体验并不比标准鼠标平滑。当应用程序仅在累积 delta 达到
WHEEL_DELTA
(在 winuser.h 中定义为 120)的幅度时才缩放时,就会发生这种情况。
如何测试增强鼠标滚轮缩放
您需要一个具有相应驱动程序的增强型滚轮功能的鼠标。您可能需要联系制造商获取驱动程序或有关如何打开此功能的说明。要查看它是否已打开,您可以使用 Spy++ 或类似的 Message Snooping 软件来捕获 WM_MOUSEWHEEL
消息。使用滚轮缩放您的应用程序。您会看到,对于鼠标滚轮的小幅度转动,会产生多条消息,并且每条消息的绝对 delta 值都小于 120(在下面的示例中,我们看到 zDelta 为 -15)。
如果您没有增强型滚轮鼠标,请使用随附的增强型滚轮模拟器。此应用程序通过向您的应用程序发送较小的滚轮 delta 来模拟增强型滚轮。因此,您仍然可以通过运行模拟器应用程序(HiResScrollWheel.exe)来测试您的应用程序,并创建您自己的高分辨率 WM_MOUSEWHEEL
消息。
启动 HiResScrollWheel.exe 并将参数设置为以下值,可以模拟一个高分辨率鼠标,每点击一次产生 8 个事件,delta 值为 15。
如果您的应用程序运行良好,那么使用增强型鼠标滚轮的缩放行为将更加平滑。它也应该与使用普通鼠标滚轮的缩放量相同。
为 Silverlight 应用程序添加增强型滚轮缩放支持
我将为其添加增强型鼠标滚轮缩放支持的第一个应用程序是 DeepZoomSample。
您可以从 CodeProject 下载、修改和构建源文件。
Page.xaml.cs 中用于处理鼠标滚轮的原始代码是:
void ZoomImage_MouseWheel(object obj,
System.Windows.Input.MouseWheelEventArgs e)
{
double newzoom = zoom;
if (e.Delta > 0)
newzoom /= 1.3;
else
newzoom *= 1.3;
Point logicalPoint =
this.ZoomImage.ElementToLogicalPoint(this.lastMousePos);
logicalPoint.X, logicalPoint.Y);
this.ZoomImage.ZoomAboutLogicalPoint(zoom/newzoom,
zoom = newzoom;
e.Handled = true;
}
该方法已更改为:
void ZoomImage_MouseWheel(object obj, System.Windows.Input.MouseWheelEventArgs e)
{
double newzoom = zoom;
newzoom /= Math.Pow(1.3, (e.Delta / 120.0F));
Point logicalPoint =
this.ZoomImage.ElementToLogicalPoint(this.lastMousePos);
this.ZoomImage.ZoomAboutLogicalPoint(zoom / newzoom,
logicalPoint.X, logicalPoint.Y);
zoom = newzoom;
e.Handled = true;
}
在 ZoomImage_MouseWheel()
方法中,有必要考虑鼠标滚轮 delta 的幅度。以前的代码只考虑了滚轮 delta 的符号(+,-)。
为了考虑 delta 的绝对值,而不是将旧缩放值除以 1.3 或乘以 1.3,我们始终将旧缩放值除以 1.3 的 (e.Delta / 120) 次方。请注意,如果 e.Delta
为负,那么我们将旧缩放值除以 1.3 的负次幂(e.Delta/120
),这相当于将旧缩放值乘以 1.3 的 abs(e.Delta/120) 次幂。
处理用于滚动和缩放的增强型鼠标滚轮 delta 之间存在重要区别。对于滚动,delta 值对滚动位置进行算术影响。
典型公式是:
if (e.Delta > 0)
newScrollPos = oldScrollPos + fixedScrollValue ;
else
newScrollPos = oldScrollPos - fixedScrollValue ;
为了考虑 delta 的幅度,我们可以简单地按如下方式缩放 fixedScrollValue
:
newScrollPos = oldScrollPos + (fixedScrollValue*delta/120) ;
然而,对于缩放,变化通常是几何学的。
if (e.Delta > 0)
newZoomFactor = oldZoomFactor * fixedZoomValue ;
else
newZoomFactor = oldZoomFactor / fixedZoomValue ;
为了考虑 delta 的幅度,我们需要按如下方式缩放 fixedZoomValue
:
newZoomFactor = oldZoomFactor * Math.Pow(fixedZoomValue, delta / 120);
为 Ab2d.Controls.ZoomPanel 库添加增强型鼠标滚轮支持
在我的第二个示例中,我为 WPF Graphics 的 ZoomPanel 示例应用程序添加了增强型鼠标滚轮支持。 WPF Graphics Tools 开发了 Ab3d.PowerToys 库,该库提供了出色的 3D 图形、编辑和缩放支持。然而,该库的默认行为开箱即用不支持增强型鼠标滚轮。看来该库使用以下算法来处理鼠标滚轮缩放:
if (e.Delta > 0)
ZoomPanel1.ZoomForFactor(ZoomPanel1.MouseWheelZoomFactor);
else
ZoomPanel1.ZoomForFactor(1 / ZoomPanel1.MouseWheelZoomFactor);
为其示例程序添加支持需要将预览鼠标滚轮事件路由到我们的处理程序。
这可以通过在 ZoomPanelSample.xaml 中添加一行来完成:
<Page x:Class="Ab2d.ZoomControlSample.ZoomPanel.ZoomPanelSample"
PreviewMouseWheel="PreviewMouseWheelEventHandler"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ab2d="clr-namespace:Ab2d.Controls;assembly=Ab2d.Controls.ZoomPanel">
我通过 Preview Mouse Wheel Event 处理缩放,因为 Mouse Wheel Event 已经被路由到 Ab2d.Controls.ZoomPanel 库。通过在 PreviewMouseWheelEventHandler()
中将 e.Handled
设置为 true
,我阻止了 Mouse Wheel Event 被库生成和处理。为了缩放,我复制了 PainterSample.xaml.cs 中的代码并调用了 ZoomForFactor()
。在用增强型鼠标滚轮缩放时,还需要关闭动画,这样图形才能很好地缩放。
PreviewMouseWheelEventHandler()
已按如下方式添加到 ZoomPanelSample.xaml.cs:
public void PreviewMouseWheelEventHandler(
Object sender,
MouseWheelEventArgs e )
{
if (ZoomPanel1.IsMouseWheelZoomEnabled == false)
return;
double zoomFactor;
bool bToggle = false;
zoomFactor = Math.Pow(ZoomPanel1.MouseWheelZoomFactor,
((double)e.Delta / 120.0));
if ((ZoomPanel1.IsAnimated) && (e.Delta > -120) && (e.Delta < 120))
bToggle = true;
if (bToggle)
{
ZoomPanel1.IsAnimated = false;
ZoomPanel1.ZoomForFactor(zoomFactor);
ZoomPanel1.IsAnimated = true;
}
else
{
ZoomPanel1.ZoomForFactor(zoomFactor);
}
e.Handled = true;
}
为 WPF 自定义控件添加增强型滚轮缩放支持
SimpleZoomAndPanSample 是 CodeProject 文章《WPF 用于缩放和平移的自定义控件》中包含的程序之一。为了添加增强型滚轮缩放支持,我修改了处理鼠标滚轮的方法,以考虑鼠标滚轮 delta 的幅度。缩放的变化是算术的,因此不需要计算缩放因子 1.1 的 n 次根。
MainWindow.xaml.cs 中的原始代码如下:
private void zoomAndPanControl_MouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
if (e.Delta > 0)
{
ZoomIn();
}
else if (e.Delta < 0)
{
ZoomOut();
}
}
private void ZoomOut()
{
zoomAndPanControl.ContentScale -= 0.1; //change is arithmetic, not geometric
}
private void ZoomIn()
{
zoomAndPanControl.ContentScale += 0.1; //change is arithmetic, not geometric
}
MainWindow.xaml.cs 中的方法已修改如下:
private void zoomAndPanControl_MouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
Zoom(e.Delta);
}
private void Zoom(int Delta)
{
zoomAndPanControl.ContentScale += 0.1*Delta/120;
}
为 Visual Basic 应用程序添加增强型滚轮缩放支持
CodeProject 文章《平移和缩放非常大的图像》包含一个 Visual Basic 程序,可以轻松修改以支持增强型鼠标滚轮的缩放。为了添加增强型滚轮缩放支持,我修改了处理鼠标滚轮的方法,以考虑鼠标滚轮 delta 的幅度。
原始代码如下:
Private Sub ZoomImage(ByVal ZoomIn As Boolean)
' Get center point
m_centerpoint.X = m_Origin.X + SrcRect.Width / 2
m_centerpoint.Y = m_Origin.Y + SrcRect.Height / 2
'set new zoomfactor
If ZoomIn Then
ZoomFactor = Math.Round(ZoomFactor * 1.1, 2)
Else
ZoomFactor = Math.Round(ZoomFactor * 0.9, 2)
End If
'Reset the origin to maintain center point
m_Origin.X = m_centerpoint.X - ClientSize.Width / m_ZoomFactor / 2
m_Origin.Y = m_centerpoint.Y - ClientSize.Height / m_ZoomFactor / 2
CheckBounds()
End Sub
所做的更改:
Private Sub ZoomImage(ByVal Delta As Integer)
' Get center point
m_centerpoint.X = m_Origin.X + SrcRect.Width / 2
m_centerpoint.Y = m_Origin.Y + SrcRect.Height / 2
'set new zoomfactor
If (ZoomIn > 0) Then
ZoomFactor = Math.Round(ZoomFactor * Math.Pow(1.1, ZoomIn / 120.0), 6)
ElseIf (ZoomIn < 0) Then
ZoomFactor = Math.Round(ZoomFactor * Math.Pow(0.9, -ZoomIn / 120.0), 6)
End If
'Reset the origin to maintain center point
m_Origin.X = m_centerpoint.X - ClientSize.Width / m_ZoomFactor / 2
m_Origin.Y = m_centerpoint.Y - ClientSize.Height / m_ZoomFactor / 2
CheckBounds()
End Sub
为 C# 应用程序添加增强型滚轮缩放支持
ImageZoom.exe 是 CodeProject 文章《在 Windows Forms 中进行缩放和平移(固定焦点)》中包含的 C# 应用程序。
MainForms.cs 中的 OnMouseWheel()
被修改为考虑鼠标滚轮 delta 的幅度。
原始代码:
if (e.Delta > 0)
{
zoom += 0.1F; //change is arithmetic, not geometric
}
else if (e.Delta < 0) //change is arithmetic, not geometric
{
zoom = Math.Max(zoom - 0.1F, 0.01F);
}
更改为:
zoom = Math.Max(zoom + (0.1F*e.Delta/120.0F), 0.01F);