前言
为什么这么做?
因为我需要在C#代码中执行一些用python写的算法,所以自然有这样的需求。
是什么项目?
本示例是使用 WPF 项目xaml
我们首先编写前端文件,定义出所用的控件
<Window x:Class="Wpf_py_demo.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:Wpf_py_demo"
mc:Ignorable="d"
Title="MainWindow" d:DesignHeight="400" d:DesignWidth="600">
<Grid ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 中间区域,使用 Grid 三行三列布局 -->
<Grid Grid.Row="1">
<!-- 三行:文件行、目录行、进度行 -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 三列:标签(Auto)、输入区(*)、按钮(Auto) -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- 第一行:截面带数据文件 -->
<TextBlock
Grid.Row="0" Grid.Column="0"
Text="截面带数据文件:"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
Margin="10,5,10,0"/>
<TextBox
Grid.Row="0" Grid.Column="1"
x:Name="FilePath" Height="50"
IsReadOnly="True"
TextWrapping="Wrap"
Text="C:\AAA-personalData\project\00-graduateProject\001-Laser_Profile\WPF_PCV\pyFunc\Comparison\Data\250606\20250606103010____ProcsData.txt"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="0,10,0,5"/>
<Button
Grid.Row="0" Grid.Column="2"
x:Name="ChooseFile"
Click="ChooseFile_Click"
Content="选择文件" Height="30"
Margin="10,5,10,0"/>
<!-- 第二行:CMM 目录 -->
<TextBlock
Grid.Row="1" Grid.Column="0"
Text="CMM 目录:"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
Margin="10,5"/>
<TextBox
Grid.Row="1" Grid.Column="1"
x:Name="FolderPath" Height="50"
IsReadOnly="True"
TextWrapping="Wrap"
Text="C:\AAA-personalData\project\00-graduateProject\001-Laser_Profile\WPF_PCV\pyFunc\Comparison\Data\cmm0609"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="0,5,0,5"/>
<Button
Grid.Row="1" Grid.Column="2"
x:Name="ChooseFolder"
Click="ChooseFolder_Click"
Content="选择CMM文件夹" Height="30"
Margin="10,5"/>
<!-- 第三行:进度 -->
<TextBlock
Grid.Row="2" Grid.Column="0"
Text="进度:"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
Margin="10,0,10,5"/>
<ProgressBar
Grid.Row="2" Grid.Column="1"
x:Name="PbReportProgress"
Height="50"
Minimum="0" Maximum="100" Value="0"
VerticalAlignment="Center"
Margin="0,5,0,10"/>
<Button
Grid.Row="2" Grid.Column="2"
x:Name="OutputPDF"
Click="OutputPDF_Click"
Content="一键生成PDF报告" Height="30"
Margin="10,0,10,5"/>
</Grid>
<!-- 底部日志框 -->
<TextBox
Grid.Row="2"
x:Name="TbProgressInfo"
Height="165"
Margin="10,5,10,0"
Text="等待生成…"
TextWrapping="Wrap"/>
</Grid>
</Window>CSharp
然后我们需要编写 code behind 逻辑
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;
using System.IO;
using System.Diagnostics;
using System.Threading.Tasks;
using System;
namespace Wpf_py_demo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// 选择文件按钮点击事件
/// </summary>
private void ChooseFile_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog
{
Title = "选择截面带数据文件",
Filter = "文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*",
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
};
if (openFileDialog.ShowDialog() == true)
{
FilePath.Text = openFileDialog.FileName;
}
}
/// <summary>
/// 选择文件夹按钮点击事件
/// </summary>
private void ChooseFolder_Click(object sender, RoutedEventArgs e)
{
using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
{
dialog.Description = "选择CMM数据文件夹";
dialog.ShowNewFolderButton = false;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
FolderPath.Text = dialog.SelectedPath;
}
}
}
/// <summary>
/// 生成PDF报告按钮点击事件
/// </summary>
private async void OutputPDF_Click(object sender, RoutedEventArgs e)
{
// 验证输入
if (string.IsNullOrWhiteSpace(FilePath.Text))
{
System.Windows.MessageBox.Show("请先选择截面带数据文件!", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (string.IsNullOrWhiteSpace(FolderPath.Text))
{
System.Windows.MessageBox.Show("请先选择CMM数据文件夹!", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (!File.Exists(FilePath.Text))
{
System.Windows.MessageBox.Show("选择的文件不存在!", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (!Directory.Exists(FolderPath.Text))
{
System.Windows.MessageBox.Show("选择的文件夹不存在!", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 禁用按钮,防止重复点击
OutputPDF.IsEnabled = false;
PbReportProgress.Value = 0;
TbProgressInfo.Text = "开始生成报告...\n";
try
{
await RunPythonScript(FilePath.Text, FolderPath.Text);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"生成报告时发生错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
TbProgressInfo.Text += $"错误:{ex.Message}\n";
}
finally
{
// 重新启用按钮
OutputPDF.IsEnabled = true;
PbReportProgress.Value = 100;
}
}
/// <summary>
/// 修复编码问题
/// </summary>
private string FixEncoding(string input)
{
if (string.IsNullOrEmpty(input))
return input;
try
{
// 如果字符串看起来已经是正确的UTF-8,直接返回
if (!input.Contains("�") && !HasEncodingIssues(input))
{
return input;
}
// 尝试从Latin-1转换为UTF-8(常见的编码问题)
byte[] bytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(input);
string result = Encoding.UTF8.GetString(bytes);
if (!result.Contains("�") && result.Length > 0)
{
return result;
}
// 如果转换失败,返回原始字符串
return input;
}
catch
{
// 如果转换失败,返回原始字符串
return input;
}
}
/// <summary>
/// 检查字符串是否有编码问题
/// </summary>
private bool HasEncodingIssues(string input)
{
// 检查是否包含常见的编码问题字符
return input.Contains("�") || input.Contains("Ã") || input.Contains("â");
}
/// <summary>
/// 查找项目根目录
/// </summary>
private string FindProjectRoot(string startPath)
{
string currentPath = startPath;
// 向上查找,直到找到包含pyFunc目录的路径
while (!string.IsNullOrEmpty(currentPath))
{
string pyFuncPath = System.IO.Path.Combine(currentPath, "pyFunc");
if (Directory.Exists(pyFuncPath))
{
return currentPath;
}
// 向上一级目录
DirectoryInfo parentDir = Directory.GetParent(currentPath);
if (parentDir == null)
break;
currentPath = parentDir.FullName;
}
// 如果没找到,尝试使用相对路径(开发环境)
string devPath = System.IO.Path.GetFullPath(System.IO.Path.Combine(startPath, "..", "..", "..", ".."));
string devPyFuncPath = System.IO.Path.Combine(devPath, "pyFunc");
if (Directory.Exists(devPyFuncPath))
{
return devPath;
}
// 最后尝试当前工作目录
string workingDir = Environment.CurrentDirectory;
string workingPyFuncPath = System.IO.Path.Combine(workingDir, "pyFunc");
if (Directory.Exists(workingPyFuncPath))
{
return workingDir;
}
throw new DirectoryNotFoundException($"无法找到pyFunc目录。搜索路径:{startPath}");
}
/// <summary>
/// 运行Python脚本
/// </summary>
private async Task RunPythonScript(string filePath, string folderPath)
{
await Task.Run(() =>
{
try
{
// 获取项目根目录(从应用程序目录向上查找)
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
string projectRoot = FindProjectRoot(appDirectory);
string pyFuncPath = System.IO.Path.Combine(projectRoot, "pyFunc");
string pythonExePath = System.IO.Path.Combine(pyFuncPath, ".venv", "Scripts", "python.exe");
string scriptPath = System.IO.Path.Combine(pyFuncPath, "comparison", "report.py");
// 检查Python可执行文件是否存在
if (!File.Exists(pythonExePath))
{
throw new FileNotFoundException($"Python可执行文件不存在:{pythonExePath}");
}
// 检查脚本文件是否存在
if (!File.Exists(scriptPath))
{
throw new FileNotFoundException($"Python脚本不存在:{scriptPath}");
}
// 更新UI(需要在UI线程中执行)
Dispatcher.Invoke(() =>
{
PbReportProgress.Value = 10;
TbProgressInfo.Text += "正在启动Python脚本...\n";
});
// 创建进程启动信息
string comparisonPath = System.IO.Path.Combine(pyFuncPath, "comparison");
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = pythonExePath,
Arguments = $"\"{scriptPath}\" \"{filePath}\" \"{folderPath}\"",
WorkingDirectory = comparisonPath, // 设置工作目录为comparison
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8, // 使用UTF8编码
StandardErrorEncoding = Encoding.UTF8 // 使用UTF8编码
};
// 设置环境变量确保Python输出UTF-8
startInfo.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";
startInfo.EnvironmentVariables["PYTHONLEGACYWINDOWSSTDIO"] = "0";
startInfo.EnvironmentVariables["LANG"] = "zh_CN.UTF-8";
// 启动进程
using (Process process = new Process())
{
process.StartInfo = startInfo;
// 处理输出
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Dispatcher.Invoke(() =>
{
// 尝试修复编码问题
string decodedData = FixEncoding(e.Data);
TbProgressInfo.Text += decodedData + "\n";
TbProgressInfo.ScrollToEnd();
// 根据输出更新进度条
if (decodedData.Contains("PROGRESS:"))
{
PbReportProgress.Value = Math.Min(PbReportProgress.Value + 15, 90);
}
});
}
};
process.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Dispatcher.Invoke(() =>
{
// 尝试修复编码问题
string decodedData = FixEncoding(e.Data);
TbProgressInfo.Text += $"错误:{decodedData}\n";
TbProgressInfo.ScrollToEnd();
});
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// 等待进程完成
process.WaitForExit();
Dispatcher.Invoke(() =>
{
if (process.ExitCode == 0)
{
TbProgressInfo.Text += "报告生成完成!\n";
PbReportProgress.Value = 100;
}
else
{
TbProgressInfo.Text += $"Python脚本执行失败,退出代码:{process.ExitCode}\n";
}
});
}
}
catch (Exception ex)
{
Dispatcher.Invoke(() =>
{
TbProgressInfo.Text += $"执行Python脚本时发生错误:{ex.Message}\n";
});
throw;
}
});
}
}
}其中我们特别需要关注路径问题,具体表现在:
python解释器路径;.py文件路径;- 传参路径
- …………
python 运行环境
我们需要在合适的地方创建 python 的虚拟环境,这里我选择的方案是
总结
按照我这么配置下来,应该就能成功调用 python 文件。其中涉及一些比较琐碎的声明,稍微一看即可。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。