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

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

需求设计

  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>

查杀进程小工具——WPF和MVVM初体验的更多相关文章

  1. shell脚本执行查找进程,然后查杀进程

    shell 执行查找进程,然后查杀进程脚本如下: ps -ef | grep 'IOE' |grep -v 'grep'| awk '{print \$2}' |while read pid; do ...

  2. windows查看端口占用情况及查杀进程

    我们平时在做web开发运行web服务器或运行某个应用时会报错,提示该应用的端口号已被占用,我们可以用以下的方法解决. 解决方法一:重新为应用配置端口. 解决方法二:找到占用端口的应用并关闭该应用释放占 ...

  3. 【全面解禁!真正的Expression Blend实战开发技巧】第七章 MVVM初体验-在DataGrid行末添加按钮

    原文:[全面解禁!真正的Expression Blend实战开发技巧]第七章 MVVM初体验-在DataGrid行末添加按钮 博客更新较慢,先向各位读者说声抱歉.这一节讲解的依然是开发中经常遇到的一种 ...

  4. Linux进程管理:查杀进程

    一.查看进程 Linux下显示系统进程的命令ps,最常用的有ps -ef 和ps aux.这两个到底有什么区别呢? 两者没太大差别,讨论这个问题,要追溯到Unix系统中的两种风格,System V风格 ...

  5. CMD查看进程ID并查杀进程

    开始-运行,输入CMD打开命令行界面,输入命令netstat -ano 结束该进程C:\>taskkill /f /t /im Wiz.exe 根据进程ID杀 >taskkill /F / ...

  6. linux一行命令查杀进程

    https://blog.csdn.net/primeprime/article/details/52415273 ps -efww | grep -w 'helloworld' | grep -v ...

  7. Linux 查杀进程

    ps -eaf |grep "stoporder.php" | grep -v "grep"| awk '{print $2}'|xargs kill -9 # ...

  8. WPF MVVM初体验

    首先MVVM设计模式的结构, Views: 由Window/Page/UserControl等构成,通过DataBinding与ViewModels建立关联: ViewModels:由一组命令,可以绑 ...

  9. 说不尽的MVVM(2) – MVVM初体验

    知识预备 阅读本文,我假定你已经具备以下知识: C#.WPF基础知识 了解Lambda表达式和TPL 对事件驱动模型的了解 知道ICommand接口 发生了什么 某程序员接到一个需求,编写一个媒体渲染 ...

随机推荐

  1. Hadoop Windows IDEA

    java jdk1.8都可以了 注意jdk的路径要拷贝到一个没有空格的路径改掉JAVA_HOME系统环境变量 在etc/hadoop/hadoop_env.cmd里有设置%JAVA_HOME%了不用管 ...

  2. Idiomatic Phrases Game(最短路+注意坑点)

    Tom is playing a game called Idiomatic Phrases Game. An idiom consists of several Chinese characters ...

  3. CMOS设计手册—基础篇

    模拟CMOS 衬底噪声:由于相邻的电阻互相注入电流而产生的衬底噪声.解决方法:在两个电阻之间加入一个P+注入区(作为P衬底晶圆的衬底接触).P+注入区保护电路免受载流子的影响,由于注入区是一个环形,所 ...

  4. P3311 [SDOI2014]数数 AC自动机+数位DP

    题意 给定一个正整数N和n个模式串,问不大于N的数字中有多少个不包含任意模式串,输出对\(1e^9+7\)取模后的答案. 解题思路 把所有模式串都加入AC自动机,然后跑数位DP就好了.需要注意的是,这 ...

  5. ASP导出数据到excel遇到的一些问题

    一直用动易平台的ASP做新闻发布网站,直到现在才接触导出数据到Excel的问题,目的在于公司要统计各部门的投稿量,要做这么个东西,实现起来是挺简单的,但是第一次做,还是费了一些功夫的,特此记录一下 主 ...

  6. FRP代理链

    一.实验拓扑: 二.实验测试 1.vps上开启frp服务端服务 2.在DMZ区域机器上开启frp客户端,同时再开启下一层代理链的服务端 3.在内网A区域中的机器上开启第二次frp代理的客户端服务 4. ...

  7. 2申请高德地图key 初始化地图

    https://console.amap.com/dev/key/app vue-amap-基于-vue-2x-与高德的地图组件 https://elemefe.github.io/vue-amap/ ...

  8. Mysql优化概述及其压力测试工具

    衡量指标 TPS:Transactions Per Second (每秒传输的事物处理个数) ,这是指服务器每秒处理的事物数,支持事物的存储引擎如Innodb等特有的一个性能指标; QPS:Queri ...

  9. 原生 Java 客户端进行消息通信

    原生 Java 客户端进行消息通信 Direct 交换器 DirectProducer:direct类型交换器的生产者 NormalConsumer:普通的消费者 MulitBindConsumer: ...

  10. kali命令大全

    arch 显示机器的处理器架构(1)uname -m 显示机器的处理器架构(2)uname -r 显示正在使用的内核版本dmidecode -q 显示硬件系统部件 - (SMBIOS / DMI)hd ...