一、目的:封装Zoom效果到Behavior中,方便实现鼠标滚轮定点放大缩小,鼠标拖动等效果
二、实现
1、鼠标滚轮定点放大缩小
2、鼠标拖动平移
3、恢复初始位置
4、设置缩放是否应用在整个容器中
5、设置居中对齐还是居左对齐
6、通过行为直接加载
三、示例
四、实现过程
1、如下定义Behavior
/// <summary> Zoom带有鼠标移动平移和滚轮定点放大效果 </summary>
public class ZoomWithWheelAndMoveBehavior : Behavior<FrameworkElement>
{
// Message:外部需要嵌套Grid
Grid parent;
// Message:Zoom控件
ZoomableCanvas zoomable;
/// <summary> 是否在父容器中也使用平移和缩放 </summary>
public bool UseInParent
{
get { return (bool)GetValue(UseInParentProperty); }
set { SetValue(UseInParentProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty UseInParentProperty =
DependencyProperty.Register("UseInParent", typeof(bool), typeof(ZoomWithWheelAndMoveBehavior), new PropertyMetadata(default(bool), (d, e) =>
{
ZoomWithWheelAndMoveBehavior control = d as ZoomWithWheelAndMoveBehavior;
if (control == null) return;
control.RefreshEvent();
}));
public bool IsCenterInZoom
{
get { return (bool)GetValue(IsCenterInZoomProperty); }
set { SetValue(IsCenterInZoomProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCenterInZoomProperty =
DependencyProperty.Register("IsCenterInZoom", typeof(bool), typeof(ZoomWithWheelAndMoveBehavior), new PropertyMetadata(default(bool), (d, e) =>
{
ZoomWithWheelAndMoveBehavior control = d as ZoomWithWheelAndMoveBehavior;
if (control == null) return;
control.RefreshLocation();
}));
public bool IsReturn
{
get { return (bool)GetValue(IsReturnProperty); }
set { SetValue(IsReturnProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsReturnProperty =
DependencyProperty.Register("IsReturn", typeof(bool), typeof(ZoomWithWheelAndMoveBehavior), new PropertyMetadata(default(bool), (d, e) =>
{
ZoomWithWheelAndMoveBehavior control = d as ZoomWithWheelAndMoveBehavior;
if (control == null) return;
control.RefreshReturn();
}));
void RefreshReturn()
{
if (zoomable == null) return;
zoomable.Scale=1;
zoomable.Offset =new Point(0,0);
}
/// <summary> 更新UseInParent刷新事件 </summary>
void RefreshEvent()
{
if (this.parent == null || this.zoomable == null) return;
if (!UseInParent)
{
parent.MouseWheel -= OnMouseWheel;
parent.MouseMove -= OnMouseMove;
}
else
{
zoomable.MouseWheel -= OnMouseWheel;
zoomable.MouseMove -= OnMouseMove;
}
if (UseInParent)
{
parent.MouseWheel += OnMouseWheel;
parent.MouseMove += OnMouseMove;
}
else
{
zoomable.MouseWheel += OnMouseWheel;
zoomable.MouseMove += OnMouseMove;
}
}
void RefreshLocation()
{
if (this.zoomable == null) return;
if (this.IsCenterInZoom)
{
var width = this.zoomable.ActualWidth - this.AssociatedObject.Width;
var height = this.zoomable.ActualHeight - this.AssociatedObject.Height;
Canvas.SetTop(this.AssociatedObject, height / 2);
Canvas.SetLeft(this.AssociatedObject, width / 2);
}
else
{
Canvas.SetTop(this.AssociatedObject, 0);
Canvas.SetLeft(this.AssociatedObject, 0);
}
}
protected override void OnAttached()
{
parent = AssociatedObject.GetParent<Grid>();
parent.Children.Remove(AssociatedObject);
zoomable = new ZoomableCanvas();
zoomable.Children.Add(AssociatedObject);
zoomable.Loaded += Zoomable_Loaded;
parent.Children.Add(zoomable);
if (UseInParent)
{
parent.MouseWheel += OnMouseWheel;
parent.MouseMove += OnMouseMove;
}
else
{
zoomable.MouseWheel += OnMouseWheel;
zoomable.MouseMove += OnMouseMove;
}
}
private void Zoomable_Loaded(object sender, RoutedEventArgs e)
{
this.RefreshLocation();
}
protected override void OnDetaching()
{
if (UseInParent)
{
parent.MouseWheel -= OnMouseWheel;
parent.MouseMove -= OnMouseMove;
}
else
{
zoomable.MouseWheel -= OnMouseWheel;
zoomable.MouseMove -= OnMouseMove;
}
zoomable.Loaded -= Zoomable_Loaded;
}
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
var x = Math.Pow(2, e.Delta / 3.0 / Mouse.MouseWheelDeltaForOneLine);
zoomable.Scale *= x;
// Adjust the offset to make the point under the mouse stay still.
var position = (Vector)e.GetPosition(parent);
zoomable.Offset = (Point)((Vector)(zoomable.Offset + position) * x - position);
e.Handled = true;
}
private Point LastMousePosition;
private void OnMouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(this.parent);
if (e.LeftButton == MouseButtonState.Pressed)
{
this.zoomable.Offset -= position - LastMousePosition;
}
LastMousePosition = position;
}
}
2、如下设置Xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<h:Row>
<h:FCheckBox x:Name="cbx_inparent" Content="UseInParent" IsChecked="True"/>
<h:FCheckBox x:Name="cbx_center" Content="IsCenterInZoom" Grid.Column="1" IsChecked="True"/>
<h:FCheckBox x:Name="cbx_return" Content="Return" Grid.Column="2" IsChecked="True"/>
<Label Style="{StaticResource S.Label.Flash}" Content="提示:尝试鼠标拖动和鼠标滚轮进行操作" HorizontalAlignment="Left" Foreground="Red" Grid.Column="3" Grid.ColumnSpan="3" FontSize="{StaticResource S.FontSize.Header}"/>
</h:Row>
<Grid ClipToBounds="True" Background="Transparent" Grid.Row="1">
<h:ObjectPropertyForm Grid.Row="1" Title="学生信息" SelectObject="{StaticResource S.Student.HeBianGu}">
<h:Interaction.Behaviors>
<h:ZoomWithWheelAndMoveBehavior UseInParent="{Binding ElementName=cbx_inparent,Path=IsChecked,Mode=TwoWay}"
IsCenterInZoom="{Binding ElementName=cbx_center,Path=IsChecked,Mode=TwoWay}"
IsReturn="{Binding ElementName=cbx_return,Path=IsChecked,Mode=TwoWay}"/>
</h:Interaction.Behaviors>
</h:ObjectPropertyForm>
</Grid>
</Grid>
其中行为代码如下:
<h:Interaction.Behaviors>
<h:ZoomWithWheelAndMoveBehavior UseInParent="{Binding ElementName=cbx_inparent,Path=IsChecked,Mode=TwoWay}"
IsCenterInZoom="{Binding ElementName=cbx_center,Path=IsChecked,Mode=TwoWay}"
IsReturn="{Binding ElementName=cbx_return,Path=IsChecked,Mode=TwoWay}"/>
</h:Interaction.Behaviors>
通过以上几部可以实现Zoom效果
其中Zoom控件部分使用的开源项目特此说明:
https://github.com/mdrabick/StiZoomableCanvas
五、下载地址
GitHub下载地址:https://github.com/HeBianGu/WPF-ControlBase.git
需要了解的知识点
System.Windows.Controls 命名空间 | Microsoft Learn
了解更多
GitHub - HeBianGu/WPF-ControlDemo: 示例
GitHub - HeBianGu/WPF-ControlBase: Wpf封装的自定义控件资源库
GitHub - HeBianGu/WPF-Control: WPF轻量控件和皮肤库
System.Windows.Controls 命名空间 | Microsoft Learn
HeBianGu的个人空间-HeBianGu个人主页-哔哩哔哩视频