Python集成C#实现界面操作下载文件功能的全过程

 更新时间:2022年03月22日 12:05:31   作者:程序园丁  
使用脚本进行下载的需求很常见,下面这篇文章主要给大家介绍了关于Python集成C#实现界面操作下载文件功能的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下

〇、写在前面

你想的没错,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. 方法定义

/// <summary>
/// 当前地址
/// </summary>
public string ThisUrl
{
    get
    {
        return textUrl.Text;
    }
}

/// <summary>
/// 当前保存路径
/// </summary>
public string ThisSavePath
{
    get
    {
        return textSavePath.Text;
    }
}
/// <summary>
/// 下载事件委托
/// </summary>
public event EventHandler DownloadEvent;

/// <summary>
/// 下载按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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

/// 选择按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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();
}

/// <summary>
/// 打开对话框
/// </summary>
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界面操作下载文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • django执行数据库查询之后实现返回的结果集转json

    django执行数据库查询之后实现返回的结果集转json

    这篇文章主要介绍了django执行数据库查询之后实现返回的结果集转json,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-03-03
  • pycharm访问mysql数据库的方法步骤

    pycharm访问mysql数据库的方法步骤

    这篇文章主要介绍了pycharm访问mysql数据库的方法步骤。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-06-06
  • Python实现脚本转换为命令行程序

    Python实现脚本转换为命令行程序

    使用Python中的scaffold和click库,你可以将一个简单的实用程序升级为一个成熟的命令行界面工具,本文就来带你看看具体实现方法,感兴趣的可以了解下
    2022-09-09
  • 使用python将csv数据导入mysql数据库

    使用python将csv数据导入mysql数据库

    这篇文章主要为大家详细介绍了如何使用python将csv数据导入mysql数据库,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-05-05
  • Nginx+Uwsgi+Django 项目部署到服务器的思路详解

    Nginx+Uwsgi+Django 项目部署到服务器的思路详解

    这篇文章主要介绍了Nginx+Uwsgi+Django 项目部署到服务器的思路,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • 使用pickle存储数据dump 和 load实例讲解

    使用pickle存储数据dump 和 load实例讲解

    今天小编就为大家分享一篇使用pickle存储数据dump 和 load实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • 基于Python编写一个二维码生成器

    基于Python编写一个二维码生成器

    二维码又称二维条码,常见的二维码为QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式。本文将利用python生成一个简单的二维码生成器,需要的可以参考一下
    2022-06-06
  • Python静态类型检查新工具之pyright 使用指南

    Python静态类型检查新工具之pyright 使用指南

    这篇文章主要介绍了Python静态类型检查新工具之pyright 使用指南,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-04-04
  • Python正则表达式经典入门教程

    Python正则表达式经典入门教程

    这篇文章主要介绍了Python正则表达式,结合具体实例形式归纳总结了Python正则表达式中常用的各种函数与相关使用技巧,需要的朋友可以参考下
    2017-05-05
  • 解决django前后端分离csrf验证的问题

    解决django前后端分离csrf验证的问题

    今天小编就为大家分享一篇解决django前后端分离csrf验证的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-02-02

最新评论