〇、写在前面
你想的没错,Python 和 C# 其实都可以单独实现我们要实现的功能,这里笔者只是抱着实验及学习的态度去解决问题。
我是一个 C# 程序员,目前在学习 Python,对于 Python 得天独厚的胶水语言特性自然是必须要领略一番,于是就有了本文。
学会了 Python 调用 C# 的话,就能做很多想到和想不到的东西,这很有趣,不是吗?
一、这个功能是怎么样的
我很熟悉用 C# & WinForm 的方式开发界面,现在刚好学习了 Python 的网络编程的基础库 socket,于是我就想到写一个程序,思路如下:
- 程序运行时会打开一个 WinForm 窗体,窗体上有:
- 输入文件下载地址的地址栏
- 选择文件保存位置的文件开窗按钮
- 当前下载状态的状态区域
- 下载按钮
- 输入下载地址,选择一个文件保存位置
- 点击下载按钮下载文件,状态区域显示文件下载状态,最好能显示下载进度
- 界面放到 WinForm,下载功能放到 Python
二、WinForm 端功能实现
WinForm 分为几部分功能
- 界面设计
- 提供下载地址的公共属性
- 提供文件存储地址公共属性
- 提供用于委托下载事件的委托定义
- 提供记录状态信息的公共方法
- 提供更新进度信息的公共方法
1. 界面设计
首先我们使用 VS 创建一个类库项目
至于为什么没有使用 .NET 5 或者 .net core,是因为:Python 调用 C# 动态链接库
创建项目后新建窗体
本例中设计界面设计如下:
2. 方法定义
////// 当前地址 /// public string ThisUrl { get { return textUrl.Text; } } ////// 当前保存路径 /// public string ThisSavePath { get { return textSavePath.Text; } }
////// 下载事件委托 /// public event EventHandler DownloadEvent; ////// 下载按钮事件 /// /// /// private void buttonDownload_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(this.textUrl.Text)) { MessageBox.Show("请先输入要下载的文件地址!"); this.textUrl.Focus(); return; } if (string.IsNullOrEmpty(this.textSavePath.Text)) { MessageBox.Show("请先选择文件要保存的地址!"); this.textSavePath.Focus(); return; } // 调用委托事件 if(this.DownloadEvent != null) { this.DownloadEvent.Invoke(this, e); } }
打开选择保存文件路径时候由于会报错
在可以调用OLE之前,必须将当前线程设置为单线程单元(STA)模式,请确保您的Main函数带有STAThreadAttribute标记
很无奈,因为我们的调用方并不是 C# 的 Main 函数,而我目前并不知道 Python 调用 C# 如何实现的,所以只能另外想方法,就是把选择保存文件路径的开窗单独启一个线程开发,在子线程上再标记 STA
/// 选择按钮事件 /// /// /// private void buttonSelect_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(this.textUrl.Text)) { MessageBox.Show("请先输入要下载的文件地址!"); this.textUrl.Focus(); return; } var index = this.textUrl.Text.LastIndexOf("/"); var fileName = this.textUrl.Text.Substring(index + 1); Thread importThread = new Thread(() => { var text = OpenDialog(fileName); MessageEvent(text); }); importThread.SetApartmentState(ApartmentState.STA); //重点 importThread.Start(); } ////// 打开对话框 /// private string OpenDialog(string fileName) { var saveFileDialog = new SaveFileDialog(); saveFileDialog.Filter = "所有文件 (*.*)|*.*"; saveFileDialog.FilterIndex = 0; if (!string.IsNullOrEmpty(fileName)) { saveFileDialog.FileName = Path.Combine(saveFileDialog.FileName, fileName); } DialogResult dialogResult = saveFileDialog.ShowDialog(); if (dialogResult == DialogResult.OK) { return saveFileDialog.FileName; } return String.Empty; }
三、Python 端功能实现
Python 中分几部分功能
- 程序调用 .NET 类库打开窗体
- 程序中存在下载指定 URL 文件存储到指定路径的函数定义
- 程序结束的函数定义
- 把当前程序封装成可运行程序(如:Windows 中为封装成 exe)
import socket import time import re mainapp = None # 调用动态链接库的更新状态信息 def LogInfo(text): # print(text) mainapp.LogInfo(text) # 调用动态链接库的更新下载进度 def downloadInfo(c, all): mainapp.SetProcess(c, all) if c == all: # LogInfo("下载进度 {:.2f}".format(c / all * 100)) LogInfo("下载完成。") # else: # LogInfo("下载进度 {:.2f}%".format(c / all * 100)) # 监听下载委托事件 def Download(source, args): thisurl = source.ThisUrl.lower() thispath = source.ThisSavePath LogInfo("下载地址是: {}".format(thisurl)) LogInfo("保存路径为: {}".format(thispath)) reobj = re.compile(r"""(?xi)\A [a-z][a-z0-9+\-.]*:// # Scheme ([a-z0-9\-._~%!$&'()*+,;=]+@)? # User ([a-z0-9\-._~%]+ # Named or IPv4 host |\[[a-z0-9\-._~%!$&'()*+,;=:]+\]) # IPv6+ host """) match = reobj.search(thisurl) if match: HOST = match.group(2) PORT = 443 if thisurl.startswith('https') else 80 mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) mysock.connect((HOST, PORT)) urlend = 'GET {} HTTP/1.0\r\n\r\n'.format(thisurl).encode() # LogInfo("传递参数: {}".format(urlend)) LogInfo("开始下载……") mysock.sendall(urlend) count = 0 picture = b"" hearlength = 0 filelength = 0 hearc = b"" while True: data = mysock.recv(5120) if (len(data) < 1): break time.sleep(0.1) count = count + len(data) picture = picture + data # print(len(data), count) if hearlength == 0: hearlength = picture.find(b"\r\n\r\n") if hearlength > 0: hearc = picture[:hearlength].decode() # print(hearc) sear = re.search('Content-Length: ([0-9]+)', hearc) if sear: filelength = int(sear.groups()[0]) downloadInfo(count - 4 - hearlength, filelength) else: downloadInfo(count - 4 - hearlength, filelength) mysock.close() # Skip past the header and save the picture data picture = picture[hearlength+4:] fhand = open(thispath, "wb") fhand.write(picture) fhand.close() # Code: http://www.py4e.com/code3/urljpeg.py # Or select Download from this trinket's left-hand menu else: LogInfo('下载失败,地址格式存在问题!') # 使用 pythonnet 的方式引入动态链接库 import clr # 此处保证动态链接库文件放在当前文件夹中,如果不在应该使用这种方式 # clr.AddReference('D:\\Path\\DotNetWithPython') # clr.AddReference('D:\\Path\\DotNetWithPython.dll') clr.AddReference('DotNetWithPython') from DotNetWithPython import * mainapp = MainForm() mainapp.DownloadEvent += Download mainapp.ShowDialog()
四、运行效果
五、存在问题
功能实现了,但是存在一个无法解决的问题,就是当文件开始下载后 WinForm 的界面会卡住,疑似是没有用现线程打开主窗体的原因,但是不能解释为什么下载开始的时候没有卡顿,有哪位大白指导一下呢?不胜感激!
总结
到此这篇关于Python集成C#实现界面操作下载文件功能的文章就介绍到这了,更多相关Python界面操作下载文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!