查殺進程小工具——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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM