WPF圖形解鎖控件ScreenUnLock使用詳解
ScreenUnLock 與智能手機(jī)上的圖案解鎖功能一樣。通過繪制圖形達(dá)到解鎖或記憶圖形的目的。
本人突發(fā)奇想,把手機(jī)上的圖形解鎖功能移植到WPF中。也應(yīng)用到了公司的項(xiàng)目中。
在創(chuàng)建ScreenUnLock之前,先來分析一下圖形解鎖的實(shí)現(xiàn)思路。
1.創(chuàng)建九宮格原點(diǎn)(或更多格子),每個(gè)點(diǎn)定義一個(gè)坐標(biāo)值
2.提供圖形解鎖相關(guān)擴(kuò)展屬性和事件,方便調(diào)用者定義。比如:點(diǎn)和線的顏色(Color),操作模式(Check|Remember),驗(yàn)證正確的顏色(RightColor), 驗(yàn)證失敗的顏色(ErrorColor), 解鎖事件 OnCheckedPoint,記憶事件 OnRememberPoint 等;
3.定義MouseMove事件監(jiān)聽畫線行為。 畫線部分也是本文的核心。在畫線過程中。程序需判斷,線條從哪個(gè)點(diǎn)開始繪制,經(jīng)過了哪個(gè)點(diǎn)(排除已經(jīng)記錄的點(diǎn))。是否完成了繪制等等。
4.畫線完成,根據(jù)操作模式處理畫線完成行為。并調(diào)用相關(guān)自定義事件
大致思路如上,下面開始一步一步編寫ScreenUnLock吧
創(chuàng)建ScreenUnLock
public partial class ScreenUnlock : UserControl
定義相關(guān)屬性
/// <summary>
/// 驗(yàn)證正確的顏色
/// </summary>
private SolidColorBrush rightColor;
/// <summary>
/// 驗(yàn)證失敗的顏色
/// </summary>
private SolidColorBrush errorColor;
/// <summary>
/// 圖案是否在檢查中
/// </summary>
private bool isChecking;
public static readonly DependencyProperty PointArrayProperty = DependencyProperty.Register("PointArray", typeof(IList<string>), typeof(ScreenUnlock));
/// <summary>
/// 記憶的坐標(biāo)點(diǎn)
/// </summary>
public IList<string> PointArray
{
get { return GetValue(PointArrayProperty) as IList<string>; }
set { SetValue(PointArrayProperty, value); }
}
/// <summary>
/// 當(dāng)前坐標(biāo)點(diǎn)集合
/// </summary>
private IList<string> currentPointArray;
/// <summary>
/// 當(dāng)前線集合
/// </summary>
private IList<Line> currentLineList;
/// <summary>
/// 點(diǎn)集合
/// </summary>
private IList<Ellipse> ellipseList;
/// <summary>
/// 當(dāng)前正在繪制的線
/// </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>
/// 坐標(biāo)點(diǎn)大小
/// </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>
/// 坐標(biāo)點(diǎn)及線條顏色
/// </summary>
public SolidColorBrush Color
{
get { return GetValue(ColorProperty) as SolidColorBrush; }
set { SetValue(ColorProperty, value); }
}
/// <summary>
/// 操作類型
/// </summary>
public enum ScreenUnLockOperationType
{
Remember = 0, Check = 1
}
初始化ScreenUnLock
public ScreenUnlock()
{
InitializeComponent();
this.Loaded += ScreenUnlock_Loaded;
this.Unloaded += ScreenUnlock_Unloaded;
this.MouseMove += ScreenUnlock_MouseMove; //監(jiān)聽繪制事件
}
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();
}
創(chuàng)建點(diǎn)
/// <summary>
/// 創(chuàng)建點(diǎn)
/// </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);
}
}
}
創(chuàng)建線
private Line CreateLine()
{
Line line = new Line()
{
Stroke = Color,
StrokeThickness = 2
};
return line;
}
點(diǎn)和線都創(chuàng)建都定義好了,可以開始監(jiān)聽繪制事件了
private void ScreenUnlock_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (isChecking) //如果圖形正在檢查中,不響應(yīng)后續(xù)處理
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
{
//遇到下一個(gè)點(diǎn),排除已經(jīng)經(jīng)過的點(diǎn)
if (currentPointArray.Contains(ellipse.Tag.ToString()))
return;
OnAfterByPoint(ellipse);
}
}
else if (currentLine != null)
{
//繪制過程中
currentLine.X2 = point.X;
currentLine.Y2 = point.Y;
//判斷當(dāng)前Line是否經(jīng)過點(diǎn)
ellipse = IsOnLine();
if (ellipse != null)
OnAfterByPoint(ellipse);
}
}
else
{
if (currentPointArray.Count == 0)
return;
isChecking = true;
if (currentLineList.Count + 1 != currentPointArray.Count)
{
//最后一條線的終點(diǎn)不在點(diǎn)上
//兩點(diǎn)一線,點(diǎn)的個(gè)數(shù)-1等于線的條數(shù)
currentLineList.Remove(currentLine); //從已記錄的線集合中刪除最后一條多余的線
canvasRoot.Children.Remove(currentLine); //從界面上刪除最后一條多余的線
currentLine = null;
}
if (Operation == ScreenUnLockOperationType.Check)
{
Console.WriteLine("playAnimation Check");
var result = CheckPoint(); //執(zhí)行圖形檢查
//執(zhí)行完成動(dòng)畫并觸發(fā)檢查事件
PlayAnimation(result, () =>
{
if (OnCheckedPoint != null)
{
this.Dispatcher.BeginInvoke(OnCheckedPoint, this, new CheckPointArgs() { Result = result }); //觸發(fā)檢查完成事件
}
});
}
else if (Operation == ScreenUnLockOperationType.Remember)
{
Console.WriteLine("playAnimation Remember");
RememberPoint(); //記憶繪制的坐標(biāo)
var args = new RememberPointArgs() { PointArray = this.PointArray };
//執(zhí)行完成動(dòng)畫并觸發(fā)記憶事件
PlayAnimation(true, () =>
{
if (OnRememberPoint != null)
{
this.Dispatcher.BeginInvoke(OnRememberPoint, this, args); //觸發(fā)圖形記憶事件
}
});
}
}
}
判斷線是否經(jīng)過了附近的某個(gè)點(diǎn)
/// <summary>
/// 兩點(diǎn)計(jì)算一線的長(zhǎng)度
/// </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)); //根據(jù)兩點(diǎn)計(jì)算線段長(zhǎng)度公式 √((x1-x2)²x(y1-y2)²)
}
/// <summary>
/// 判斷線是否經(jīng)過了某個(gè)點(diǎn)
/// </summary>
/// <param name="ellipse"></param>
/// <returns></returns>
private Ellipse IsOnLine()
{
double lineAB = 0; //當(dāng)前畫線的長(zhǎng)度
double lineCA = 0; //當(dāng)前點(diǎn)和A點(diǎn)的距離
double lineCB = 0; //當(dāng)前點(diǎn)和B點(diǎn)的距離
double dis = 0;
double deciation = 1; //允許的偏差距離
lineAB = GetLineLength(currentLine.X1, currentLine.Y1, currentLine.X2, currentLine.Y2); //計(jì)算當(dāng)前畫線的長(zhǎng)度
foreach (Ellipse ellipse in ellipseList)
{
if (currentPointArray.Contains(ellipse.Tag.ToString())) //排除已經(jīng)經(jīng)過的點(diǎn)
continue;
var ellipseCenterPoint = GetCenterPoint(ellipse); //取當(dāng)前點(diǎn)的中心點(diǎn)
lineCA = GetLineLength(currentLine.X1, currentLine.Y1, ellipseCenterPoint.X, ellipseCenterPoint.Y); //計(jì)算當(dāng)前點(diǎn)到線A端的長(zhǎng)度
lineCB = GetLineLength(currentLine.X2, currentLine.Y2, ellipseCenterPoint.X, ellipseCenterPoint.Y); //計(jì)算當(dāng)前點(diǎn)到線B端的長(zhǎng)度
dis = Math.Abs(lineAB - (lineCA + lineCB)); //線CA的長(zhǎng)度+線CB的長(zhǎng)度>當(dāng)前線AB的長(zhǎng)度 說明點(diǎn)不在線上
if (dis <= deciation) //因?yàn)槔L制的點(diǎn)具有一個(gè)寬度和高度,所以需設(shè)定一個(gè)允許的偏差范圍,讓線靠近點(diǎn)就命中之(吸附效果)
{
return ellipse;
}
}
return null;
}
檢查點(diǎn)是否正確,按數(shù)組順序逐個(gè)匹配之
/// <summary>
/// 檢查坐標(biāo)點(diǎn)是否正確
/// </summary>
/// <returns></returns>
private bool CheckPoint()
{
//PointArray:正確的坐標(biāo)值數(shù)組
//currentPointArray:當(dāng)前繪制的坐標(biāo)值數(shù)組
if (currentPointArray.Count != PointArray.Count)
return false;
for (var i = 0; i < currentPointArray.Count; i++)
{
if (currentPointArray[i] != PointArray[i])
return false;
}
return true;
}
記錄經(jīng)過點(diǎn),并創(chuàng)建一條新的線
/// <summary>
/// 記錄經(jīng)過的點(diǎn)
/// </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>
/// 獲取原點(diǎn)的中心點(diǎn)坐標(biāo)
/// </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;
}
當(dāng)繪制完成時(shí),執(zhí)行完成動(dòng)畫并觸發(fā)響應(yīng)模式的事件
/// <summary>
/// 執(zhí)行動(dòng)畫
/// </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();
}
});
}
圖形解鎖的調(diào)用
<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>

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
asp.net sql 數(shù)據(jù)庫(kù)處理函數(shù)命令
asp.net sql 數(shù)據(jù)庫(kù)處理函數(shù)命令 ,需要的朋友可以參考下。2009-10-10
.NET使用?OpenTelemetry?Traces?追蹤應(yīng)用程序的方法
OpenTelemetry Traces是OpenTelemetry提供的一種遙測(cè)數(shù)據(jù)類型,用于記錄和描述在分布式系統(tǒng)中的單個(gè)操作或工作單元的生命周期,這篇文章主要介紹了.NET中使用OpenTelemetry Traces追蹤應(yīng)用程序,需要的朋友可以參考下2024-06-06
Gridview使用CheckBox全選與單選采用js實(shí)現(xiàn)同時(shí)高亮顯示選擇行
Gridview使用CheckBox單選與全選功能再次進(jìn)行簡(jiǎn)單演示,選中的行,使用高亮顯示,讓用戶一目了然看到哪一行被選擇了,在項(xiàng)目中很實(shí)用的,開發(fā)中的朋友們可要考慮一下哦2013-01-01
ASP.NET MVC HtmlHelper如何擴(kuò)展
ASP.NET MVC 中HtmlHelper方法為我們提供很多html標(biāo)簽,只需在頁(yè)面調(diào)用就行了,但是微軟并沒有把所有的html標(biāo)簽都對(duì)應(yīng)有了擴(kuò)展方法,需要我們自定義HtmlHelper,來滿足我們需要。2016-05-05
Asp.Net二級(jí)域名共享Forms身份驗(yàn)證、下載站/圖片站的授權(quán)訪問控制
我們平時(shí)一般在做圖片或者文件下載權(quán)限控制的時(shí)候基本都是控制到下載頁(yè)面的,當(dāng)你的下載地址暴露后,瀏覽者就直接可以通過文件地址進(jìn)行下載了,這時(shí)候也就出現(xiàn)了我們常說的盜鏈2012-02-02

