Home > Article > Backend Development > ScreenUnLock-detailed explanation on the use of graphic unlock control
This article mainly introduces the use of WPF graphic unlocking control ScreenUnLock in detail. It has certain reference value. Interested friends can refer to
ScreenUnLock and pattern unlocking on smartphones. Functions are the same. The purpose of unlocking or memorizing graphics is achieved by drawing graphics.
I had a sudden idea to transplant the graphics unlocking function on my mobile phone to WPF. It has also been applied to the company's projects.
Before creating ScreenUnLock, let’s first analyze the implementation idea of graphic unlocking.
1. Create the origin of the nine-square grid (or more grids), and define a coordinate value for each point
2. Provide extended attributes and events related to graphic unlocking to facilitate the definition of the caller. For example: color of points and lines (Color), operation mode (Check|Remember), correct color for verification (RightColor), color for failed verification (ErrorColor), unlock event OnCheckedPoint, memory event OnRememberPoint, etc.;
3. Define the MouseMove event to monitor the line drawing behavior. The line drawing part is also the core of this article. During the line drawing process. The program needs to determine from which point the line starts to be drawn and which point it passes through (excluding already recorded points). Whether the drawing is completed and so on.
4. Line drawing completion, the line drawing completion behavior is processed according to the operation mode. And call related custom events
The general idea is as above, let’s start writing ScreenUnLock step by step
Create ScreenUnLock
public partial class ScreenUnlock : UserControl
Define related properties
/// <summary> /// 验证正确的颜色 /// </summary> private SolidColorBrush rightColor; /// <summary> /// 验证失败的颜色 /// </summary> private SolidColorBrush errorColor; /// <summary> /// 图案是否在检查中 /// </summary> private bool isChecking; public static readonly DependencyProperty PointArrayProperty = DependencyProperty.Register("PointArray", typeof(IList<string>), typeof(ScreenUnlock)); /// <summary> /// 记忆的坐标点 /// </summary> public IList<string> PointArray { get { return GetValue(PointArrayProperty) as IList<string>; } set { SetValue(PointArrayProperty, value); } } /// <summary> /// 当前坐标点集合 /// </summary> private IList<string> currentPointArray; /// <summary> /// 当前线集合 /// </summary> private IList<Line> currentLineList; /// <summary> /// 点集合 /// </summary> private IList<Ellipse> ellipseList; /// <summary> /// 当前正在绘制的线 /// </summary> private Line currentLine; public static readonly DependencyProperty OperationPorperty = DependencyProperty.Register("Operation", typeof(ScreenUnLockOperationType), typeof(ScreenUnlock), new FrameworkPropertyMetadata(ScreenUnLockOperationType.Remember)); /// <summary> /// 操作类型 /// </summary> public ScreenUnLockOperationType Operation { get { return (ScreenUnLockOperationType)GetValue(OperationPorperty); } set { SetValue(OperationPorperty, value); } } public static readonly DependencyProperty PointSizeProperty = DependencyProperty.Register("PointSize", typeof(double), typeof(ScreenUnlock), new FrameworkPropertyMetadata(15.0)); /// <summary> /// 坐标点大小 /// </summary> public double PointSize { get { return Convert.ToDouble(GetValue(PointSizeProperty)); } set { SetValue(PointSizeProperty, value); } } public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(SolidColorBrush), typeof(ScreenUnlock), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), new PropertyChangedCallback((s, e) => { (s as ScreenUnlock).Refresh(); }))); /// <summary> /// 坐标点及线条颜色 /// </summary> public SolidColorBrush Color { get { return GetValue(ColorProperty) as SolidColorBrush; } set { SetValue(ColorProperty, value); } } /// <summary> /// 操作类型 /// </summary> public enum ScreenUnLockOperationType { Remember = 0, Check = 1 }
Initialize ScreenUnLock
public ScreenUnlock() { InitializeComponent(); this.Loaded += ScreenUnlock_Loaded; this.Unloaded += ScreenUnlock_Unloaded; this.MouseMove += ScreenUnlock_MouseMove; //监听绘制事件 } private void ScreenUnlock_Loaded(object sender, RoutedEventArgs e) { isChecking = false; rightColor = new SolidColorBrush(Colors.Green); errorColor = new SolidColorBrush(Colors.Red); currentPointArray = new List<string>(); currentLineList = new List<Line>(); ellipseList = new List<Ellipse>(); CreatePoint(); } private void ScreenUnlock_Unloaded(object sender, RoutedEventArgs e) { rightColor = null; errorColor = null; if (currentPointArray != null) this.currentPointArray.Clear(); if (currentLineList != null) this.currentLineList.Clear(); if (ellipseList != null) ellipseList.Clear(); this.canvasRoot.Children.Clear(); }
Create point
/// <summary> /// 创建点 /// </summary> private void CreatePoint() { canvasRoot.Children.Clear(); int row = 3, column = 3; //三行三列,九宫格 double oneColumnWidth = (this.ActualWidth == 0 ? this.Width : this.ActualWidth) / 3; //单列的宽度 double oneRowHeight = (this.ActualHeight == 0 ? this.Height : this.ActualHeight) / 3; //单列的高度 double leftDistance = (oneColumnWidth - PointSize) / 2; //单列左边距 double topDistance = (oneRowHeight - PointSize) / 2; //单列上边距 for (var i = 0; i < row; i++) { for (var j = 0; j < column; j++) { Ellipse ellipse = new Ellipse() { Width = PointSize, Height = PointSize, Fill = Color, Tag = string.Format("{0}{1}", i, j) }; Canvas.SetLeft(ellipse, j * oneColumnWidth + leftDistance); Canvas.SetTop(ellipse, i * oneRowHeight + topDistance); canvasRoot.Children.Add(ellipse); ellipseList.Add(ellipse); } } }
Create line
private Line CreateLine() { Line line = new Line() { Stroke = Color, StrokeThickness = 2 }; return line; }
The points and lines have been created and defined, and you can start listening for drawing events
private void ScreenUnlock_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) { if (isChecking) //如果图形正在检查中,不响应后续处理 return; if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed) { var point = e.GetPosition(this); HitTestResult result = VisualTreeHelper.HitTest(this, point); Ellipse ellipse = result.VisualHit as Ellipse; if (ellipse != null) { if (currentLine == null) { //从头开始绘制 currentLine = CreateLine(); var ellipseCenterPoint = GetCenterPoint(ellipse); currentLine.X1 = currentLine.X2 = ellipseCenterPoint.X; currentLine.Y1 = currentLine.Y2 = ellipseCenterPoint.Y; currentPointArray.Add(ellipse.Tag.ToString()); Console.WriteLine(string.Join(",", currentPointArray)); currentLineList.Add(currentLine); canvasRoot.Children.Add(currentLine); } else { //遇到下一个点,排除已经经过的点 if (currentPointArray.Contains(ellipse.Tag.ToString())) return; OnAfterByPoint(ellipse); } } else if (currentLine != null) { //绘制过程中 currentLine.X2 = point.X; currentLine.Y2 = point.Y; //判断当前Line是否经过点 ellipse = IsOnLine(); if (ellipse != null) OnAfterByPoint(ellipse); } } else { if (currentPointArray.Count == 0) return; isChecking = true; if (currentLineList.Count + 1 != currentPointArray.Count) { //最后一条线的终点不在点上 //两点一线,点的个数-1等于线的条数 currentLineList.Remove(currentLine); //从已记录的线集合中删除最后一条多余的线 canvasRoot.Children.Remove(currentLine); //从界面上删除最后一条多余的线 currentLine = null; } if (Operation == ScreenUnLockOperationType.Check) { Console.WriteLine("playAnimation Check"); var result = CheckPoint(); //执行图形检查 //执行完成动画并触发检查事件 PlayAnimation(result, () => { if (OnCheckedPoint != null) { this.Dispatcher.BeginInvoke(OnCheckedPoint, this, new CheckPointArgs() { Result = result }); //触发检查完成事件 } }); } else if (Operation == ScreenUnLockOperationType.Remember) { Console.WriteLine("playAnimation Remember"); RememberPoint(); //记忆绘制的坐标 var args = new RememberPointArgs() { PointArray = this.PointArray }; //执行完成动画并触发记忆事件 PlayAnimation(true, () => { if (OnRememberPoint != null) { this.Dispatcher.BeginInvoke(OnRememberPoint, this, args); //触发图形记忆事件 } }); } } }
Judge whether the line passes through a nearby point
/// <summary> /// 两点计算一线的长度 /// </summary> /// <param name="pt1"></param> /// <param name="pt2"></param> /// <returns></returns> private double GetLineLength(double x1, double y1, double x2, double y2) { return Math.Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); //根据两点计算线段长度公式 √((x1-x2)²x(y1-y2)²) } /// <summary> /// 判断线是否经过了某个点 /// </summary> /// <param name="ellipse"></param> /// <returns></returns> private Ellipse IsOnLine() { double lineAB = 0; //当前画线的长度 double lineCA = 0; //当前点和A点的距离 double lineCB = 0; //当前点和B点的距离 double dis = 0; double deciation = 1; //允许的偏差距离 lineAB = GetLineLength(currentLine.X1, currentLine.Y1, currentLine.X2, currentLine.Y2); //计算当前画线的长度 foreach (Ellipse ellipse in ellipseList) { if (currentPointArray.Contains(ellipse.Tag.ToString())) //排除已经经过的点 continue; var ellipseCenterPoint = GetCenterPoint(ellipse); //取当前点的中心点 lineCA = GetLineLength(currentLine.X1, currentLine.Y1, ellipseCenterPoint.X, ellipseCenterPoint.Y); //计算当前点到线A端的长度 lineCB = GetLineLength(currentLine.X2, currentLine.Y2, ellipseCenterPoint.X, ellipseCenterPoint.Y); //计算当前点到线B端的长度 dis = Math.Abs(lineAB - (lineCA + lineCB)); //线CA的长度+线CB的长度>当前线AB的长度 说明点不在线上 if (dis <= deciation) //因为绘制的点具有一个宽度和高度,所以需设定一个允许的偏差范围,让线靠近点就命中之(吸附效果) { return ellipse; } } return null; }
Check whether the points are correct and match them one by one in array order
/// <summary> /// 检查坐标点是否正确 /// </summary> /// <returns></returns> private bool CheckPoint() { //PointArray:正确的坐标值数组 //currentPointArray:当前绘制的坐标值数组 if (currentPointArray.Count != PointArray.Count) return false; for (var i = 0; i < currentPointArray.Count; i++) { if (currentPointArray[i] != PointArray[i]) return false; } return true; }
Record passing points and create a new line
/// <summary> /// 记录经过的点 /// </summary> /// <param name="ellipse"></param> private void OnAfterByPoint(Ellipse ellipse) { var ellipseCenterPoint = GetCenterPoint(ellipse); currentLine.X2 = ellipseCenterPoint.X; currentLine.Y2 = ellipseCenterPoint.Y; currentLine = CreateLine(); currentLine.X1 = currentLine.X2 = ellipseCenterPoint.X; currentLine.Y1 = currentLine.Y2 = ellipseCenterPoint.Y; currentPointArray.Add(ellipse.Tag.ToString()); Console.WriteLine(string.Join(",", currentPointArray)); currentLineList.Add(currentLine); canvasRoot.Children.Add(currentLine); }
/// <summary> /// 获取原点的中心点坐标 /// </summary> /// <param name="ellipse"></param> /// <returns></returns> private Point GetCenterPoint(Ellipse ellipse) { Point p = new Point(Canvas.GetLeft(ellipse) + ellipse.Width / 2, Canvas.GetTop(ellipse) + ellipse.Height / 2); return p; }
When the drawing is completed, the completion animation is executed and Events that trigger response mode
/// <summary> /// 执行动画 /// </summary> /// <param name="result"></param> private void PlayAnimation(bool result, Action callback = null) { Task.Factory.StartNew(() => { this.Dispatcher.Invoke((Action)delegate { foreach (Line l in currentLineList) l.Stroke = result ? rightColor : errorColor; foreach (Ellipse e in ellipseList) if (currentPointArray.Contains(e.Tag.ToString())) e.Fill = result ? rightColor : errorColor; }); Thread.Sleep(1500); this.Dispatcher.Invoke((Action)delegate { foreach (Line l in currentLineList) this.canvasRoot.Children.Remove(l); foreach (Ellipse e in ellipseList) e.Fill = Color; }); currentLine = null; this.currentPointArray.Clear(); this.currentLineList.Clear(); isChecking = false; }).ContinueWith(t => { try { if (callback != null) callback(); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { t.Dispose(); } }); }
Calls for graphics unlock
<local:ScreenUnlock Width="500" Height="500" PointArray="{Binding PointArray, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Operation="Check"> <!--或Remember--> <i:Interaction.Triggers> <i:EventTrigger EventName="OnCheckedPoint"> <Custom:EventToCommand Command="{Binding OnCheckedPoint}" PassEventArgsToCommand="True"/> </i:EventTrigger> <i:EventTrigger EventName="OnRememberPoint"> <Custom:EventToCommand Command="{Binding OnRememberPoint}" PassEventArgsToCommand="True"/> </i:EventTrigger> </i:Interaction.Triggers> </local:ScreenUnlock>
The above is the detailed content of ScreenUnLock-detailed explanation on the use of graphic unlock control. For more information, please follow other related articles on the PHP Chinese website!