WPF实现手势解锁
桌面程序的解锁方式一般是账号密码,互联网的可以使用扫码解锁,甚至人脸识别。但扫码需要网络,人脸识别又较复杂。所以就想把安卓常用的手势解锁移植到桌面程序上。
先来张效果图,有兴趣的往下看,没兴趣的打扰了。
WPF手势解锁使用鼠标点击事件,鼠标移动事件,鼠标弹起事件实现。自定义了三个属性(初始化颜色,选中颜色,选中点的集合),一个事件(绘制完成后触发的事件)。
实现的功能:
绘制过程中直线随鼠标移动的效果
绘制两个连接点的连线
绘制完成后可调用的事件
实现初始化颜色,选中颜色,选择连接点依赖属性
源码主要说明:
1.构造函数,完成事件注册
1 /// <summary> 2 /// 构造函数 3 /// </summary> 4 public ScreenUnlock() 5 { 6 InitializeComponent(); 7 Points = new List<int>(); 8 this.Loaded += ScreenUnlock_Loaded; 9 this.MouseDown += ScreenUnlock_MouseDown; 10 this.MouseUp += ScreenUnlock_MouseUp; 11 this.MouseMove += ScreenUnlock_MouseMove; 12 }
2.窗体加载事件
绘制九宫格,tag用动态类型保存了实际位置(Point)和序号(Loaction)
1 /// <summary> 2 /// Load事件,绘制九宫格 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void ScreenUnlock_Loaded(object sender, RoutedEventArgs e) 7 { 8 canvas.Children.Clear(); 9 //为了保证正方形 10 var distance = Math.Min(this.ActualWidth == 0 ? this.Width : this.ActualWidth, this.ActualHeight == 0 ? this.Height : this.ActualHeight) / 3; 11 double left = (distance - PointSize) / 2; 12 for (var i = 0; i < 3; i++) 13 { 14 for (var j = 0; j < 3; j++) 15 { 16 var x = j * distance + left; 17 var y = i * distance + left; 18 Ellipse ellipse = new Ellipse() 19 { 20 Width = PointSize, 21 Height = PointSize, 22 Fill = Color, 23 Tag = new 24 { 25 Point = new Point(x + PointSize / 2, y + PointSize / 2), 26 Location = i * 3 + j + 1 27 } 28 }; 29 ellipse.SetValue(Canvas.LeftProperty, x); 30 ellipse.SetValue(Canvas.TopProperty, y); 31 Canvas.SetLeft(ellipse, x); 32 Canvas.SetTop(ellipse, y); 33 canvas.Children.Add(ellipse); 34 } 35 } 36 }
3.鼠标左键点击事件
3.1清空了除九宫格之外所有元素
3.2判断点击位置是否是圆点位置,如果不是则不处理,否则记录当前位置用于画线,一条是跟踪鼠标的线(currentLine),另一个是为了显示选中的圆点的连线,此处记录绘制线的第一个点(currentEllipse与后续经过的点的连线).
1 private void ScreenUnlock_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) 2 { 3 if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed) 4 { 5 //每次点击都是重新绘制,清空除了九宫格的所有元素 6 while (canvas.Children.Count > 9) 7 canvas.Children.RemoveAt(canvas.Children.Count - 1); 8 ellipseList.Clear(); 9 currentEllipse = null; 10 Points.Clear(); 11 12 //再次点击时需要先把颜色修改为初始化颜色 13 foreach (Shape item in canvas.Children) 14 item.Fill = Color; 15 16 //获取当前鼠标位置 17 var point = e.GetPosition(this); 18 //鼠标所在位置是否有圆点 19 if (VisualTreeHelper.HitTest(this, point).VisualHit is Ellipse ellipse) //鼠标经过圆点 20 { 21 currentEllipse = ellipse; 22 ellipseList.Add(ellipse); 23 Points.Add((int)((dynamic)ellipse.Tag).Location); 24 var p = (Point)((dynamic)currentEllipse.Tag).Point; 25 currentLine = new Line() 26 { 27 Stroke = Color, 28 StrokeThickness = PointSize / 2, 29 X1 = p.X, 30 Y1 = p.Y, 31 X2 = p.X, 32 Y2 = p.Y 33 }; 34 } 35 } 36 }
4.鼠标移动事件
4.1绘制跟随鼠标的线
4.2判断是否经过之前没经过的圆点,绘线过程中,一个点只能用一次。经过的点保存在ellipseList集合中。
4.3如果经过未曾经过的点,则与上个经过的点绘制直线,并且重新赋值当前点currentEllipse,重新定义跟随鼠标的线(currentLine)的起始点为该点。
4.4把点添加到Points集合中
1 /// <summary> 2 /// 鼠标移动 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void ScreenUnlock_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) 7 { 8 //鼠标左键处于点击状态 9 if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed) 10 { 11 //获取当前鼠标位置 12 var point = e.GetPosition(this); 13 14 ///当没有遇到圆点之前绘制跟随鼠标的线 15 if (currentLine != null) 16 { 17 canvas.Children.Remove(currentLine); 18 currentLine.X2 = point.X; 19 currentLine.Y2 = point.Y; 20 canvas.Children.Add(currentLine); 21 } 22 23 //线跟着移动 24 if (VisualTreeHelper.HitTest(this, point).VisualHit is Ellipse ellipse && currentEllipse != null) 25 { 26 var p1 = (Point)((dynamic)currentEllipse.Tag).Point; 27 var p = (Point)((dynamic)ellipse.Tag).Point; 28 if (p1 != p) //鼠标经过圆点 29 { 30 //如果不包含该圆点,一个点只能用一次 31 if (!ellipseList.Contains(ellipse)) 32 { 33 //绘制当前点和上个点之间的连线 34 var t = new Line() 35 { 36 Stroke = Color, 37 StrokeThickness = PointSize / 2, 38 X1 = p1.X, 39 Y1 = p1.Y, 40 X2 = p.X, 41 Y2 = p.Y 42 }; 43 //修改当前点 44 currentEllipse = ellipse; 45 ellipseList.Add(ellipse); 46 canvas.Children.Add(t); 47 Points.Add((int)((dynamic)ellipse.Tag).Location); 48 if (currentLine != null) 49 { 50 canvas.Children.Remove(currentLine); 51 currentLine.X1 = p.X; 52 currentLine.Y1 = p.Y; 53 currentLine.X2 = p.X; 54 currentLine.Y2 = p.Y; 55 canvas.Children.Add(currentLine); 56 } 57 } 58 } 59 } 60 } 61 }
5.鼠标左键弹起事件
5.1鼠标弹起时,修改所有经过的点以及点之间的连线颜色。
5.2清空所有使用的临时的变量,currentEllipse,currentLine
5.3触发绘制后触发的事件(AfterDraw)
1 /// <summary> 2 /// 鼠标左键弹起 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void ScreenUnlock_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) 7 { 8 if (canvas.Children.Count > 9) 9 foreach (Shape item in canvas.Children) 10 if (item is Line) 11 item.Stroke = SelectedColor; 12 else if (item is Ellipse ellipse && ellipseList.Contains(ellipse)) 13 item.Fill = SelectedColor; 14 currentEllipse = null; 15 currentLine = null; 16 canvas.Children.Remove(currentLine); 17 RaiseEvent(new RoutedEventArgs(AfterDrawEvent)); 18 }
6.选中点和线的颜色,SelectedColor属性
设置该颜色时,需要同时修改选中的点和线的颜色。
1 /// <summary> 2 /// 选中的颜色 3 /// </summary> 4 public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register("SelectedColor", typeof(SolidColorBrush), typeof(ScreenUnlock), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Green), new PropertyChangedCallback((s, e) => 5 { 6 var t = s as ScreenUnlock; 7 if (t.canvas.Children.Count > 9) 8 for (int i = 9; i < t.canvas.Children.Count; i++) 9 { 10 Shape item = t.canvas.Children[i] as Shape; 11 if (item is Line) 12 item.Stroke = e.NewValue as SolidColorBrush; 13 else if (item is Ellipse ellipse) 14 item.Fill = e.NewValue as SolidColorBrush; 15 } 16 }))); 17 18 /// <summary> 19 /// 选中的颜色 20 /// </summary> 21 public SolidColorBrush SelectedColor 22 { 23 get { return GetValue(SelectedColorProperty) as SolidColorBrush; } 24 set { SetValue(SelectedColorProperty, value); } 25 }
7.绘制用的点的集合Points
绑定Points属性,后台就可以获取到绘制图案经历的点的集合。
8.其他代码
其他包含初始颜色,绘制完成以后触发的事件,以及使用到的变量。见源码。
9.利用该控件实现解锁
9.1绑定三个属性一个事件,分别是初始化颜色(Color),选中的颜色(SelectedColor),经过的点(Points)
9.2SelectedColor绑定方式
需要一个转换器(验证正确与否与颜色的转换),如果验证正确,则显示绿色,否则显示红色。
9.3如果连接的点太少时,则需进行提示,并且恢复原来的状态(即把选中的颜色设置为初始化的颜色)
参考:
https://www.cnblogs.com/ShenNan/p/5587009.html
源码:
没找到上传附件,附上码云地址。
https://gitee.com/yiyecao/temporary-components