缩放到滚动视图内的鼠标位置
Zoom to mouse position inside a scrollview
所以我做了一个使用边框和图像的 ZoomControl
<UserControl x:Class="ImageViewer.Controls.ZoomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ImageViewer.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Border x:Name="BorderImage">
<Image HorizontalAlignment="Left" VerticalAlignment="Top" x:Name="RenderingImage" RenderTransformOrigin="0,0" Stretch="None" Source="{Binding}" RenderTransform="{Binding}"/>
</Border>
此控件嵌套在 ScrollViewer 中
<ScrollViewer x:Name="ScollViewerImage" Grid.Column="2" Grid.Row="0" Grid.RowSpan="3" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Controls:ZoomControl x:Name="RenderingImage" ViewModel="{Binding}" ClipToBounds="True"></Controls:ZoomControl>
</ScrollViewer>
所以我构建了一个缩放到鼠标位置的函数
private void DoZoom(double deltaZoom, Point mousePosition)
{
var scaleTransform = GetScaleTransform();
var translateTransform = GetTranslateTransform();
if (!(deltaZoom > 0) && (scaleTransform.ScaleX < .4 || scaleTransform.ScaleY < .4))
return;
var mousePositionAfterScaleX = mousePosition.X * deltaZoom;
var mousePositionAfterScaleY = mousePosition.Y * deltaZoom;
var newMousePositionX = mousePosition.X - mousePositionAfterScaleX;
var newMousePositionY = mousePosition.Y - mousePositionAfterScaleY;
var newTranslateX = newMousePositionX - mousePosition.X;
var newTranslateY = newMousePositionY - mousePosition.Y;
var translateX = newTranslateX + translateTransform.X;
var translateY = newTranslateY + translateTransform.Y;
scaleTransform.ScaleX += deltaZoom;
scaleTransform.ScaleY += deltaZoom;
_currentZoom = scaleTransform.ScaleX;
ChangeTranslateTransofrm(translateX - overflowWidth, translateY - overflowHeight);
UpdateScaleTransfromValue();
}
只要图像适合 ScrollViewer 大小,这就可以正常工作。但是在图像大于 ScrollVIewer 控件(它显示滚动条)之后,每当我放大时,我的鼠标位置不再位于同一点上。我确信这与滚动条可见这一事实有关,但我无法弄清楚在滚动条可见后使鼠标停留在同一位置的数学方法。
因此,经过 2 天的尝试和错误,我找到了针对我的特定问题的解决方案。首先有两个主要问题。
- 滚动条处于自动状态(它们 hide/show 在 运行 时间基于 ScrollViewer[=25= 中 child 的 width.height ]).因为里面的图像改变了它的翻译变换。
- 在 zoom/in out 操作(child 变大或变小)后,滚动条位置(偏移量)不会保持在同一位置。
第一个问题已通过向 ScrollViewer 添加 2 个边框解决,只要滚动条不可见,滚动条所需的 space 就会被占用。
<ScrollViewer x:Name="ScollViewerImage" Grid.Column="2" Grid.Row="0" Grid.RowSpan="3" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Column="1" Width="{x:Static SystemParameters.VerticalScrollBarWidth}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ComputedVerticalScrollBarVisibility, ElementName=ScollViewerImage}"
Value="Visible">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<Border Grid.Row="1" Width="{x:Static SystemParameters.HorizontalScrollBarHeight}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ComputedHorizontalScrollBarVisibility, ElementName=ScollViewerImage}"
Value="Visible">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<Controls:ZoomControl Grid.Column="0" Grid.Row="0" x:Name="RenderingImage" ViewModel="{Binding}" ClipToBounds="True"></Controls:ZoomControl>
</Grid>
</ScrollViewer>
对于第二个问题,每当滚动完成时,我都会保存偏移量。
我还保存了缩放大小(ScrollableHeight 和 Scorllable Width)以及它们何时更改(已完成缩放 in/zoom)。我将滚动条重新定位在所需位置。
private void SetScrollbarOffset(ScrollViewer scrollViewer, double verticalChange, double horizontalChange)
{
// after each move of the scrollbar we save the current offsets
_currentVerticalOffset = scrollViewer.VerticalOffset;
_currentHorizonalOffset = scrollViewer.HorizontalOffset;
// we check if there was a zoom in/out perfomed
if (_scrollableHeight != scrollViewer.ScrollableHeight)
{
// we save the current zoom in/out scrollable height
_scrollableHeight = scrollViewer.ScrollableHeight;
// we move the scrollbar to the position needed to persist the mouse under the same point in the image
scrollViewer.ScrollToVerticalOffset(_currentVerticalOffset - verticalChange);
}
if (_scrollableWidth != scrollViewer.ScrollableWidth)
{
_scrollableWidth = scrollViewer.ScrollableWidth;
scrollViewer.ScrollToHorizontalOffset(_currentHorizonalOffset - horizontalChange);
}
}
最后一个方法是从 SizeChangedEvent 调用的
public void SizeChange(ScrollChangedEventArgs e)
{
var scrollViewer = (e.OriginalSource as ScrollViewer);
SetScrollbarOffset(scrollViewer, e.VerticalChange, e.HorizontalOffset);
}
所以我做了一个使用边框和图像的 ZoomControl
<UserControl x:Class="ImageViewer.Controls.ZoomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ImageViewer.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Border x:Name="BorderImage">
<Image HorizontalAlignment="Left" VerticalAlignment="Top" x:Name="RenderingImage" RenderTransformOrigin="0,0" Stretch="None" Source="{Binding}" RenderTransform="{Binding}"/>
</Border>
此控件嵌套在 ScrollViewer 中
<ScrollViewer x:Name="ScollViewerImage" Grid.Column="2" Grid.Row="0" Grid.RowSpan="3" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Controls:ZoomControl x:Name="RenderingImage" ViewModel="{Binding}" ClipToBounds="True"></Controls:ZoomControl>
</ScrollViewer>
所以我构建了一个缩放到鼠标位置的函数
private void DoZoom(double deltaZoom, Point mousePosition)
{
var scaleTransform = GetScaleTransform();
var translateTransform = GetTranslateTransform();
if (!(deltaZoom > 0) && (scaleTransform.ScaleX < .4 || scaleTransform.ScaleY < .4))
return;
var mousePositionAfterScaleX = mousePosition.X * deltaZoom;
var mousePositionAfterScaleY = mousePosition.Y * deltaZoom;
var newMousePositionX = mousePosition.X - mousePositionAfterScaleX;
var newMousePositionY = mousePosition.Y - mousePositionAfterScaleY;
var newTranslateX = newMousePositionX - mousePosition.X;
var newTranslateY = newMousePositionY - mousePosition.Y;
var translateX = newTranslateX + translateTransform.X;
var translateY = newTranslateY + translateTransform.Y;
scaleTransform.ScaleX += deltaZoom;
scaleTransform.ScaleY += deltaZoom;
_currentZoom = scaleTransform.ScaleX;
ChangeTranslateTransofrm(translateX - overflowWidth, translateY - overflowHeight);
UpdateScaleTransfromValue();
}
只要图像适合 ScrollViewer 大小,这就可以正常工作。但是在图像大于 ScrollVIewer 控件(它显示滚动条)之后,每当我放大时,我的鼠标位置不再位于同一点上。我确信这与滚动条可见这一事实有关,但我无法弄清楚在滚动条可见后使鼠标停留在同一位置的数学方法。
因此,经过 2 天的尝试和错误,我找到了针对我的特定问题的解决方案。首先有两个主要问题。
- 滚动条处于自动状态(它们 hide/show 在 运行 时间基于 ScrollViewer[=25= 中 child 的 width.height ]).因为里面的图像改变了它的翻译变换。
- 在 zoom/in out 操作(child 变大或变小)后,滚动条位置(偏移量)不会保持在同一位置。
第一个问题已通过向 ScrollViewer 添加 2 个边框解决,只要滚动条不可见,滚动条所需的 space 就会被占用。
<ScrollViewer x:Name="ScollViewerImage" Grid.Column="2" Grid.Row="0" Grid.RowSpan="3" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Column="1" Width="{x:Static SystemParameters.VerticalScrollBarWidth}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ComputedVerticalScrollBarVisibility, ElementName=ScollViewerImage}"
Value="Visible">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<Border Grid.Row="1" Width="{x:Static SystemParameters.HorizontalScrollBarHeight}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ComputedHorizontalScrollBarVisibility, ElementName=ScollViewerImage}"
Value="Visible">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<Controls:ZoomControl Grid.Column="0" Grid.Row="0" x:Name="RenderingImage" ViewModel="{Binding}" ClipToBounds="True"></Controls:ZoomControl>
</Grid>
</ScrollViewer>
对于第二个问题,每当滚动完成时,我都会保存偏移量。 我还保存了缩放大小(ScrollableHeight 和 Scorllable Width)以及它们何时更改(已完成缩放 in/zoom)。我将滚动条重新定位在所需位置。
private void SetScrollbarOffset(ScrollViewer scrollViewer, double verticalChange, double horizontalChange)
{
// after each move of the scrollbar we save the current offsets
_currentVerticalOffset = scrollViewer.VerticalOffset;
_currentHorizonalOffset = scrollViewer.HorizontalOffset;
// we check if there was a zoom in/out perfomed
if (_scrollableHeight != scrollViewer.ScrollableHeight)
{
// we save the current zoom in/out scrollable height
_scrollableHeight = scrollViewer.ScrollableHeight;
// we move the scrollbar to the position needed to persist the mouse under the same point in the image
scrollViewer.ScrollToVerticalOffset(_currentVerticalOffset - verticalChange);
}
if (_scrollableWidth != scrollViewer.ScrollableWidth)
{
_scrollableWidth = scrollViewer.ScrollableWidth;
scrollViewer.ScrollToHorizontalOffset(_currentHorizonalOffset - horizontalChange);
}
}
最后一个方法是从 SizeChangedEvent 调用的
public void SizeChange(ScrollChangedEventArgs e)
{
var scrollViewer = (e.OriginalSource as ScrollViewer);
SetScrollbarOffset(scrollViewer, e.VerticalChange, e.HorizontalOffset);
}