欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

WPF图形解锁控件ScreenUnLock使用详解

程序员文章站 2022-06-23 11:07:21
screenunlock 与智能手机上的图案解锁功能一样。通过绘制图形达到解锁或记忆图形的目的。 本人突发奇想,把手机上的图形解锁功能移植到wpf中。也应用到了公司的项目...

screenunlock 与智能手机上的图案解锁功能一样。通过绘制图形达到解锁或记忆图形的目的。

本人突发奇想,把手机上的图形解锁功能移植到wpf中。也应用到了公司的项目中。

在创建screenunlock之前,先来分析一下图形解锁的实现思路。

1.创建九宫格原点(或更多格子),每个点定义一个坐标值

2.提供图形解锁相关扩展属性和事件,方便调用者定义。比如:点和线的颜色(color),操作模式(check|remember),验证正确的颜色(rightcolor), 验证失败的颜色(errorcolor), 解锁事件 oncheckedpoint,记忆事件 onrememberpoint 等;

3.定义mousemove事件监听画线行为。 画线部分也是本文的核心。在画线过程中。程序需判断,线条从哪个点开始绘制,经过了哪个点(排除已经记录的点)。是否完成了绘制等等。

4.画线完成,根据操作模式处理画线完成行为。并调用相关自定义事件

大致思路如上,下面开始一步一步编写screenunlock吧

创建screenunlock

public partial class screenunlock : usercontrol

定义相关属性

/// <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
     }

初始化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();
  }

创建点

/// <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);
    }
   }
  }

创建线

private line createline()
  {
   line line = new line()
   {
    stroke = color,
    strokethickness = 2
   };
   return line;
  }

点和线都创建都定义好了,可以开始监听绘制事件了

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); //触发图形记忆事件
      }
     });
    }
   }
  }

判断线是否经过了附近的某个点

/// <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;
  }

检查点是否正确,按数组顺序逐个匹配之

/// <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;
  }

记录经过点,并创建一条新的线

/// <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;
  }

当绘制完成时,执行完成动画并触发响应模式的事件

/// <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();
    }
   });
  }

图形解锁的调用

<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>

WPF图形解锁控件ScreenUnLock使用详解

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。