前言

    发现实习了以后自己就变懒了,每天回到宿舍只想打游戏,看剧。要是能有人家考研的一半劲头就好了。
    写完Everything的C#实现之后呢,我想写一些工作期间遇到的问题。一来当作笔记,二来加深学习。

UI

磁盘图标

    首先呢,我们先做一个可以显示各磁盘空间的控件。这里其实是一个个使用了GDI绘图后的Panel,我采用FlowLayoutPanel作为它的容器。容器的宽度作为参数之一,用它来决定Panel的大小,绘图的尺寸。绘制也很简单,先画一个绿色的大圆,根据比例,在其之上再画一个红色的扇形,然后再覆盖一个白色的小圆,三者同心,这样就是一个可以展示磁盘空间占用比例的圆环了。最后在中间写上文字即可,很简单吧!

    我还用GDI+Winform做过很多小游戏,例如贪吃蛇,五子棋,你画我猜等等,觉得很好玩。后来接触到WPF,一时间接受不了。它的动画效果怎么这么棒!写起来怎么怎么方便!再后来,见识到了眼花缭乱的前端技术,我便不再对UI设计抱有什么梦想了,还是后端比较单纯。

过滤器图标

    再说过滤器控件,Everything中的过滤器是一条菜单项,我将其可视化为了UI图形。它由一个PictureBox,一个CheckBox,以及两个Label组成。同样采用FlowLayoutPanel作为它的容器。与上面那个磁盘圆环一样,我将它们上面的事件,属性封装成了一个UI库文件。

    过滤器控件相对来说复杂一些,为了可以让用户自由选择图标,并且控件本身要记录代表的文件类型,它的三个组成部分还要表现的像一个整体一样。封装时,我给这个控件添加了四个public属性,分别为Name,FileNumber,IsCheck,以及图片的path, 并且完善了点击事件联动代码,图片显示,出错处理等代码。

查询

    让我们先来明确一下查询的条件是什么。

  • 需要支持按照文件名称模糊查询。
  • 需要支持按照文件类型进行查询,系统肯定不能定义出所有的文件类别,因此还需要让用户可以自定义查询类型。
  • 需要支持正则表达式查询。
  • 需要支持特定的目录下查询。

    文件名模糊查询很简单,我这里使用Contains函数来完成。文件类型查询怎么做到呢?我们通过观察Everything可以发现它是按照文件扩展名来做的。那我们就用一个List集合来保存多个扩展名,并用它代表某一类文件,做成文件过滤器吧!为了能够让用户的配置可以保存下来,我使用到了XML。并且在程序中定义了一个与其对应的Model,叫做Filter。

    <?xml version="1.0" encoding="UTF-8"?>
      <filters>
        <filter>
            <name>实验文档</name>
            <url>E:\图片\壁纸\《ARC X Windows 10 Theme》简约黑白风景壁纸_彼岸图网.jpg</url>
            <param>.DOC</param>
            <param>.DOCX</param>
        </filter>
    </filters>         
  
public class Filter
{
    public Filter(string name, List<string> param, string url)
    {
        this.name = name;
        this.param = new List<string>();
        foreach (string p in param)
        {
            if (p.StartsWith("."))
            {
                this.param.Add(p.ToUpper());
            }
            else
            {
                this.param.Add(string.Concat(".", p).ToUpper());
            }
        }        
        this.url = url;
    }

    private string name;
    public string Name { get=>name; set=>name=value; }

    private List<string> param;
    public List<string> Param {
        get =>param;
        set
        {
            foreach (string p in value)
            {
                if (p.StartsWith("."))
                {
                    this.param.Add(p.ToUpper());
                }
                else
                {
                    this.param.Add(string.Concat(".", p).ToUpper());
                }
            }
        }
    }
   
    private string url;
    public string Url { get=>url; set=>url=value; }
}

    上面介绍到的磁盘UI控件的构造函数接受传入一个DriveInfo对象,我封装好的GDI方法会完成绘图。

    过滤器控件则不同。我会将用户自定义的过滤器从xml中解析出来,保存至List中,过滤器控件依据Filter的个数而创建。其中,过滤器的Name就取自该Filter在List中的Index。这样,当我们进行查询时,可以遍历每一个过滤器控件,将被选中的(IsCheck=true)过滤器控件的名字转换成Index,找到List中对应的Filter,取出它们的List Param,拼接成一个List,作为查询条件之一。

    这样便实现了多种类组合查询的功能。为了能够让用户对过滤器进行必要的增删改查操作,我们需要书写一个包含各种方法的xml工具类。当然使用xpath就很简单。

  • 判断文件是否存在,不存在则创建一个

  • 存在则创建XmlDocument对象。取出根节点

      root = xmlfilter.SelectSingleNode("filters");
    
  •   public List<Filter> ReadXml()
      {            
          List<Filter> filters = new List<Filter>();            
          foreach (XmlNode item in root)
          {
              XmlElement xm = (XmlElement)item;
              string name = xm.SelectSingleNode("name").InnerText;
              string url = xm.SelectSingleNode("url").InnerText;
              List<string> param = new List<string>();
              foreach (XmlNode c in xm.SelectNodes("param"))
              {
                  param.Add(c.InnerText.ToString().ToLower());
              }
              Filter filter = new Filter(name, param, url);
              filters.Add(filter);
          }
          return filters;
      }
    
  • 添加子节点

       private void CreateNode(XmlDocument xmlDoc, XmlNode parentNode, string name, string value)
      {
          XmlNode node = xmlDoc.CreateNode(XmlNodeType.Element, name, null);
          node.InnerText = value;
          parentNode.AppendChild(node);
      }
    

    其他的功能就不再介绍了。

    再来看剩下的两个查询需求,正则查询和指定文件目录查询。这两个功能就很简单了。指定目录也就是文件路径以什么字符串开头 ;正则查询又不需要我们自己写正则,在界面上添加个CheckBox,让用户自己选择是正则查询,还是普通查询就好了,使用C#中的Regex类即可完成。

    因此,虽然我们的提供查询功能很丰富,实际上只需要写一个重载的查询函数即可——正则查询和普通查询。

     /// <summary>
    /// 查询文件
    /// </summary>
    /// <param name="startWith"></param>
    /// <param name="inputs"></param>
    /// <param name="filters"></param>
    /// <returns></returns>
    public IEnumerable<FileStruct> SearchMatchFileMethod(string startWith,string inputs, List<string> filters)
    {
        if (string.IsNullOrEmpty(inputs) && string.IsNullOrEmpty(startWith))
             return _fileSource.Files;

        if (filters.Contains(".*"))
           return _fileSource.Files.AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount).
                Where<FileStruct>(r => r.Path.StartsWith(startWith) && r.Name.Contains(inputs)) ;
        
        return _fileSource.Files.AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount).
            Where<FileStruct>(r => r.Path.StartsWith(startWith) && r.Name.Contains(inputs) && filters.Contains(r.ExpandedName));
                    
    }
   
    /// <summary>
    /// 根据正则表达式查询文件
    /// </summary>
    /// <param name="startWith"></param>
    /// <param name="regex"></param>
    /// <param name="filters"></param>
    /// <returns></returns>
    public IEnumerable<FileStruct> SearchMatchFileMethod(string startWith, Regex regex, List<string> filters)
    {
        if (filters.Contains(".*"))
            return _fileSource.Files.AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount).
                Where<FileStruct>(r => r.Path.StartsWith(startWith) && regex.IsMatch(r.Name));         

        return _fileSource.Files.AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount).
            Where<FileStruct>(r => r.Path.StartsWith(startWith) && regex.IsMatch(r.Name) && filters.Contains(r.ExpandedName));            
    }

数据可视化

    为了使用HighChart画饼图,Winform原生web控件不好用。安装CefSharp包,使用ChromiumWebBrowser。

    ChromiumWebBrowser CWebBrowser;
    private void AnalysisView_Load(object sender, EventArgs e)
    {
        CWebBrowser = new ChromiumWebBrowser(Environment.CurrentDirectory + "\\view.html");//
        this.panel1.Controls.Add(CWebBrowser);
        CWebBrowser.Dock = DockStyle.Fill;
        CWebBrowser.ContextMenu = new ContextMenu();
        CWebBrowser.FrameLoadEnd += new EventHandler<FrameLoadEndEventArgs>(FrameLoadEnd_Event);
    }

    执行js脚本也超级简单!
    CWebBrowser.EvaluateScriptAsync(script)即可!!!

            private void FrameLoadEnd_Event(object sender,FrameLoadEndEventArgs e)
    {           
        List<string> param = new List<string>();
        param.Add(".*");
        for (int i = 0; i < Program.fileSource.Drives.Length; i++)
        {
            DriveInfo d = Program.fileSource.Drives[i];
            int count = Program.fileParallelQuery.SearchMatchFileMethod(d.Name, "", param).Count();
            string script = "drivechange(" + i.ToString() + ",'" + d.Name.Trim('\\') + "'," + count.ToString() + ")";
            CWebBrowser.EvaluateScriptAsync(script);
        }

        for (int i = 0; i < Program.fileSource.ClassificNumber.Count; i++)
        {
            string name = i<Program.filterSource.SystemFilters.Count-1? 
                Program.filterSource.SystemFilters[i+1].Name//i+1是为了不算上 "全部"
                : Program.filterSource.UserFilters[i+1- Program.filterSource.SystemFilters.Count].Name;                
            int count;
            Program.fileSource.ClassificNumber.TryGetValue(i, out count);
            string script = "filterchange(" + (i - 1).ToString() + ",'" + name + "'," + count.ToString() + ")";
            CWebBrowser.EvaluateScriptAsync(script);
        }

        string recordPath = Environment.CurrentDirectory + "\\countrecord.txt";
        using (StreamReader sr = new StreamReader(recordPath))
        {
            string records = sr.ReadToEnd();
            string[] clauses = records.Split('\n');
            if (clauses.Length > 12)
            {
                clauses = clauses.Skip<string>(clauses.Length - 12).ToArray<string>();
            }
            for (int i = 0; i < clauses.Length - 1; i++)
            {
                string[] info = clauses[i].Split('|');
                string script = "countchange(" + i + ",'" + info[0] + "'," + info[1] + ")";
                CWebBrowser.EvaluateScriptAsync(script);
            }
        }
    }

Shell编程之鼠标右键

    想获得Windows资源管理器的鼠标右键功能,需要先了解Windows外壳名字空间以及Windows shell编程。去看这位大神的博客吧!

https://www.cnblogs.com/xumaojun/p/8547395.html

在这里插入图片描述
    其中最关的一点就是要获取到:所选中文件的父节点的 IShellFolder 接口和该文件的pidl。 ShellApi.GetPShellAndPIDL是我封装好的一个方法。

IntPtr[] pidls = new IntPtr[1];
IShellFolder IParent;
//获得父节点的 IShellFolder 接口 和该文件的pidl
ShellApi.GetPShellAndPIDL(iDeskTop, out IParent, out pidls[0], file);

//得到 IContextMenu 接口
IntPtr iContextMenuPtr = IntPtr.Zero;
iContextMenuPtr = IParent.GetUIObjectOf(IntPtr.Zero, (uint)pidls.Length,
            pidls, ref Guids.IID_IContextMenu, out iContextMenuPtr);
            IContextMenu iContextMenu = (IContextMenu)Marshal.GetObjectForIUnknown(iContextMenuPtr);

//提供一个弹出式菜单的句柄
IntPtr contextMenu = API.CreatePopupMenu();
iContextMenu.QueryContextMenu(contextMenu, 0,
            API.CMD_FIRST, API.CMD_LAST, CMF.NORMAL | CMF.EXPLORE);

//弹出菜单
uint cmd = API.TrackPopupMenuEx(contextMenu, TPM.RETURNCMD,
            MousePosition.X, MousePosition.Y, this.Handle, IntPtr.Zero);

//获取命令序号,执行菜单命令
if (cmd >= API.CMD_FIRST)
{
    CMINVOKECOMMANDINFOEX invoke = new CMINVOKECOMMANDINFOEX();
    invoke.cbSize = Marshal.SizeOf(typeof(CMINVOKECOMMANDINFOEX));
    invoke.lpVerb = (IntPtr)(cmd - 1);
    invoke.lpDirectory = string.Empty;
    invoke.fMask = 0;
    invoke.ptInvoke = new POINT(MousePosition.X, MousePosition.Y);
    invoke.nShow = 1;
    iContextMenu.InvokeCommand(ref invoke);
}

结语

    这个程序做起来挺累的。没人指导,啥都得自己摸索。挺简单的一个东西可能要捣鼓好几天。做完以后就觉得好简单,没学到什么东西一样,写博客的时候总觉得脑袋里没东西。其实里边任意一个模块都值得深入去了解一下的,而我比较懒,交完作业就放那了。
    而且从恣意妄为的代码风格应该可以看出,我是个野生程序员。

源码

https://pan.baidu.com/s/1KI0bvHp3YPtwmYvi9cRZMg

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐