1 需求描述

绘制物体外框线条盒子 中介绍了绘制物体外框长方体的方法,本文将介绍物体投影到屏幕上的二维外框绘制方法。

  • 点选物体:点击物体,可以选中物体,按住 Ctrl 追加选中,选中的物体设置为红色。
  • 框选物体:拖拽鼠标,屏幕上会出现滑动框,滑动框内的物体会被选中,选中的物体设置为红色。
  • 绘制外边框:给选中的物体绘制外边框(选中框)。

滑动框效果如下:

选中边框效果如下:

本文完整代码见→ Unity3D点选物体、框选物体、绘制外边框。

2 需求实现

2.1 场景搭建

1)场景对象

说明:Plane 的 Layer 设置为 Plane (值为 6)。

2)滑动框

拖拽鼠标时,屏幕上会出现滑动框。SlideBox 对象用于显示滑动框,其 Image 组件中,Source Image 设置为红色滑动框 Sprite,去掉 Raycast Target 勾选,Image Type 设置为 Sliced;RectTransform 组件中,Pivot 设置为 (0, 0),Width、Height 都设置为 0。参数设置如下:

滑动框图片如下:

说明:滑动框图片是 png 格式,中间部分都是半透明的,图片导入 Unity 后,需要修改 Texture Type 为 Sprite,Sprite Mode 设置为 Multiple,并且需要在 Sprite Editor 中 编辑 border(九宫格格式),如下:

2)选框

选中物体后,选中的物体边界会显示外边框(选框)。SelectBox 对象用于显示选框,其 Image 组件中,Source Image 设置为黄色外框 Sprite,去掉 Raycast Target 勾选,Image Type 设置为 Sliced;RectTransform 组件中,Pivot 设置为 (0, 0),Width、Height 都设置为 0。参数设置如下:

外框图片如下:

说明:外框图片是 png 格式,并且除了黄色边角,其他部分都是透明的,图片导入 Unity 后,需要修改 Texture Type 为 Sprite,Sprite Mode 设置为 Multiple,并且需要在 Sprite Editor 中 编辑 border(九宫格格式),如下:

2.2 代码

EventDetector.cs

using UnityEngine;public class EventDetector : MonoBehaviour { // 事件检测器private MyEventType eventType = MyEventType.None; // 事件类型private MyEventType lastEventType = MyEventType.None; // 上次事件类型private float scroll; // 滑轮滑动刻度private bool detecting; // 事件检测中private Vector3 clickDownMousePos; // 鼠标按下时的坐标private const float dragThreshold = 1; // 识别为拖拽的鼠标偏移private void Update() {detecting = true;DetectMouseEvent();DetectScrollEvent();UpgradeMouseEvent();detecting = false;lastEventType = eventType;}private void DetectMouseEvent() { // 检测鼠标事件if (Input.GetMouseButtonDown(0)) { // Click DowneventType = MyEventType.ClickDown;clickDownMousePos = Input.mousePosition;} else if (Input.GetMouseButtonUp(0)) {if (IsDragEvent(eventType)) { // End DrageventType = MyEventType.EndDrag;} else { // Click UpeventType = MyEventType.ClickUp;}} else if (Input.GetMouseButton(0)) {if (IsDragEvent(eventType)) { // DrageventType = MyEventType.Drag;} else if (Vector3.Distance(clickDownMousePos, Input.mousePosition) > dragThreshold) { // Begin DrageventType = MyEventType.BeginDrag;} else { // ClickeventType = MyEventType.Click;}} else {eventType = MyEventType.None;}}private void DetectScrollEvent() { // 检测滑轮事件if (eventType != MyEventType.None&& (!IsBeginEvent(eventType) || lastEventType != MyEventType.None && !IsScrollEvent(lastEventType))) {scroll = 0;return;}float temScroll = Input.GetAxis("Mouse ScrollWheel");if (Mathf.Abs(scroll) < float.Epsilon && Mathf.Abs(temScroll) > float.Epsilon) { // Begin ScrolleventType = MyEventType.BeginScroll;scroll = temScroll;} else if (Mathf.Abs(scroll) > float.Epsilon && Mathf.Abs(temScroll) < float.Epsilon) { // End ScrolleventType = MyEventType.EndScroll;scroll = temScroll;} else if (Mathf.Abs(temScroll) > float.Epsilon) { // ScrolleventType = MyEventType.Scroll;scroll = temScroll;} else {scroll = 0;}}private void UpgradeMouseEvent() { // 升级鼠标事件(关联键盘事件)if (eventType == MyEventType.None) {return;}if (IsBeginEvent(eventType)) {if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) {AddKeyType("Ctrl");} else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) {AddKeyType("Alt");}} else {ContinueKeyType(); // 保持按键事件}}public MyEventType EventType() { // 事件类型if (detecting) {return lastEventType;}return eventType;}public bool HasClickEvent() { // 是否有点击事件MyEventType type = EventType();return IsClickEvent(type);}public bool HasDragEvent() { // 是否有拖拽事件MyEventType type = EventType();return IsDragEvent(type);}public bool HasScrollEvent() { // 是否有滑轮事件MyEventType type = EventType();return IsScrollEvent(type);}public bool HasCtrlScrollEvent() { // 是否有Ctrl滑轮事件MyEventType type = EventType();return type >= MyEventType.BeginCtrlScroll && type <= MyEventType.EndCtrlScroll;}public bool IsBeginDrag() { // 是否是开始拖拽类型事件MyEventType type = EventType();return type == MyEventType.BeginDrag || type == MyEventType.BeginCtrlDrag || type == MyEventType.BeginAltDrag;}public float Scroll() { // 鼠标滑轮滑动刻度if (HasScrollEvent()) {return scroll;}return 0;}private bool IsClickEvent(MyEventType type) { // 是否是点击事件return type >= MyEventType.ClickDown && type <= MyEventType.CtrlClickUp;}private bool IsDragEvent(MyEventType type) { // 是否是拖拽事件return type >= MyEventType.BeginDrag && type <= MyEventType.EndAltDrag;}private bool IsScrollEvent(MyEventType type) { // 是否是滑轮事件return type >= MyEventType.BeginScroll && type <= MyEventType.EndCtrlScroll;}private bool IsBeginEvent(MyEventType type) { // 是否是开始类型事件return type == MyEventType.ClickDown|| type == MyEventType.BeginDrag|| type == MyEventType.BeginCtrlDrag|| type == MyEventType.BeginAltDrag|| type == MyEventType.BeginScroll|| type == MyEventType.BeginCtrlScroll;}private bool HasCtrlKey(MyEventType type) { // 是否有Ctrl按键事件return type >= MyEventType.CtrlClickDown && type <= MyEventType.CtrlClickUp|| type >= MyEventType.BeginCtrlDrag && type <= MyEventType.EndCtrlDrag|| type >= MyEventType.BeginCtrlScroll && type <= MyEventType.EndCtrlScroll;}private bool HasAltKey(MyEventType type) { // 是否有Alt按键事件return type >= MyEventType.BeginAltDrag && type <= MyEventType.EndAltDrag;}private void ContinueKeyType() { // 保持按键事件if (HasCtrlKey(lastEventType)) {AddKeyType("Ctrl");} else if (HasAltKey(lastEventType)) {AddKeyType("Alt");}}private void AddKeyType(string key) { // 添加按键事件if ("Ctrl".Equals(key)) {if (eventType == MyEventType.ClickDown) { // 点击事件eventType = MyEventType.CtrlClickDown;} else if (eventType == MyEventType.Click) {eventType = MyEventType.CtrlClick;} else if (eventType == MyEventType.ClickUp) {eventType = MyEventType.CtrlClickUp;} else if (eventType == MyEventType.BeginDrag) { // 拖拽事件eventType = MyEventType.BeginCtrlDrag;} else if (eventType == MyEventType.Drag) {eventType = MyEventType.CtrlDrag;} else if (eventType == MyEventType.EndDrag) {eventType = MyEventType.EndCtrlDrag;} else if (eventType == MyEventType.BeginScroll) { // 滑轮事件eventType = MyEventType.BeginCtrlScroll;} else if (eventType == MyEventType.Scroll) {eventType = MyEventType.CtrlScroll;} else if (eventType == MyEventType.EndScroll) {eventType = MyEventType.EndCtrlScroll;}} else if ("Alt".Equals(key)) {if (eventType == MyEventType.BeginDrag) { // 拖拽事件eventType = MyEventType.BeginAltDrag;} else if (eventType == MyEventType.Drag) {eventType = MyEventType.AltDrag;} else if (eventType == MyEventType.EndDrag) {eventType = MyEventType.EndAltDrag;}}}
}public enum MyEventType { // 事件类型None = 0,ClickDown = 1,Click = 2,ClickUp = 3,CtrlClickDown = 4,CtrlClick = 5,CtrlClickUp = 6,BeginDrag = 10,Drag = 11,EndDrag = 12,BeginCtrlDrag = 13,CtrlDrag = 14,EndCtrlDrag = 15,BeginAltDrag = 16,AltDrag = 17,EndAltDrag = 18,BeginScroll = 20,Scroll = 21,EndScroll = 22,BeginCtrlScroll = 23,CtrlScroll = 24,EndCtrlScroll = 25
}

说明: EventDetector 脚本组件挂在相机下,用于统一管理事件。点选物体(ClickUp / Ctrl + ClickUp)、滑动选框(Drag)、场景变换(Ctrl + Drag / Alt + Drag)都有鼠标事件,这些事件相互冲突,不便于在每个类里都去捕获鼠标和键盘事件,因此需要 EventDetector 统一管理事件。

ClickSelect.cs

using System.Collections.Generic;
using UnityEngine;public class ClickSelect : MonoBehaviour { // 点选物体private EventDetector eventDetector; // 鼠标事件检测器private List<Transform> targets; // 选中的游戏对象private List<Transform> loseFocus; // 失焦的游戏对象private RaycastHit hit; // 碰撞信息private void Awake() {targets = new List<Transform>();loseFocus = new List<Transform>();eventDetector = Camera.main.GetComponent<EventDetector>();GameObject.Find("Work").GetComponent<SlideSelect>().targetsChangedHandler += SetTargets;}private void Update() {if (eventDetector.EventType() == MyEventType.ClickUp || eventDetector.EventType() == MyEventType.CtrlClickUp) {Transform hitTrans = GetHitTrans();if (hitTrans == null || hitTrans.gameObject.layer == LayerMask.NameToLayer("Plane")) { // 未选中物体或点到地面, 全部取消选中targets.ForEach(obj => loseFocus.Add(obj));targets.Clear();}else if (eventDetector.EventType() == MyEventType.CtrlClickUp) {if (targets.Contains(hitTrans)) { // Ctrl重复选中, 取消选中loseFocus.Add(hitTrans);targets.Remove(hitTrans);} else { // Ctrl追加选中targets.Add(hitTrans);}} else { // 单选targets.ForEach(trans => loseFocus.Add(trans));loseFocus.Remove(hitTrans);targets.Clear();targets.Add(hitTrans);}UpdateSelectColor();RectPainter.DrawRect(targets);}}private void UpdateSelectColor() { // 更新选中的物体颜色foreach(var item in loseFocus) {item.GetComponent<Renderer>().material.color = Color.gray;}foreach(var item in targets) {item.GetComponent<Renderer>().material.color = Color.red;}loseFocus.Clear();}private void SetTargets(List<Transform> targets) { // 框选时触发this.targets.ForEach(trans => loseFocus.Add(trans));if (targets == null) {this.targets.Clear();} else {this.targets = targets;this.targets.ForEach(trans => loseFocus.Remove(trans));}UpdateSelectColor();}private Transform GetHitTrans() { // 获取屏幕射线碰撞的物体Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);if (Physics.Raycast(ray, out hit)) {return hit.transform;}return null;}
}

说明:ClickSelect 脚本组件挂在 Work 对象下,用于点选物体。

SlideSelect.cs

using System;
using System.Collections.Generic;
using UnityEngine;public class SlideSelect : MonoBehaviour { // 滑动框选物体public Action<List<Transform>> targetsChangedHandler; // 框选目标改变时的处理器private EventDetector eventDetector; // 鼠标事件检测器private RectTransform slideTrans; // 滑动选框private Vector3 preMousePos; // 鼠标滑动前的位置private Transform work; // 需要检测是否被框选的物体根对象private List<Transform> targets; // 框选的目标对象private void Awake() {slideTrans = GameObject.Find("Canvas/SlideBox").GetComponent<RectTransform>();work = GameObject.Find("Work").transform;eventDetector = Camera.main.GetComponent<EventDetector>();}private void Update() {if (eventDetector.EventType() == MyEventType.BeginDrag) {preMousePos = Input.mousePosition;} else if (eventDetector.EventType() == MyEventType.EndDrag) {Rect rect = slideTrans.rect;rect.position = slideTrans.position;targets = RectPainter.DrawRect(work, rect);targetsChangedHandler?.Invoke(targets);ClearRect();} else if (eventDetector.EventType() == MyEventType.Drag) {DrawRect();}}private void DrawRect() { // 绘制滑动选框float minX = Mathf.Min(Input.mousePosition.x, preMousePos.x);float minY = Mathf.Min(Input.mousePosition.y, preMousePos.y);slideTrans.position = new Vector3(minX, minY, 0);Vector3 delta = Input.mousePosition - preMousePos;slideTrans.sizeDelta = new Vector2(Mathf.Abs(delta.x), Mathf.Abs(delta.y));}private void ClearRect() { // 清除滑动选框slideTrans.sizeDelta = Vector2.zero;}
}

说明:SlideSelect 脚本组件挂在 Work 对象下,用于滑动框选物体。

RectDetector.cs

using System;
using System.Collections.Generic;
using UnityEngine;public class RectDetector { // 边框检测器public static Rect GetRect(List<Transform> targets) { // 获取物体的外边框(包含子对象)if (targets != null && targets.Count > 0) {Rect[] rects = new Rect[targets.Count];for (int i = 0; i < targets.Count; i++) {rects[i] = GetRect(targets[i]);}return GetRect(rects);}return new Rect();}public static Rect GetCurrRect(List<Transform> targets) { // 获取物体的外边框(不包含子对象)if (targets != null && targets.Count > 0) {Rect[] rects = new Rect[targets.Count];for (int i = 0; i < targets.Count; i++) {rects[i] = GetCurrRect(targets[i]);}return GetRect(rects);}return new Rect();}public static Rect GetRect(Transform transform) { // 获取物体外边框(包含子物体)Rect rect = GetInitRect();ForAllChildren(transform, trans => {Rect rect1 = GetCurrRect(trans);rect.xMin = Mathf.Min(rect.xMin, rect1.xMin);rect.yMin = Mathf.Min(rect.yMin, rect1.yMin);rect.xMax = Mathf.Max(rect.xMax, rect1.xMax);rect.yMax = Mathf.Max(rect.yMax, rect1.yMax);});return rect;}public static Rect GetCurrRect(Transform transform) { // 获取物体外边框(不包含子对象)Rect rect = GetInitRect();Vector3[] vertices = GetVertices(transform);if (vertices != null && vertices.Length > 0) {for (int i = 0; i < vertices.Length; i++) {Vector3 screenPos = Camera.main.WorldToScreenPoint(vertices[i]);rect.xMin = Mathf.Min(rect.xMin, screenPos.x);rect.yMin = Mathf.Min(rect.yMin, screenPos.y);rect.xMax = Mathf.Max(rect.xMax, screenPos.x);rect.yMax = Mathf.Max(rect.yMax, screenPos.y);}}return rect;}private static Rect GetRect(Rect[] rects) { // 合并一组边框if (rects == null || rects.Length == 0) {return new Rect();}Rect rect = rects[0];for (int i = 1; i < rects.Length; i++) {rect.xMin = Mathf.Min(rect.xMin, rects[i].xMin);rect.yMin = Mathf.Min(rect.yMin, rects[i].yMin);rect.xMax = Mathf.Max(rect.xMax, rects[i].xMax);rect.yMax = Mathf.Max(rect.yMax, rects[i].yMax);}return rect;}private static Rect GetInitRect() { // 获取初始的边框Rect rect = new Rect();rect.xMin = float.MaxValue;rect.yMin = float.MaxValue;rect.xMax = float.MinValue;rect.yMax = float.MinValue;return rect;}private static Vector3[] GetVertices(Transform transform) { // 获取网格顶点的世界坐标if (transform.GetComponent<MeshFilter>() == null || transform.GetComponent<MeshFilter>().mesh == null) {return null;}Vector3[] vertices = transform.GetComponent<MeshFilter>().mesh.vertices;for (int i = 0; i < vertices.Length; i++){vertices[i] = transform.TransformPoint(vertices[i]);}return vertices;}private static void ForAllChildren(Transform transform, Action<Transform> action) { // 对所有子对象执行活动if (transform == null || action == null) {return;}Transform[] children = transform.GetComponentsInChildren<Transform>();for(int i = 0; i < children.Length; i++) {action(children[i]);}}
}

说明:RectDetector 通过遍历 mesh 的所有顶点,并将其投射到屏幕上,以计算出物体的屏幕选框大小。

RectPainter.cs

using System;
using System.Collections.Generic;
using UnityEngine;public class RectPainter { // 矩形选框渲染器private const float border = 5; // 矩形选框的边界宽度private static RectPainter instance; // 单例private RectTransform selectTrans; // 选框private List<Transform> targets; // 选中的游戏对象private RectPainter() {selectTrans = GameObject.Find("Canvas/SelectBox").transform as RectTransform;Camera.main.GetComponent<SceneController>().camChangedHandler += DrawRect;}public static RectPainter GetInstance() { // 获取单例if (instance == null) {instance = new RectPainter();}return instance;}public static void DrawRect(List<Transform> targets) { // 绘制被选中物体的外边框(包含子对象)if (instance != null) {instance.targets = targets;Rect rect = RectDetector.GetRect(targets);instance.DrawRect(rect);}}public static void DrawCurrRect(List<Transform> targets) { // 绘制被选中物体的外边框(不包含子对象)if (instance != null) {instance.targets = targets;Rect rect = RectDetector.GetCurrRect(targets);instance.DrawRect(rect);}}public static List<Transform> DrawRect(Transform root, Rect rect) { // 绘制root下面的在rect内的物体的外边框if (instance != null) {instance.targets = new List<Transform>();ForAllChildren(root, trans => {Rect rect1 = RectDetector.GetCurrRect(trans);if (ContainsRect(rect, rect1)) {instance.targets.Add(trans);}});instance.DrawCurrRect();return instance.targets;}return null;}private void DrawRect() { // 绘制边框(包含子对象)Rect rect = RectDetector.GetRect(targets);DrawRect(rect);}private void DrawCurrRect() { // 绘制边框(不包含子对象)Rect rect = RectDetector.GetCurrRect(targets);DrawRect(rect);}private void DrawRect(Rect rect) { // 绘制边框selectTrans.position = new Vector3(rect.x - border, rect.y - border, 0);selectTrans.sizeDelta = new Vector2(rect.width + border * 2, rect.height + border * 2);}private static bool ContainsRect(Rect rect1, Rect rect2) { // 判断rect1是否包含rect2if (rect1.width <= 0 || rect1.height <= 0 || rect2.width <= 0 || rect2.height <= 0) {return false;}if (rect2.xMin < rect1.xMin || rect2.yMin < rect1.yMin || rect2.xMax > rect1.xMax || rect2.yMax > rect1.yMax) {return false;}return true;}private static void ForAllChildren(Transform transform, Action<Transform> action) { // 对所有子对象执行活动if (transform == null || action == null) {return;}Transform[] children = transform.GetComponentsInChildren<Transform>();for(int i = 0; i < children.Length; i++) {action(children[i]);}}
}

SceneController.cs

using System;
using UnityEngine;public class SceneController : MonoBehaviour {private EventDetector eventDetector; // 鼠标事件检测器public Action camChangedHandler; // 相机改变处理器private Transform cam; // 相机private float nearPlan; // 近平面private Vector3 preMousePos; // 上一帧的鼠标坐标private void Awake() {cam = Camera.main.transform;nearPlan = Camera.main.nearClipPlane;eventDetector = cam.GetComponent<EventDetector>();}private void Update() { // 更新场景(Ctrl+Scroll: 缩放场景, Ctrl+Drag: 平移场景, Alt+Drag: 旋转场景)if (eventDetector.HasCtrlScrollEvent()) { // 缩放场景ScaleScene(eventDetector.Scroll());} else if (eventDetector.IsBeginDrag()) {preMousePos = Input.mousePosition;} else if (eventDetector.HasDragEvent()) {Vector3 offset = Input.mousePosition - preMousePos;if (eventDetector.EventType() == MyEventType.CtrlDrag) { // 移动场景MoveScene(offset);} else if (eventDetector.EventType() == MyEventType.AltDrag) { // 旋转场景RotateScene(offset);}preMousePos = Input.mousePosition;}}private void ScaleScene(float scroll) { // 缩放场景cam.position += cam.forward * scroll;camChangedHandler?.Invoke();}private void MoveScene(Vector3 offset) { // 平移场景cam.position -= (cam.right * offset.x / 100 + cam.up * offset.y / 100);camChangedHandler?.Invoke();}private void RotateScene(Vector3 offset) { // 旋转场景Vector3 rotateCenter = GetRotateCenter(0);cam.RotateAround(rotateCenter, Vector3.up, offset.x / 3); // 水平拖拽分量cam.LookAt(rotateCenter);cam.RotateAround(rotateCenter, -cam.right, offset.y / 5); // 竖直拖拽分量camChangedHandler?.Invoke();}private Vector3 GetRotateCenter(float planeY) { // 获取旋转中心if (Mathf.Abs(cam.forward.y) < Vector3.kEpsilon || Mathf.Abs(cam.position.y) < float.Epsilon){return cam.position + cam.forward * (nearPlan + 1 / nearPlan);}float t = (planeY - cam.position.y) / cam.forward.y;float x = cam.position.x + t * cam.forward.x;float z = cam.position.z + t * cam.forward.z;return new Vector3(x, planeY, z);}
}

说明: SceneController 脚本组件挂在相机下,用于平移、旋转、缩放场景,其原理见→缩放、平移、旋转场景。

3 运行效果

点选效果如下:

框选效果如下:

【Unity3D】点选物体、框选物体、绘制外边框相关推荐

  1. Android TV Demo 工程,其中包含 TV 常用的自定义控件,飞框效果实现,外边框效果实现,UI 控件焦点自动处理,使 TV 开发更简单,更高效。

    TVLibraryDemo 项目地址:zhangtiansheng/TVLibraryDemo  简介:Android TV Demo 工程,其中包含 TV 常用的自定义控件,飞框效果实现,外边框效果 ...

  2. Unity功能开发之_(单选、多选、框选)

    using System; using UnityEngine; using System.Collections; using System.Collections.Generic; using S ...

  3. css画个框,用CSS绘制带有边框的尖端

    我正在尝试在CSS中画一个提示. 到目前为止,我取得了"中等成功",唯一的问题是,根据DIV宽度,尖端有时不在中心位置. 我想要的是: 到目前为止,我的代码: .logo { co ...

  4. CV战士的自我修养1—框选

    CV战士的自我修养1-框选 框选 CV与替换 End 作为一名合格的CV战士,最重要的就是基本功.直接CV谁不会,但重点是如何能高效的CV.这里使用的武器是 IDEA. 框选 双击 双击变量或者字符串 ...

  5. three.js第五十二用 较为精确的框选思路 像素拾取大法

    threejs交流群511163089 首先是像素拾取物体,这个先看看官方DEMO哈,每个颜色编码一个物体,像素的颜色对应物体. 框选怎么弄喃 第一步,筛选出renderlist 第二步,绘制,以ID ...

  6. 【UE4】UE4框选

    目录: 视窗操作篇 反转鼠标中键 自适应视窗缩放速度 解除对选中物体旋转 对屏幕外物体进行位移旋转缩放 物体吸附到模型顶点 屏幕编队(记录当前相机位置和旋转) 落到正下方物体 显示与隐藏 资源操作篇 ...

  7. 鼠标框选时不显示虚线_3DsMax在使用时必须要设置的内容

    Hello大家好,本节为读者朋友分享一些在3DsMax常规方面的功能项部分的使用与设置内容,以便更好的完成与优化软件上的操作,以至于在效率上有所提高. 1.3DsMax与Cad的鼠标匹配 相信很多读者 ...

  8. 基于Fixed定位的框选功能

    最近项目涉及到一个支持批量操作的小需求,交互上需要使用框选来触发.在查阅了一些资料后发现,网上的方案基本都是基于绝对定位布局的,此方案如果是针对全局(在body上)的框选,还是可用的.但是现实需求里几 ...

  9. C#制作QQ截图的自动框选功能的个人思路(三)自动框选

    效果图: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; ...

最新文章

  1. DataFrame类型数据的主要处理方法
  2. POJ 1651 Multiplication Puzzle 区间dp(水
  3. 新年到,献给从一线工程师到CTO的实用指南:《2019区块链开发者报告》
  4. Item03. 设计模式 Item04. STL
  5. QML中定义JavaScript资源
  6. BZOJ1026 [SCOI2009]windy数 数位dp
  7. html转换react native,React native HTML entities
  8. 【数据分析学习】线性降维方法
  9. workerman mysql_workerman/mysql
  10. 【clickhouse】Clickhouse 版本号历史
  11. macfee怎么生成释放代码_mcafee规则设置技巧
  12. 8、Map存储世界杯信息相关操作
  13. php 音频转换 WAV转MP3
  14. 德国制造和中国制造究竟不同在哪
  15. linux服务器的外网IP查阅方式
  16. Sublime text 设置快捷键让html文件在浏览器中打开
  17. html类型的网页文件,文件的ContentType类型-网页设计,HTML/CSS
  18. 七月算法课程《python爬虫》第三课: 爬虫基础知识与简易爬虫实现
  19. E:无法定位软件资源
  20. ubuntu16.04 设置静态ip

热门文章

  1. 正在连接 raw.githubusercontent.com (raw.githubusercontent.com)|::|:443... 失败:拒绝连接。
  2. arXiv每日推荐-5.5:语音/音频每日论文速递
  3. exsist什么意思_exist什么意思_通达信EXIST什么意思
  4. 权重掩码单网络多任务:Piggyback: Adapting a Single Network to Multiple Tasks by Learning to Mask Weights
  5. TCP-IP学习笔记-- 浅析TCP(1)
  6. 可信时间戳服务,如何确保电子数据法律效力?
  7. idea中maven依赖引入不进来解决方法
  8. winds10桌面彻底关闭系统更新
  9. jupyter内核无法连接,出现error,代码无法运行解决办法
  10. 华为手机[助手]备份的db通讯录文件如何恢复到其他手机