• 查杀进程小工具——WPF和Prism初体验


    最近因为工作需要,研究了一下桌面应用程序。在winform、WPF、Electron等几种技术里,最终选择了WPF作为最后的选型。WPF最吸引我的地方,就是MVVM模式了。MVVM模式完全把界面和业务剥离开来,页面所有操作都通过数据来驱动。更替页面不用修改业务代码逻辑。

    以一个查杀进程的小工具来作为初次学习的成果总结。日常开发Java Web程序的时候,进程遇到端口占用问题,通过命令查找端口、查找进程、杀死进程,这一套命令敲下来过于麻烦。于是就写了这么一个小Demo,即作为学习使用,也为以后工作降低工作量。

    项目代码

    https://github.com/zer0Black/WinPidKiller

    需求设计

    1. 进程列表:展示所有经常的列表,按照应用名称正序排序。列表展示进程名、PID、协议、本机IP:端口、远程IP:端口、进程路径
    2. 搜索框:进行端口搜索,在经常列表中展示搜索结果
    3. 刷新按钮:刷新进程列表
    4. 杀死按钮:选中进程,进行进程的杀死。杀死进程后刷新进程列表

    关键要点

    1. DataContext
      DataContext主要作用是用于绑定数据源,默认值为null。
      DataContext是FrameworkElement中的一个属性。而绝大部分的UI组件都继承路径中都有FrameworkElement类,所以我们可以认为,大部分UI组件都有DataContext属性。并且设置了某个对象的DataContext,那么会对这个对象的所有子对象都会产生同样的影响。
      所以一般来说,我们都会在顶级对象(Window对象)中去设置DataContext属性。

    2. 使用MVVM的意义
      使用统一开发模式最大的优点,是统一团队的思维方式和实现方式,从思维上保持代码的整洁。每个理解了模式的人都知道代码该怎么写。此外,MVVM模式在架构上解耦的比较彻底,数据驱动界面的模式也可让结构更清晰。由于业务和界面剥离,业务代码的可测性、可读性、可替换性得到提升。所以,既然WPF支持MVVM模式,就不要把WPF写成WinForm。

    3. View 和 ViewModel
      View是UI、ViewModel是界面的数据模型。ViewModel和View是怎么沟通的呢?ViewModel只会给View传递两种数据:属性数据和操作数据。传递数据用一般的数据模型来处理,传递操作用命令属性来处理。

    项目结构

    引用包说明

    1. MaterialDesignThemes:主要用于界面的美化,通过NuGet包管理搜索MaterialDesignThemes直接安装
    2. Prism.Wpf:是实现MVVM的框架,通过NuGet包管理搜索Prism.Wpf直接安装

    项目目录结构说明

    WinPidKiller 项目名
         - Models 业务数据模型层
             NetworkInfo.cs 网络端口数据模型
             ProcessInfo.cs 进程数据模型
        - Services 业务逻辑层
             IProcessInfoService.cs 进程业务操作接口
             - impl 业务逻辑实现
                 ProcessInfoService.cs 进程业务操作实现类
         - ViewModels 视图数据模型层,沟通View和Model的重要组件
             ProcessItemViewModel.cs 单行进程视图数据模型(列表中每行数据的模型)
             MainWindowViewModel.cs 主视图数据模型
         - Views 界面层
         MainWindow.xmal 主窗口文件

    代码解释说明

    Models

    数据模型仅针对于业务数据
    NetworkInfo.cs

    namespace WinPidKiller.Models
    {
        class NetworkInfo
        {
            public string Pid { get; set; }
            public string AgreeMent { get; set; }
            public string LocalIp { get; set; }
            public string RemoteIp { get; set; }
        }
    }
    

    ProcessInfo.cs

    namespace WinPidKiller.Models
    {
        class ProcessInfo
        {
            public string Name { get; set; }
            public string Pid { get; set; }
            public string AgreeMent { get; set; }
            public string LocalIp { get; set; }
            public string RemoteIp { get; set; }
        }
    }
    
    Services

    仅包含ProcessInfoService类,主要实现端口的查询(通过调用cmd进程),进程的获取和杀死等操作
    ProcessInfoService.cs

    namespace WinPidKiller.Services.Impl
    {
        class ProcessInfoService : IProcessInfoService
        {
            /**
             * 若port为空则获取所有进程信息
             * 若port不为空则获取占用port的线程
             */
            public List<ProcessInfo> GetAllProcessInfo(String port)
            {
                List<ProcessInfo> processInfoList = new List<ProcessInfo>();
    
                // 拿到所有进程
                Dictionary<int, Process> processMap = GetAllProcess();
    
                List<NetworkInfo> networkInfos = null;
                if (!(string.IsNullOrEmpty(port)))
                {
                    // 根据port查询出对应的端口信息,展示对应进程信息
                    networkInfos = GetPortInfo(port);
                } else
                {
                    networkInfos = GetPortInfo(); 
                }
    
                foreach (NetworkInfo networkInfo in networkInfos)
                {
                    ProcessInfo processInfo = new ProcessInfo();
    
                    int.TryParse(networkInfo.Pid, out int pid);
                    Process process = processMap[pid];
    
                    processInfo.Name = process.ProcessName;
                    processInfo.Pid = process.Id.ToString();
                    processInfo.AgreeMent = networkInfo.AgreeMent;
                    processInfo.LocalIp = networkInfo.LocalIp;
                    processInfo.RemoteIp = networkInfo.RemoteIp;
    
                    processInfoList.Add(processInfo);
                }
    
                return processInfoList;
            }
    
            /**
             * 获取所有进程信息
             */
            public List<ProcessInfo> GetAllProcessInfo()
            {
                return GetAllProcessInfo(null);
            }
    
            /**
             * 根据pid列表杀死所有进程
             */
            public void KillProcess(List<string> pidList)
            {
                if (pidList == null || pidList.Count == 0)
                {
                    MessageBox.Show("请选择正确的进程号");
                    return;
                }
    
                Dictionary<int, Process> processMap = GetAllProcess();
    
                StringBuilder sb = new StringBuilder();
                foreach (var pidStr in pidList)
                {
                    int.TryParse(pidStr, out int pid);
                    Process process = processMap[pid];
                    try
                    {
                        process.Kill();
                        sb.Append("已杀掉");
                        sb.Append(process.ProcessName);
                        sb.Append("进程!!!");
                    }
                    catch (Win32Exception e)
                    {
                        sb.Append(process.ProcessName);
                        sb.Append(e.Message.ToString());
                    }
                    catch (InvalidOperationException e)
                    {
                        sb.Append(process.ProcessName);
                        sb.Append(e.Message.ToString());
                    }
                }
    
                MessageBox.Show(sb.ToString());
            }
    
            /**
             * 获取所有原始进程信息,并封装为Dictionary
             */
            private Dictionary<int, Process> GetAllProcess()
            {
                Process[] processes = Process.GetProcesses();
                return processes.ToDictionary(key => key.Id, process => process);
            }
    
            /**
             * 获取所有端口信息
             */
            private List<NetworkInfo> GetPortInfo()
            {
                return GetPortInfo(null);
            }
    
            /**
             * 通过端口取出所有相关的数据
             */
            private List<NetworkInfo> GetPortInfo(string port)
            {
                List<NetworkInfo> networkInfoList = new List<NetworkInfo>();
                Process process = CreateCmd();
                process.Start();
    
                if (string.IsNullOrEmpty(port))
                {
                    process.StandardInput.WriteLine(string.Format("netstat -ano"));
                } else
                {
                    process.StandardInput.WriteLine(string.Format("netstat -ano|find "{0}"", port));
                }
               
                process.StandardInput.WriteLine("exit");
                StreamReader reader = process.StandardOutput;
                string strLine = reader.ReadLine();
                while (!reader.EndOfStream)
                {
                    strLine = strLine.Trim();
                    if (strLine.Length > 0 && ((strLine.Contains("TCP") || strLine.Contains("UDP"))))
                    {
                        Regex r = new Regex(@"s+");
                        string[] strArr = r.Split(strLine);
                        // 解析数据格式为 TCP   0.0.0.0:135    0.0.0.0:0   LISTENING   692
                        int defaultResultLength = 5;
                        if (strArr.Length == defaultResultLength)
                        {
                            NetworkInfo networkInfo = new NetworkInfo();
                            // 只拿第一行数据,拿完就撤(每个PID展示一个port就行)
                            networkInfo.AgreeMent = strArr[0];
                            networkInfo.LocalIp = strArr[1];
                            networkInfo.RemoteIp = strArr[2];
                            networkInfo.Pid = strArr[4];
    
                            networkInfoList.Add(networkInfo);
                        }
                    }
                    strLine = reader.ReadLine();
                }
                reader.Close();
                process.Close();
                return networkInfoList;
            }
    
            /**
             * 创建cmd控件
             */
            private Process CreateCmd()
            {
                Process process = new Process();
                process.StartInfo.FileName = "cmd.exe";
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.RedirectStandardInput = true;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.CreateNoWindow = true;
                return process;
            }
    
        }
    
    }
    
    ViewModels

    主要实现进程列表中单个进程的数据模型ProcessItemViewModel的实现,ProcessItemViewModel比业务数据模型多了选中属性selectItem。另外包含主窗体模型,完成剩下的数据和命令传递。
    ProcessItemViewModel.cs

    namespace WinPidKiller.ViewModels
    {
        class ProcessItemViewModel : BindableBase
        {
            public ProcessInfo ProcessInfo { get; set; }
    
            private Boolean selectItem;
            public Boolean SelectItem
            {
                get { return selectItem; }
                set
                {
                    selectItem = value;
                    SetProperty(ref selectItem, value);
                }
            }
        }
    }
    

    MainWindowViewModel.cs

    namespace WinPidKiller.ViewModels
    {
        /**
         * 做双向绑定,port提供查询框用,processInfo列表提供dataGrid用
         */
        class MainWindowViewModel : BindableBase
        {
            private int port;
            public int Port
            {
                get { return port; }
                set { 
                    port = value;
                    SetProperty(ref port, value);
                }
            }
    
            /**
             * 如果这个DataList列表的内容需要同步刷新,
             * 则类型必须是ObservableCollection。
             * 否则就算控件与数据绑定成功,控件只在初始化时能够正确显示数据,
             * 之后数据发生改变时,控件不会自动刷新。
             */
            private ObservableCollection<ProcessItemViewModel> processItemList;
            public ObservableCollection<ProcessItemViewModel> ProcessItemList
            {
                get { return processItemList; }
                set {
                    processItemList = value;
                    SetProperty(ref processItemList, value);
                }
            }
    
            public MainWindowViewModel()
            {
                // 加载数据
                LoadProcessInfo();
    
                QueryPortCommand = new DelegateCommand(new Action(QueryPortCommandExec));
                KillCommand = new DelegateCommand(new Action(KillCommandExec));
                RefreshCommand = new DelegateCommand(new Action(RefreshCommandExec));
            }
    
            private void LoadProcessInfo()
            {
                IProcessInfoService processInfoService = new ProcessInfoService();
                processItemList = new ObservableCollection<ProcessItemViewModel>();
                processItemList.AddRange(GetProcessItemViewModel(processInfoService.GetAllProcessInfo())); 
            }
    
            // 绑定检索命令 和 kill命令
            public DelegateCommand QueryPortCommand { get; set; }
            public DelegateCommand KillCommand { get; set; }
            public DelegateCommand RefreshCommand { get; set; }
    
            private void QueryPortCommandExec()
            {
                IProcessInfoService processInfoService = new ProcessInfoService();
                processItemList.Clear();
                processItemList.AddRange(GetProcessItemViewModel(processInfoService.GetAllProcessInfo(port.ToString())));
            }
    
            private void RefreshCommandExec()
            {
                IProcessInfoService processInfoService = new ProcessInfoService();
                processItemList.Clear();
                processItemList.AddRange(GetProcessItemViewModel(processInfoService.GetAllProcessInfo()));
            }
    
            private void KillCommandExec()
            {
                List<String> pidList = new List<string>();
                foreach (var processItem in processItemList)
                {
                    if (processItem.SelectItem) 
                    {
                        pidList.Add(processItem.ProcessInfo.Pid);
                    }
                }
    
                IProcessInfoService processInfoService = new ProcessInfoService();
                processInfoService.KillProcess(pidList);
    
                // 杀死进程后,重新加载列表
                this.QueryPortCommandExec();
            }
    
            /**
         * 将ProcessInfo列表转为ProcessItemViewModel列表
         */
            private List<ProcessItemViewModel> GetProcessItemViewModel(List<ProcessInfo> processInfos)
            {
                List<ProcessItemViewModel> itemList = new List<ProcessItemViewModel>();
                foreach(ProcessInfo processInfo in processInfos){
                    ProcessItemViewModel item = new ProcessItemViewModel() { ProcessInfo = processInfo };
                    itemList.Add(item);
                }
                return itemList;
            }
    
        }
        
    }
    
    主窗体界面

    MainWindow.xaml.cs

    namespace WinPidKiller
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new MainWindowViewModel();
            }
        }
    }
    

    MainWindow.xaml

    <Window x:Class="WinPidKiller.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WinPidKiller"
            mc:Ignorable="d"
            Title="Pid Killer" Height="450" Width="800"
            xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
            TextElement.Foreground="{DynamicResource MaterialDesignBody}"
            TextElement.FontWeight="Regular"
            TextElement.FontSize="13"
            TextOptions.TextFormattingMode="Ideal" 
            TextOptions.TextRenderingMode="Auto"        
            Background="{DynamicResource MaterialDesignPaper}"
            >
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="80"></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
    
            <materialDesign:Card Grid.Row="0" Padding="8" Margin="8,5,8,0">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition></ColumnDefinition>
                        <ColumnDefinition Width="110"></ColumnDefinition>
                        <ColumnDefinition Width="110"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
    
                    <TextBox Grid.Column="0" Text="{Binding Path=Port}" HorizontalAlignment="Stretch" Margin="0,0,110,0" FontSize="20" VerticalAlignment="Center"/>
                    <Button Content="检索" Grid.Column="0" Width="100" HorizontalAlignment="Right" Command="{Binding QueryPortCommand}"/>
                    <Button Content="刷新" Grid.Column="1" Width="100" HorizontalAlignment="Right" Command="{Binding RefreshCommand}"/>
                    <Button Content="杀死" Grid.Column="2" Width="100" HorizontalAlignment="Right" Command="{Binding KillCommand}"/>
                </Grid>
            </materialDesign:Card>
    
            <materialDesign:Card Grid.Row="1" Padding="8" Margin="8,5,8,5" >
                <DataGrid 
                    x:Name="dataGrid"
                    FontSize="15"
                    AlternationCount="2"
                    GridLinesVisibility="Vertical"
                    AutoGenerateColumns="False"
                    IsReadOnly="True"
                    ItemsSource="{Binding Path=ProcessItemList}"
                          >
                    <DataGrid.Columns>
                        <DataGridCheckBoxColumn Width="50" Header="" Binding="{Binding Path=SelectItem,UpdateSourceTrigger=PropertyChanged}" IsReadOnly="False" CanUserSort="False" />
                        <DataGridTextColumn Width="Auto" Header="进程名" Binding="{Binding Path=ProcessInfo.Name}"/>
                        <DataGridTextColumn Width="100" Header="PID"  Binding="{Binding Path=ProcessInfo.Pid}"/>
                        <DataGridTextColumn Width="80" Header="协议"  Binding="{Binding Path=ProcessInfo.AgreeMent}"/>
                        <DataGridTextColumn Width="200" Header="本机IP:端口"  Binding="{Binding Path=ProcessInfo.LocalIp}"/>
                        <DataGridTextColumn Width="200" Header="远程IP:端口"  Binding="{Binding Path=ProcessInfo.RemoteIp}"/>
                    </DataGrid.Columns>
                </DataGrid>
            </materialDesign:Card>
        
        </Grid>
    </Window>
    
  • 相关阅读:
    循环冗余检验 (CRC) 算法原理
    工具分享——将C#文档注释生成.chm帮助文档
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
  • 原文地址:https://www.cnblogs.com/zer0Black/p/13766857.html
Copyright © 2020-2023  润新知