本来要做一个从服务器中读取数据,校验数据有没有被修改过。然后我自己在本地模拟了下,读取本地的文件夹,其中文件夹里面有可能有文件夹和文件。
一、最后结果图:
经过带我的老大的一点点的指教,最后是酱紫的:
主窗体FormDataCheck有一个PanelControl、两个按钮(一个校验一个确定)、一个进度条和一个labelControl,如下图01:
图01 主窗体
PanelControl里面准备放个用户自定义控件,于是新建了个用户控件UCCheck,用户控件里面放了个GridControl控件,如下图02:
图02 用户控件
点击查看按钮后,会弹出一个窗体FormDataBag,查看文件夹(数据包)里面的详细信息,这个窗体里面我放了一个TreeList,如下图03所示:
图03 查看数据包的详细信息
二、详细设计过程
(1)主窗体FormDataCheck加载时,PanelControl里面的UCControl里面的GridControl先自动加载出指定路径下的所有的文件夹,代码如下:
using Geoway.ADF.GIS.TreeListMD5.Public; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace Geoway.ADF.GIS.TreeListMD5 { public partial class FormDataCheck : Form { private UCCheck _ucCheck=null; public FormDataCheck() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { _ucCheck = new UCCheck(); _ucCheck.Dock = DockStyle.Fill; panelControl1.Controls.Add(_ucCheck); //panelControl1里面添加自定义好的控件 this._ucCheck.TestEvent += Test_UCCheckEvent; //注册事件,直接 += 后就行 } private void btn_Check_Click(object sender, EventArgs e) { _ucCheck.Check(); this.progressBarControl1.Properties.Maximum = _ucCheck._maxcount; } //实现注册事件中所触发的方法,注意方法的参数需要和 EventHandler 方法一致。参数sender可以是任何基本类型,e可以是方法 void Test_UCCheckEvent(object sender, EventArgs e) { this.progressBarControl1.Position = (int)sender; this.labelControl1.Text = this.progressBarControl1.Position + "/" + this.progressBarControl1.Properties.Maximum; if (this.progressBarControl1.Position == this.progressBarControl1.Properties.Maximum) { Thread.Sleep(1000); this.labelControl1.Text = "校验完成"; } } //点击确定按钮 private void btn_Sure_Click(object sender, EventArgs e) { this.Close(); } } }
(2)UCControl用户自定义控件,代码如下:
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 Geoway.ADF.GIS.TreeListMD5.Public; using System.IO; using System.Collections.Concurrent; using System.Threading; namespace Geoway.ADF.GIS.TreeListMD5 { public partial class UCCheck : UserControl { private delegate void DelegateUpdateCheckState(DataRow row, EnumCheckType checkType); //声明一个委托 public event EventHandler TestEvent; //声明一个委托事件,最好直接继承EventHandler,不用写那么多的事件 //设置GridControl属性 private const string COL_Edit = "Edit"; private const string COL_DataName = "DataName"; private const string COL_DataPath = "DataPath"; private const string COL_CheckStatus = "CheckStatus"; private const string COL_Object = "_Object"; public string _path = "E:\\test123"; public int _maxcount = 0; //设置进度条的最大值 DataTable _dt = new DataTable(); FormDataBag _dataForm =new FormDataBag(); public UCCheck() { InitializeComponent(); _dt.Columns.Add(COL_DataName, System.Type.GetType("System.String")); _dt.Columns[0].Caption = "数据包名称"; _dt.Columns.Add(COL_DataPath, System.Type.GetType("System.String")); _dt.Columns[1].Caption = "路径"; _dt.Columns.Add(COL_CheckStatus, System.Type.GetType("System.String")); _dt.Columns[2].Caption = "校验状态"; _dt.Columns.Add(COL_Edit, System.Type.GetType("System.String")); _dt.Columns[3].Caption = "查看"; _dt.Columns.Add(COL_Object, typeof(DirClass)); InitFolders(_path); this.DataGridControl.DataSource = _dt; this.DataGridView.PopulateColumns(); this.DataGridView.Columns[COL_Edit].ColumnEdit = repositoryItemHyperLinkEdit1; this.DataGridView.Columns[COL_Object].Visible = false; } //校验 public void Check() { _maxcount = _dt.Rows.Count; int progressBar = 0; var blockingCollection = new BlockingCollection<DataRow>(); //新建一个生产者线程 var producer = Task.Factory.StartNew(() => { foreach (DataRow row in _dt.Rows) { blockingCollection.Add(row); } blockingCollection.CompleteAdding(); //CompleteAdding()方法会通知GetConsumingEnumerable的迭代器不用再等了,后面不会再有元素被加进来了。 }); //新建四个线程 for (int j = 0; j < 4; j++) { var consumer = Task.Factory.StartNew(() => { foreach (DataRow row in blockingCollection.GetConsumingEnumerable()) { DirClass dir = row[COL_Object] as DirClass; dir.Check(); if (DataGridControl.InvokeRequired) { DataGridControl.BeginInvoke(new DelegateUpdateCheckState(UpdateCheckState), row, dir.CheckType); //对于控件的调用总是由主线程来执行的 } //把进度条的 progressBar 值传给 FormDataCheck()窗体 Interlocked.Increment(ref progressBar); if (this.TestEvent != null) { this.TestEvent(progressBar ,new EventArgs()); //委托事件传值 } Thread.Sleep(1 * 2000); } }); } } //委托方法 private void UpdateCheckState(DataRow row, EnumCheckType checkType) { row[COL_CheckStatus] = GetEnumDescription.GetDescription(checkType); } //遍历指定路径下的文件夹和文件 private void InitFolders(string path) { try { List<DirClass> dirs = new List<DirClass>(); InitDirClass(path, dirs); foreach (DirClass s in dirs) { InitFiles(s); //初始化表格 } } catch { } } //右侧表格的初始化事件 private void InitFiles(DirClass dir) { DataRow dr = _dt.NewRow(); dr[COL_DataName] = dir.DirName; dr[COL_DataPath] = dir.DirPath; dr[COL_CheckStatus] = GetEnumDescription.GetDescription(dir.CheckType); dr[COL_Edit] = ""; dr[COL_Object] = dir; _dt.Rows.Add(dr); } //这里注意递归调用 public void InitDirClass(string folder, List<DirClass> dirs) { foreach (string dirString in Directory.GetDirectories(folder)) { DirClass dirClass = new DirClass(); dirClass.DirName = Path.GetFileName(dirString); dirClass.DirPath = dirString; foreach (var file in Directory.GetFiles(dirString)) { FileInfo fi = new FileInfo(file); dirClass.FileClass.Add(new FileClass() { FileName = Path.GetFileName(file), FileSize = System.Math.Ceiling(fi.Length / 1024.0).ToString() + " KB", FilePath = file, FileMD5 = GetFileMD5Class.GetFileMD5(file) }); } List<DirClass> dirs_2 = new List<DirClass>(); InitDirClass(dirString, dirs_2); //递归调用 dirClass.dirClass = dirs_2; dirs.Add(dirClass); } } //点击“查看”按钮 private void repositoryItemHyperLinkEdit1_Click(object sender, EventArgs e) { DirClass dir = DataGridView.GetFocusedDataRow()[COL_Object] as DirClass; _dataForm.Text = dir.DirName + "数据包"; _dataForm.LoadFile(dir); if (_dataForm.ShowDialog() == System.Windows.Forms.DialogResult.OK) { } } } }
(3)点击查看按钮之后的FormDataBag窗体,详细代码如下:
using DevExpress.XtraTreeList.Nodes; using Geoway.ADF.GIS.TreeListMD5.Public; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace Geoway.ADF.GIS.TreeListMD5 { public partial class FormDataBag : Form { public FormDataBag() { InitializeComponent(); } public void LoadFile(DirClass dirClass) { treeList1.ClearNodes(); TreeListNode pNode = treeList1.AppendNode(new object[] { dirClass.DirName, "", "根结点", GetEnumDescription.GetDescription(dirClass.CheckType), dirClass.DirPath }, null); //给treeList添加节点 pNode.HasChildren = true; pNode.Tag = true; pNode.ExpandAll(); InitFolders(dirClass.dirClass,pNode); InitFiles(dirClass.FileClass, pNode); } //读取文件夹 private void InitFolders(List<DirClass> dirclass , TreeListNode pNode) { foreach(DirClass dir in dirclass) { TreeListNode node = treeList1.AppendNode(new object[] { dir.DirName, "", "文件夹", GetEnumDescription.GetDescription(dir.CheckType), dir.DirPath },pNode); node.HasChildren = true; node.ExpandAll(); node.Tag = true; InitFolders(dir.dirClass,node); //递归调用 InitFiles(dir.FileClass, node); } } //读取文件 private void InitFiles(List<FileClass> fileClass , TreeListNode pNode) { foreach(FileClass fileclass in fileClass) { TreeListNode node=treeList1.AppendNode(new object[] { fileclass.FileName, fileclass.FileSize, "文件", GetEnumDescription.GetDescription(fileclass.CheckStatus), fileclass.FilePath }, pNode); node.HasChildren = false; node.Tag = true; } } //点击确定按钮关闭 private void btn_Sure_Click(object sender, EventArgs e) { this.Close(); } } }
(4)由于是面向对象编程,故需先新建几个类,实例化几个对象。其实可以把UCControl里面的grodControl每一列当做一个属性,这样就可以建造出一个新的类。其实我总结面向对象编程有两个要点:一是首先找到事物的共性,看界面需要哪些属性和操作(行为),抽象出属性和方法,建立一个类。注意类里面也可以写构造方法和其他方法,注意多思考;二是在其他地方要使用的时候,先构建对象,将数据读入进对象,存进对象中,再根据对象初始化界面,看界面需要对象的哪些属性。
1.DirClass类
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Geoway.ADF.GIS.TreeListMD5.Public { public class DirClass { //数据包名称 public string DirName { get; set; } //数据包路径 public string DirPath { get; set; } //List<FileClass> 对象---对应文件 public List<FileClass> FileClass { get; set; } //List<DirClass>对象---对应文件夹 public List<DirClass> dirClass { get; set; } //校验状态--枚举类型 public EnumCheckType CheckType { get; set; } //构造方法 public DirClass() { CheckType = EnumCheckType.UnCheck; FileClass = new List<FileClass>(); } //判断校验状态 public bool Check() { CheckType = EnumCheckType.Completed; //获得枚举类型的描述 foreach (var item in FileClass) { item.Check(); if (item.CheckStatus == EnumCheckType.Modified || item.CheckStatus == EnumCheckType.Deleted) { CheckType = EnumCheckType.Modified; } } foreach(var dirItem in dirClass) { dirItem.Check(); //递归调用 if (dirItem.CheckType == EnumCheckType.Modified) { CheckType = EnumCheckType.Modified ; } } return true; } } public enum EnumCheckType { [Description("未校验(枚举)")] UnCheck = 0, [Description("完整(枚举)")] Completed, [Description("已删除(枚举)")] Deleted, [Description("修改(枚举)")] Modified } }
2.FileClass类
using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace Geoway.ADF.GIS.TreeListMD5.Public { public class FileClass { //文件名称 public string FileName { get; set; } //文件路径 public string FilePath { get; set; } //文件大小 public string FileSize { get; set; } //文件MD5值 public string FileMD5 { get; set; } //校验状态--枚举类型 public EnumCheckType CheckStatus { get; set; } public FileClass() { CheckStatus = EnumCheckType.UnCheck; } public bool Check() { string md5 = GetFileMD5Class.GetFileMD5(FilePath); if (md5 == null) { this.CheckStatus = EnumCheckType.Deleted; } else if (this.FileMD5 == md5) { this.CheckStatus =EnumCheckType.Completed; } else { this.CheckStatus =EnumCheckType.Modified; } return true; } } }
3.获得枚举的描述类GetEnumDescription
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace Geoway.ADF.GIS.TreeListMD5.Public { public static class GetEnumDescription { public static string GetDescription(this Enum value) //参数中加 this 关键字,给枚举 Enum 注册事件(注意以后都可以这样做,要给某个类型注册事件时) { Type type = value.GetType(); string name = Enum.GetName(type, value); FieldInfo field = type.GetField(name); DescriptionAttribute attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute; return attribute.Description; } } }
4.获得文件的MD5值:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace Geoway.ADF.GIS.TreeListMD5.Public { public static class GetFileMD5Class { // 根据传入的文件路径获取到 文件的MD5值 public static string GetFileMD5(string path) { if (!File.Exists(path)) { return null; } else { MD5 md5 = MD5.Create(); FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); byte[] hash = md5.ComputeHash(fs); fs.Close(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < hash.Length; i++) { sb.Append(hash[i].ToString("x2")); } return sb.ToString(); } } } }
三,总结
(1)面向对象编程有两个要点:一是首先找到事物的共性,看界面需要哪些属性和操作(行为),抽象出属性和方法,建立一个类。注意类里面也可以写构造方法和其他方法,注意多思考;二是在其他地方要使用的时候,先构建对象,将数据读入进对象,存进对象中,再根据对象初始化界面,看界面需要对象的哪些属性。 面向对象、面向对象、面向对象、面向对象、面向对象
(2)多线程编程。我是点击“校验”按钮时新建了几个线程。根据生产者、消费者模式进行设计的。首先将数据读入进一个线程安全集合BlockingCollection<T>中,读完之后加上 blockingCollection.CompleteAdding() 其中CompleteAdding()方法会通知GetConsumingEnumerable的迭代器不用再等了,后面不会再有元素被加进来了。然后最后就停止读集合里面的数据了。这里新建线程用的是Task.Factory.StartNew(()=>{ }),用的是Task(),不是线程Thread,也没用线程池。
var blockingCollection = new BlockingCollection<DataRow>(); //新建一个生产者线程 var producer = Task.Factory.StartNew(() => { foreach (DataRow row in _dt.Rows) { blockingCollection.Add(row); } blockingCollection.CompleteAdding(); //CompleteAdding()方法会通知GetConsumingEnumerable的迭代器不用再等了,后面不会再有元素被加进来了。 }); //新建四个线程 for (int j = 0; j < 4; j++) { var consumer = Task.Factory.StartNew(() => { foreach (DataRow row in blockingCollection.GetConsumingEnumerable()) { DirClass dir = row[COL_Object] as DirClass; dir.Check(); if (DataGridControl.InvokeRequired) { DataGridControl.BeginInvoke(new DelegateUpdateCheckState(UpdateCheckState), row, dir.CheckType); //对于控件的调用总是由主线程来执行的 } //把进度条的 progressBar 值传给 FormDataCheck()窗体 Interlocked.Increment(ref progressBar); if (this.TestEvent != null) { this.TestEvent(progressBar ,new EventArgs()); //委托事件传值 } Thread.Sleep(1 * 2000); } }); }
(3)委托。其中这里面UCCheck里面的dataGridControl是主线程执行的,所以如果放到新建的线程里面执行会发生 跨线程 的错误,所以需要设置一个委托
private delegate void DelegateUpdateCheckState(DataRow row, EnumCheckType checkType); //声明一个委托
在新建的线程里面写:
if (DataGridControl.InvokeRequired) { DataGridControl.BeginInvoke(new DelegateUpdateCheckState(UpdateCheckState), row, dir.CheckType); //对于控件的调用总是由主线程来执行的 }
新建委托方法:
private void UpdateCheckState(DataRow row, EnumCheckType checkType) { row[COL_CheckStatus] = GetEnumDescription.GetDescription(checkType); }
(4)事件。老大跟我说:主控件通过子控件的实例来调用子控件,子控件通过事件来给主控件传递消息,订阅了该事件的主控件就都响应子控件传递的消息并作出相应的反应。有人可能会问,为啥不能子控件也通过主控件的实例来调用主控件,这样的话,那这个主控件和这个子控件就绑定死了,所以这样不好。要实现子控件的封装和复用性,就是子控件也可以被其他的主控件调用,但是子控件怎么告知其他的所有调用我的主控件自己的变化呢,那就是用事件,所有订阅了我的事件的主控件都可以随着变化。这里用控件告知主窗体,我在检验每个文件夹,主窗体知道后进度条在走。 在UCControl里面先注册事件:
public event EventHandler TestEvent; //声明一个委托事件,最好直接继承EventHandler,不用写那么多的事件写触发事件的方法:
//把进度条的 progressBar 值传给 FormDataCheck()窗体 Interlocked.Increment(ref progressBar); if (this.TestEvent != null) { this.TestEvent(progressBar ,new EventArgs()); //委托事件传值 }
然后在FormDataCheck里面先订阅事件
this._ucCheck.TestEvent += Test_UCCheckEvent; //注册事件,直接 += 后就行在实现事件所触发的方法:
//实现注册事件中所触发的方法,注意方法的参数需要和 EventHandler 方法一致。参数sender可以是任何基本类型,e可以是方法 void Test_UCCheckEvent(object sender, EventArgs e) { this.progressBarControl1.Position = (int)sender; this.labelControl1.Text = this.progressBarControl1.Position + "/" + this.progressBarControl1.Properties.Maximum; if (this.progressBarControl1.Position == this.progressBarControl1.Properties.Maximum) { Thread.Sleep(1000); this.labelControl1.Text = "校验完成"; } }
OK,先写到这里,以后再补充。