首頁 > 軟體

Python整合C#實現介面操作下載檔案功能的全過程

2022-03-22 16:00:25

〇、寫在前面

你想的沒錯,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.0rnrn'.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"rnrn")
                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介面操作下載檔案內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com