一、写在前面

在工作中需要实现一个场景,有一个名单类的数据需要维护,这个维护工作需要有一个复核功能,为了方便复核时对名单变更情况有一个良好的掌握,需要做一个便跟前后名单的对比功能。

功能实现后效果如下图:

其中,修改前名单、修改后名单、前后名单对比三个部分都使用了封装后的ListView控件保存数据

二、步骤一:封装ListView

封装ListView主要是为了保证对“前后名单对比”部分数据的着色。虽然微软原生的ListView就支持了对数据项进行着色,但因为“前后名单对比”部分使用了分组功能,点击分组标题时,默认选中分组内的全部数据,这回导致分组内的数据颜色都变为黑色。为改变这一情况,我们需要创建一个继承ListView的类ListViewEnhanced,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;namespace NameListComparer
{class ListViewEnhanced : ListView{/// <summary>/// call SendMessage using hit test structures/// </summary>[DllImport("User32.dll")]static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref LVHITTESTINFO lParam);     #region Windows constants/// <summary>/// WndProc message for the left mouse button down/// </summary>const int WM_LBUTTONUP = 0x0201;/// <summary>/// offset for the first SendMessage for a ListView/// </summary>const int LVM_FIRST = 0x1000;/// <summary>/// ListView SendMessage to check for an item hit test/// </summary>const int LVM_HITTEST = (LVM_FIRST + 18);/// <summary>/// ListView SendMessage to check for a sub-item hit test/// </summary>const int LVM_SUBITEMHITTEST = (LVM_FIRST + 57);#endregion Windows constants/// <summary>/// see http://msdn.microsoft.com/en-us/library/bb774754%28v=VS.85%29.aspx/// </summary>[Flags]internal enum LVHITTESTFLAGS : uint{LVHT_NOWHERE = 0x00000001,LVHT_ONITEMICON = 0x00000002,LVHT_ONITEMLABEL = 0x00000004,LVHT_ONITEMSTATEICON = 0x00000008,LVHT_ONITEM = (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON),LVHT_ABOVE = 0x00000008,LVHT_BELOW = 0x00000010,LVHT_TORIGHT = 0x00000020,LVHT_TOLEFT = 0x00000040,// Vista/Win7+ onlyLVHT_EX_GROUP_HEADER = 0x10000000,LVHT_EX_GROUP_FOOTER = 0x20000000,LVHT_EX_GROUP_COLLAPSE = 0x40000000,LVHT_EX_GROUP_BACKGROUND = 0x80000000,LVHT_EX_GROUP_STATEICON = 0x01000000,LVHT_EX_GROUP_SUBSETLINK = 0x02000000,}/// <summary>/// see http://msdn.microsoft.com/en-us/library/bb774754%28v=VS.85%29.aspx/// </summary>[StructLayout(LayoutKind.Sequential)]struct LVHITTESTINFO{public POINT pt;public LVHITTESTFLAGS flags;public int iItem;public int iSubItem;// Vista/Win7+public int iGroup;}/// <summary>/// see http://msdn.microsoft.com/en-us/library/dd162805%28v=VS.85%29.aspx/// </summary>[StructLayout(LayoutKind.Sequential)]struct POINT{public POINT(int x, int y){this.x = x;this.y = y;}public int x;public int y;}/// <summary>/// convert the IntPtr LParam to an Point./// </summary>private static POINT LParamToPoint(IntPtr lparam){return new POINT(lparam.ToInt32() & 0xFFFF, lparam.ToInt32() >> 16);}protected override void WndProc(ref Message m){//the link uses WM_LBUTTONDOWN but I found that it doesn't workif (m.Msg == WM_LBUTTONUP){LVHITTESTINFO info = new LVHITTESTINFO();//The LParamToPOINT function I adapted to not bother with //  converting to System.Drawing.Point, rather I just made //  its return type the POINT structinfo.pt = LParamToPoint(m.LParam);//if the click is on the group header, exit, otherwise send messageif (SendMessage(this.Handle, LVM_SUBITEMHITTEST, -1, ref info) != -1)if ((info.flags & LVHITTESTFLAGS.LVHT_EX_GROUP_HEADER) != 0)return; //*}base.WndProc(ref m);}}
}

三、步骤二:建立存放单个名单的对象

存放单个名单的对象,可根据业务系统自身情况量身定制,下面代码是一个我实现的MemberInfo类,包含成员编码、成员名称、成员称号三个属性:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace NameListComparer
{public class MemberInfo{/// <summary>/// 成员信息/// </summary>/// <param name="memCode">成员编码</param>/// <param name="memName">成员名称</param>/// <param name="memTitle">成员称号</param>public MemberInfo(string memCode, string memName, string memTitle = ""){this.MemCode = memCode;this.MemName = memName;this.MemTitle = memTitle;}/// <summary>/// 编号/// </summary>private string _memCode;/// <summary>/// 编号/// </summary>public string MemCode{get{return _memCode;}set{_memCode = value;}}/// <summary>/// 编号/// </summary>private string _memName;/// <summary>/// 编号/// </summary>public string MemName{get{return _memName;}set{_memName = value;}}/// <summary>/// 称号/// </summary>private string _memTitle;/// <summary>/// 称号/// </summary>public string MemTitle{get{return _memTitle;}set{_memTitle = value;}}}
}

四、步骤三:创建自定义控件

创建一个继承UserControl的自定义控件MemberComparer,如下图所示:

三个ListView的View属性,都要设置成System.Windows.Forms.View.Details

MemberComparer控件的代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;namespace NameListComparer
{/// <summary>/// 自定义控件:用于比较修改前和修改后的名单/// </summary>public partial class MemberComparer : UserControl{/// <summary>/// 自定义控件:用于比较修改前和修改后的名单/// </summary>public MemberComparer(){InitializeComponent();}/// <summary>/// Load函数/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void MemberComparer_Load(object sender, EventArgs e){//创建最左侧僵尸列(必须)ColumnHeader chPreZombie = new ColumnHeader();chPreZombie.Name = "zombie";chPreZombie.Text = "";chPreZombie.Width = 0;chPreZombie.TextAlign = HorizontalAlignment.Center;lvwPreData.Columns.Add(chPreZombie);//成员编码ColumnHeader chPreMemCode = new ColumnHeader();chPreMemCode.Text = "成员编码";chPreMemCode.Width = 100;chPreMemCode.TextAlign = HorizontalAlignment.Center;lvwPreData.Columns.Add(chPreMemCode);//成员名称ColumnHeader chPreMemName = new ColumnHeader();chPreMemName.Text = "成员名称";chPreMemName.Width = 100;chPreMemName.TextAlign = HorizontalAlignment.Center;lvwPreData.Columns.Add(chPreMemName);ColumnHeader chPreMemTitle = new ColumnHeader();//成员称号chPreMemTitle.Text = "成员称号";chPreMemTitle.Width = 100;chPreMemTitle.TextAlign = HorizontalAlignment.Center;lvwPreData.Columns.Add(chPreMemTitle);//为ListView添加横向滚动条chPreMemTitle.Width = 110; //你没看错,这个功能就是这么写的//指定排序规则lvwPreData.ListViewItemSorter = new ListViewItemComparer(1); //僵尸列不允许拖动lvwPreData.ColumnWidthChanging += (obj, arg) =>{ColumnHeader header = lvwPreData.Columns[arg.ColumnIndex];if (header.Name == "zombie"){arg.Cancel = true;}arg.NewWidth = lvwPreData.Columns[arg.ColumnIndex].Width;};//创建最左侧僵尸列(必须)ColumnHeader chPostZombie = new ColumnHeader();chPostZombie.Name = "zombie";chPostZombie.Text = "";chPostZombie.Width = 0;chPostZombie.TextAlign = HorizontalAlignment.Center;lvwPostData.Columns.Add(chPostZombie);//成员编码ColumnHeader chPostMemCode = new ColumnHeader();chPostMemCode.Text = "成员编码";chPostMemCode.Width = 100;chPostMemCode.TextAlign = HorizontalAlignment.Center;lvwPostData.Columns.Add(chPostMemCode);//成员名称ColumnHeader chPostMemName = new ColumnHeader();chPostMemName.Text = "成员名称";chPostMemName.Width = 100;chPostMemName.TextAlign = HorizontalAlignment.Center;lvwPostData.Columns.Add(chPostMemName);ColumnHeader chPostMemTitle = new ColumnHeader();//成员称号chPostMemTitle.Text = "成员称号";chPostMemTitle.Width = 100;chPostMemTitle.TextAlign = HorizontalAlignment.Center;lvwPostData.Columns.Add(chPostMemTitle);//为ListView添加横向滚动条chPostMemTitle.Width = 110; //你没看错,这个功能就是这么写的//指定排序规则lvwPostData.ListViewItemSorter = new ListViewItemComparer(1);//僵尸列不允许拖动lvwPostData.ColumnWidthChanging += (obj, arg) =>{ColumnHeader header = lvwPostData.Columns[arg.ColumnIndex];if (header.Name == "zombie"){arg.Cancel = true;}arg.NewWidth = lvwPostData.Columns[arg.ColumnIndex].Width;};//创建最左侧僵尸列(必须)ColumnHeader chCmpZombie = new ColumnHeader();chCmpZombie.Name = "zombie";chCmpZombie.Text = "";chCmpZombie.Width = 0;chCmpZombie.TextAlign = HorizontalAlignment.Center;lvwCmpData.Columns.Add(chCmpZombie);//成员编码ColumnHeader chCmpMemCode = new ColumnHeader();chCmpMemCode.Text = "成员编码";chCmpMemCode.Width = 100;chCmpMemCode.TextAlign = HorizontalAlignment.Center;lvwCmpData.Columns.Add(chCmpMemCode);//成员名称ColumnHeader chCmpMemName = new ColumnHeader();chCmpMemName.Text = "成员名称";chCmpMemName.Width = 100;chCmpMemName.TextAlign = HorizontalAlignment.Center;lvwCmpData.Columns.Add(chCmpMemName);ColumnHeader chCmpMemTitle = new ColumnHeader();//成员称号chCmpMemTitle.Text = "成员称号";chCmpMemTitle.Width = 100;chCmpMemTitle.TextAlign = HorizontalAlignment.Center;lvwCmpData.Columns.Add(chCmpMemTitle);//为ListView添加横向滚动条chCmpMemTitle.Width = 110; //你没看错,这个功能就是这么写的//指定排序规则lvwCmpData.ListViewItemSorter = new ListViewItemComparer(1);//僵尸列不允许拖动lvwCmpData.ColumnWidthChanging += (obj, arg) =>{ColumnHeader header = lvwCmpData.Columns[arg.ColumnIndex];if (header.Name == "zombie"){arg.Cancel = true;}arg.NewWidth = lvwCmpData.Columns[arg.ColumnIndex].Width;};}/// <summary>/// ListView比较规则/// </summary>class ListViewItemComparer : IComparer{/// <summary>/// 按第几列进行比较(首列为第0列)/// </summary>private int col;/// <summary>/// ListView比较规则,默认以第0列比较/// </summary>public ListViewItemComparer(){col = 0;}/// <summary>/// ListView比较规则,指定以第几列比较/// </summary>/// <param name="column"></param>public ListViewItemComparer(int column){col = column;}/// <summary>/// 比较函数/// </summary>/// <param name="x"></param>/// <param name="y"></param>/// <returns></returns>public int Compare(object x, object y){return String.Compare(((ListViewItem)x).SubItems[col].Text, ((ListViewItem)y).SubItems[col].Text);}}/// <summary>/// 清空所有数据/// </summary>public void EmptyAllData(){lvwPreData.Items.Clear();lvwPostData.Items.Clear();lvwCmpData.Items.Clear();lvwCmpData.Groups.Clear();}/// <summary>/// 初始化名单数据/// </summary>/// <param name="preMembers"></param>/// <param name="postMembers"></param>public void SetMemberData(MemberInfo[] preMembers, MemberInfo[] postMembers){EmptyAllData();//数据更新,UI暂时挂起this.lvwPreData.BeginUpdate();for (int i = 0; i < preMembers.Length; i++)   //添加10行数据 {if (preMembers[i] != null){ListViewItem lvi = new ListViewItem();lvi.SubItems.Add(preMembers[i].MemCode);lvi.SubItems.Add(preMembers[i].MemName);lvi.SubItems.Add(preMembers[i].MemTitle);this.lvwPreData.Items.Add(lvi);}}//结束数据处理,UI界面一次性绘制this.lvwPreData.EndUpdate();//数据更新,UI暂时挂起this.lvwPostData.BeginUpdate();for (int i = 0; i < postMembers.Length; i++)   //添加10行数据 {if (postMembers[i] != null){ListViewItem lvi = new ListViewItem();lvi.SubItems.Add(postMembers[i].MemCode);lvi.SubItems.Add(postMembers[i].MemName);lvi.SubItems.Add(postMembers[i].MemTitle);this.lvwPostData.Items.Add(lvi);}}//结束数据处理,UI界面一次性绘制this.lvwPostData.EndUpdate();//数据更新,UI暂时挂起this.lvwCmpData.BeginUpdate();//添加分组ListViewGroup lvgAdd = new ListViewGroup();lvgAdd.Header = "新加入成员";lvgAdd.Name = "add";lvgAdd.HeaderAlignment = HorizontalAlignment.Left;ListViewGroup lvgDelete = new ListViewGroup();lvgDelete.Header = "已删除成员";lvgDelete.Name = "delete";lvgDelete.HeaderAlignment = HorizontalAlignment.Left; ListViewGroup lvgNoChange = new ListViewGroup();lvgNoChange.Header = "未变动成员";lvgNoChange.Name = "nochange";lvgNoChange.HeaderAlignment = HorizontalAlignment.Left;lvwCmpData.Groups.Add(lvgAdd);lvwCmpData.Groups.Add(lvgDelete);lvwCmpData.Groups.Add(lvgNoChange);lvwCmpData.ShowGroups = true; //显示分组//新增加成员IEnumerable<MemberInfo> memberAdd =from member1 in postMemberswhere !(from member2 in preMemberswhere member1.MemCode == member2.MemCodeselect member2).Any()select member1;foreach (MemberInfo member in memberAdd){ListViewItem lvi = new ListViewItem();lvi.ForeColor = Color.Blue;lvi.SubItems.Add(member.MemCode);lvi.SubItems.Add(member.MemName);lvi.SubItems.Add(member.MemTitle);lvgAdd.Items.Add(lvi);this.lvwCmpData.Items.Add(lvi);}//已删除成员IEnumerable<MemberInfo> memberDelete =from member1 in preMemberswhere !(from member2 in postMembers where member1.MemCode == member2.MemCode select member2).Any()select member1;foreach (MemberInfo member in memberDelete){ListViewItem lvi = new ListViewItem();lvi.ForeColor = Color.Red;lvi.SubItems.Add(member.MemCode);lvi.SubItems.Add(member.MemName);lvi.SubItems.Add(member.MemTitle);lvgDelete.Items.Add(lvi);this.lvwCmpData.Items.Add(lvi);}//未变动成员IEnumerable<MemberInfo> memberNoChange =from member1 in preMembersfrom member2 in postMemberswhere member1.MemCode == member2.MemCodeselect member1;foreach (MemberInfo member in memberNoChange){ListViewItem lvi = new ListViewItem();lvi.ForeColor = Color.Black;lvi.SubItems.Add(member.MemCode);lvi.SubItems.Add(member.MemName);lvi.SubItems.Add(member.MemTitle);lvgNoChange.Items.Add(lvi);this.lvwCmpData.Items.Add(lvi);}//结束数据处理,UI界面一次性绘制this.lvwCmpData.EndUpdate();}}
}

本段代码有如下几点需要注意(可以理解为ListView控件的几个坑)

1、ListView最左侧的列,是无法设置对齐规则的,因此我将它设置成一个长度为0的列(即上面代码中的“僵尸列”),这一列的长度在ColumnWidthChanging事件中被指定为不能通过鼠标拉伸。对于用户而言,可以认为这一列是没有的。

2、将ListView的Scrollable设置成true后,可以做到列被拉伸超过ListView时会下方会自动出现滚动条,但如果窗口被打开时列的总长度就已经超出ListView的显示范围,滚动条则不会默认出现,而是需要拉动一下列才能出现。因此在代码中,需要找一列做一下调整,就像下面这行代码做的这样,这行代码看上去没有什么意义,却是为了规避ListView的一个缺陷。

//为ListView添加横向滚动条
chCmpMemTitle.Width = 110;

3、对ListView内数据进行分组后,单击分组标题会全选该组下数据,如果对该组下数据进行了着色,则着色会消失,这个问题需要重写ListView的WndProc方法,在第二节已有描述。

4、使用ListView内的BeginUpdate和EndUpdate函数可有效避免因不断刷新控件导致显示器上内容的闪动。

5、ListView的排序规则需自己指定,需实现一个继承自System.Collections.IComparer的类,放到ListView的ListViewItemSorter属性下。

五、步骤四:调用控件

在FormMain中添加一个Dock为Fill的控件MemberComparer,实现FormMain的Load函数,代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace NameListComparer
{public partial class FormMain : Form{public FormMain(){InitializeComponent();}private void FormMain_Load(object sender, EventArgs e){MemberInfo[] preMemberInfo = new MemberInfo[] {new MemberInfo("10001", "王伦", "白衣秀士"),new MemberInfo("10005", "吴用", "智多星"),new MemberInfo("10003", "宋江", "及时雨"),new MemberInfo("10004", "卢俊义", "玉麒麟"),new MemberInfo("10002", "晁盖", "托塔天王"),new MemberInfo("10007", "花荣", "小李广")};MemberInfo[] postMemberInfo = new MemberInfo[] {new MemberInfo("10003", "宋江", "及时雨"),new MemberInfo("10004", "卢俊义", "玉麒麟"),new MemberInfo("10008", "张顺", "浪里白条"),new MemberInfo("10009", "周通", "小霸王"),new MemberInfo("10010", "时迁", "鼓上蚤"),new MemberInfo("10005", "吴用", "智多星"),new MemberInfo("10006", "林冲", "豹子头"),new MemberInfo("10007", "花荣", "小李广")};cmpMembers.SetMemberData(preMemberInfo, postMemberInfo);}}
}

这段代码运行的效果,和本文一开始的那张图片是一样的。

六、参考资料

1、ListView的使用方法,我参考了这篇博客

http://blog.csdn.net/xiaohan2826/article/details/8603015

2、禁用ListView下分组标题栏的单击全选功能,我参考了下面的资料

http://stackoverflow.com/questions/10532039/listview-group-header-click-disable-select-all-in-windows-7

3、另一个资料是为分组标题栏添加单击事件的,资料2的答题者就参考了它

https://social.msdn.microsoft.com/Forums/windows/en-US/eccdf58a-dd06-4ae3-908d-5f5863c01d64/listviewgroupclicked-event?forum=winforms

七、附言

1、我个人感觉.NET中的ListView控件并不好驾驭,不是因为使用它的规则有多难,而是因为这个控件的坑比较多

2、本文中程序的一个DEMO可以在这个地址下载到:http://pan.baidu.com/s/1qWRVrac

3、我的Windows版本为Win7旗舰版,VS版本为2012,编译目标框架为.NET Framework 4

END

转载于:https://my.oschina.net/Tsybius2014/blog/610051

C# - 使用自定义控件实现名单修改的比较功能相关推荐

  1. 联想笔记本G50-80 bios白名单修改

    联想笔记本G50-80 bios白名单修改. 缘由:更换网卡为AX200,遇到bios白名单的限制,需要修改bios程序. bios型号:B0CNA0WW 软件工具:UEFITOOL. UltraEd ...

  2. android 状态栏一体化 fragment,单Activity多Fragment动态修改状态栏颜色功能

    目录介绍 1.关于如何集成 2.关于如何使用 3.关于鸣谢 4.关于版本更新说明 5.关于其他介绍 0.说明 状态栏工具类,应该可以满足绝大多数的使用场景.具体可以参考代码案例,欢迎star!! 1. ...

  3. macos修改WIFI共享功能的默认网段

    macos用有线网络开启网络共享.电脑终端无法连接到内网IP上的服务. 需要修改WIFI共享功能的默认网段. 转载自:http://www.voidcn.com/article/p-qjalcvsp- ...

  4. CRM客户关系管理系统开发第十三讲——实现联系人管理模块中修改联系人的功能

    修改联系人列表页面上的链接地址 编写LinkManAction的edit方法 首先,我们要在LinkManAction类中编写一个跳转到联系人编辑页面的方法.在该方法中,我们不仅要查询出某个联系人,而 ...

  5. 公众平台新增修改文章错别字功能 每篇文章允许被修改一次仅限正文内五个字...

    微信团队又深夜放大招了:公众平台新增修改文章错别字功能,支持运营者对已群发文章进行小范围修改.每篇文章允许被修改一次,修改范围仅限正文内五个字.这样确实给运营者.读者提供更友好的编辑.阅读体验了.网友 ...

  6. 为用户信息修改添加审核功能

    最近做了一个功能为用户信息修改添加审核功能,审核通过以后修改才会生效.一般的做法是修改的时候添加一条记录,审核通过以后再把修改记录更新到原始的记录中. 尝试了一种新的方式,用一张表记录更新的字段和值, ...

  7. vue.js实现用户评论、登录、注册、及修改用户部分信息功能代码。

    github效果在线预览 仓库地址: https://github.com/zhongyoucong/vuejs/ vue.js实现用户评论.登录.注册.及修改用户部分信息功能代码.效果图如下: 登入 ...

  8. Spring5框架基础详解(五)(JdbcTemplate概念和准备、jdbcTemplate操作数据库添加功能、修改和删除功能、查询功能)

    文章目录 一.JdbcTemplate是什么和准备工作 1.1.引入相关maven依赖 1.2在spring配置文件配置数据库连接 1.3配置JdbcTemplate对象,注入DateSource 1 ...

  9. Flask博客开发实战-用户中心实现修改个人信息功能

    用户中心实现修改个人信息功能 该功能的实现在做用户管理的时候其实我们已经写过了,这里其实仅仅只需要做的是获取到当前用户,允许用户登录后修改自己用户信息就可以了! 补充User的模型数据 gexing ...

最新文章

  1. C语言实现高斯-赛德尔迭代gauss seidel(附完整源码)
  2. 这些焊接不良,你遇见过吗?
  3. JavaFX的科幻用户界面第1部分
  4. html弹窗_对付流氓广告弹窗:彻底告别,这一招最有效
  5. linux内核软中断引起大量丢包
  6. 如何查看页面是否开启gzip压缩
  7. 用python画分段函数图像_我想用Python matplotlib 画一个这样类似的图像,需要用到分段函数。大佬帮帮这个小弟?...
  8. sockscap on linux: wsocks
  9. 计算机专业学生毕业实习周记
  10. Java Instrument(一) Java Agent
  11. 目标检测基础:好文推荐
  12. PRi——自行车码表
  13. 【SHOI2008】【BZOJ1023】cactus仙人掌图
  14. 原创电子书《菜鸟程序员成长之路:从技术小白到阿里巴巴Java工程师》
  15. Bootstrap DataTable自定义表格 设置某列不排序
  16. 外文文献下载网站;数据获取网站;中文文献下载网站;论文原创性保真网站;外包项目申请网站;大数据比赛收录网站;提高编程能力;代码分享网站
  17. 获取cookies(pyppeteer)
  18. JY02调试-无刷电机驱动芯片
  19. SWIG和MapGuide Web API
  20. 10.Atomic-原子性操作

热门文章

  1. 一图看尽全生态, 2018区块链产业云图重磅发布!
  2. 初学树莓派——(九)DHT11数据传入ONENET
  3. 3D转换之三维坐标系,透视旋转等基础知识
  4. 类型转换异常:java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.
  5. 基于Echarts实现可视化数据大屏翼兴消防监控(1页)
  6. 计算机主机平时怎么保养,电脑如何保养和维护?电脑日常保养和维护技巧
  7. 百度地图获取河流_华为AR地图: 导航功能确实不错,还有更多AR新场景也值得关注...
  8. 使用UIImagePickerController实现 iOS录像拍照
  9. oracle 查看数据泵,1.Oracle数据泵介绍
  10. Python实现工单的内容在数据库中的录入和修改的可视化界面操作