图上显示跟随鼠标的十字交叉线
我们在PageLight页面上实现该功能
第一步,PageLight.xaml添加以下代码
<Page ...>
<Grid>
...
<Grid Name="workspace" Grid.Row="2" Grid.Column="1" Background="Black"
MouseLeftButtonDown="ImgMouseLeftButtonDown"
MouseLeftButtonUp="ImgMouseLeftButtonUp"
MouseMove="ImgMouseMove"
MouseWheel="ImgMouseWheel"
MouseLeave="ImgMouseLeave">
<Grid.Resources>
<TransformGroup x:Key="Imageview">
<ScaleTransform/>
<TranslateTransform/>
</TransformGroup>
</Grid.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled"
Cursor="Cross" Focusable="False" x:Name="BackFrame">
<ContentControl Name="imgcontrol">
<Image Name="image" Source="F:\chenggeng\source\Image\Desert.jpg"
Width="Auto" Height="Auto" RenderTransform="{StaticResource Imageview}"
Stretch="Uniform" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</ContentControl>
</ScrollViewer>
<Path Fill="Yellow" Stroke="Blue" Name="crossline"></Path>
</Grid>
</Grid>
</Page>
工作布局Grid中有两个控件,一个是可变换的图像,一个是承载十字交叉线的Path
第二步,PageLight.xaml.cs中添加以下代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace wpfbase
{
public partial class PageLight : Page
{
// 定义十字交叉线
private LineGeometry lineX, lineY;
private GeometryGroup linegroup;
public PageLight()
{
InitializeComponent();
// 初始化十字交叉线
lineX = new LineGeometry();
lineY = new LineGeometry();
linegroup = new GeometryGroup();
crossline.Data = linegroup;
linegroup.Children.Add(lineX);
linegroup.Children.Add(lineY);
this.SizeChanged += new System.Windows.SizeChangedEventHandler(PageLightResized);
// 工作区添加滚轮事件冒泡
workspace.PreviewMouseWheel += (sender, e) => {
var eventArg = new MouseWheelEventArgs(e.MouseDevice,e.Timestamp,e.Delta);
eventArg.RoutedEvent = UIElement.MouseWheelEvent;
eventArg.Source = sender;
workspace.RaiseEvent(eventArg);
};
}
/* ------------页面缩放响应-------------- */
private void PageLightResized(object sender, System.EventArgs e) {
// 窗口及图像的ActualWidth均发生改变
// 窗口实际尺寸,图像比例下实际尺寸
var group = workspace.FindResource("Imageview") as TransformGroup;
var scale = group.Children[0] as ScaleTransform;
var move = group.Children[1] as TranslateTransform;
move.X = 0;
move.Y = 0;
scale.ScaleX = 1;
scale.ScaleY = 1;
}
/* -------------图像缩放处理--------------- */
// img为可视区域,image为图像
private bool mouseDown;
private Point position;
private void ImgMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
if(!image.IsMouseOver && !crossline.IsMouseOver) {return;}
image.CaptureMouse();
mouseDown = true;
position = e.GetPosition(imgcontrol);
Point mouseXY = e.GetPosition(workspace);
}
private void ImgMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
if(!image.IsMouseOver && !crossline.IsMouseOver) {return;}
image.ReleaseMouseCapture();
mouseDown = false;
}
private void ImgMouseMove(object sender, MouseEventArgs e) {
if(!image.IsMouseOver && !crossline.IsMouseOver) {
ClearCrossLine();
return;
}
if(mouseDown) {
DoMouseMove(imgcontrol, e);
}
Point mouse = e.GetPosition(imgcontrol);
FlashCrossLine(mouse);
}
private void ImgMouseLeave(object sender, MouseEventArgs e) {
ClearCrossLine();
}
private void ClearCrossLine() {
lineX.StartPoint = new Point();
lineX.EndPoint = new Point();
lineY.StartPoint = new Point();
lineY.EndPoint = new Point();
}
private void FlashCrossLine(Point mouse) {
var group = workspace.FindResource("Imageview") as TransformGroup;
double scale = (group.Children[0] as ScaleTransform).ScaleX;
var move = group.Children[1] as TranslateTransform;
double W = imgcontrol.ActualWidth;
double H = imgcontrol.ActualHeight;
double w = image.ActualWidth;
double h = image.ActualHeight;
lineX.StartPoint = new Point(mouse.X, Math.Max(0, move.Y+(H-h)/2));
lineX.EndPoint = new Point(mouse.X, Math.Min(H, move.Y+h*scale+(H-h)/2));
lineY.StartPoint = new Point(Math.Max(0, move.X+(W-w)/2), mouse.Y);
lineY.EndPoint = new Point(Math.Min(W, move.X+w*scale+(W-w)/2), mouse.Y);
}
private void DoMouseMove(ContentControl img, MouseEventArgs e) {
if(e.LeftButton != MouseButtonState.Pressed) {return;}
var group = workspace.FindResource("Imageview") as TransformGroup;
double scale = (group.Children[0] as ScaleTransform).ScaleX;
var move = group.Children[1] as TranslateTransform;
var mouseXY = e.GetPosition(img);
move.X += mouseXY.X - position.X;
move.Y += mouseXY.Y - position.Y;
position = mouseXY;
// W+w > 2*move_x > -((2*scale-1)*w + W) 水平平移限制条件
// H+h > 2*move_y > -((2*scale-1)*h + H) 垂直平移限制条件
double W = img.ActualWidth;
double H = img.ActualHeight;
double w = image.ActualWidth;
double h = image.ActualHeight;
if(move.X*2 > W+w-20)
move.X = (W+w-20)/2;
if(-move.X*2 > (2*scale-1)*w+W-20)
move.X = 10-(scale-0.5)*w-W/2;
if(move.Y*2 > H+h-20)
move.Y = (H+h-20)/2;
if(-move.Y*2 > (2*scale-1)*h+H-20)
move.Y = 10-(scale-0.5)*h-H/2;
}
private void ImgMouseWheel(object sender, MouseWheelEventArgs e) {
if(!image.IsMouseOver && !crossline.IsMouseOver) {return;}
var point = e.GetPosition(image);
var group = workspace.FindResource("Imageview") as TransformGroup;
var delta = e.Delta * 0.002;
DoWheelZoom(group, point, delta);
Point mouse = e.GetPosition(imgcontrol);
FlashCrossLine(mouse);
}
private void DoWheelZoom(TransformGroup group, Point point, double delta) {
var transform = group.Children[0] as ScaleTransform;
if (transform.ScaleX + delta < 0.1) return;
transform.ScaleX += delta;
transform.ScaleY += delta;
var transform1 = group.Children[1] as TranslateTransform;
transform1.X -= point.X*delta;
transform1.Y -= point.Y*delta;
}
}
}
代码说明:
-
为了后台实现十字交叉线对鼠标的跟随,我们不在前台xaml文件中添加十字线(后台查找LineGeometry元素的方法比较复杂),直接在后台页面类中定义十字交叉线成员lineX,lineY,linegroup;并在构造函数中初始化十字交叉线。
-
在上一篇中,我们的鼠标响应函数都是针对图像控件的,但是在这一篇中,由于十字交叉线是跟随鼠标的,图像控件响应鼠标的函数都不能被触发,所以我们将鼠标响应函数移接到Grid控件上,鼠标在Gird工作区都能够响应。
-
由于Grid中包含了ScrollViewer,而此处的ScrollViewer功能又是被禁用的,从而导致滚轮事件无法触发。对于这种嵌套式滚动(一个滚动控件嵌套另一个滚动控件)我们在页面的构造函数中添加滚轮事件的冒泡。
-
在鼠标位置判定上,使用控件的IsMouseOver属性代替空控件判断鼠标是否在图像区域
-
在鼠标移动的响应函数中,增加十字交叉线的刷新和清除功能。
-
在Grid控件中增加鼠标离开响应函数,实现离开时清除十字交叉线
效果如下:
WPF跟随图像的十字线