Windows 窗體(或簡稱 WinForms),多年來被用於開發具有豐富和互動式介面的基於 Windows 的強大應用程式。
各類企業對這些桌面應用程式的投入量非常巨大,每月有大約 240 萬開發人員使用 Visual Studio 建立桌面式應用。
利用和擴充套件現有WinForms程式碼資產的好處無疑極具吸引力,但還有其他好處。
WinForms 拖放式設計器體驗使使用者能夠構建功能齊全的 UI,而不需要任何特殊知識或培訓。
WinForms 應用程式易於部署和更新,可獨立於 Internet 連線工作,並且可以在不向 Internet 公開配置的本地計算機上執行,提高了安全性。
一直到最近,WinForms 應用程式都還依然只能使用完整的 .NET Framework 進行構建,但 .NET Core 3.0 預覽版的釋出改變了這一現狀。
.NET Core 的新功能和優點不再侷限於 Web 開發。
透過.NET Core 3.0 WinForms增加了一些功能,比如更易於部署、更高的效能、對.NET Core 特有的NuGet 包的支援、.NET Core 命令列介面 (CLI) 等等。
本文將介紹使用這些功能的諸多好處、它們的重要性以及如何在WinForms應用程式中使用它們。
讓我們直接開始構建第一個 .NET Core 3.0 WinForms 應用程式。
在本文中,我將構建一個應用程式,用於檢索並顯示託管在 GitHub 上的其中一個開源 Microsoft 儲存庫的開放式拉取請求。
第一步是安裝最新版本的 Visual Studio 2019 和 .NET Core 3.0 SDK,之後便可使用 .NET Core CLI 命令來建立新的 WinForms 應用程式。
在新增 .NET Core 支援之前,WinForms 應用程式無法實現這一點。
即將釋出的是一個新的 Visual Studio 模板,用於建立針對 .NET Core 3.0 的 WinForms 專案。
由於模板目前尚未釋出,因此現在讓我們透過執行以下命令生成一個名為 PullRequestHub 的新 WinForms 專案:
dotnet new winforms -o PullRequestHub
為了確保專案成功建立,請導航到 dotnet new 命令建立的新目錄,使用 CLI 構建並執行專案,如下所示:
cd .\PullRequestHub\
由於可以訪問 .NET Core CLI,因此也可以訪問要還原、執行和構建的命令。在執行之前,請嘗試還原和構建命令,如下所示:
dotnet restore
dotnet build
這些命令的工作方式與在 .NET Core Web 應用程式的命令列中執行時的工作方式相同。請註意,在執行 dotnet run 命令時,它實際上會在執行應用之前執行還原和構建命令 (bit.ly/2UCkEaN)。現在讓我們執行專案,透過在命令列輸入 dotnet run 對其進行測試。
成功!你剛剛建立了第一個 .NET Core WinForms 應用程式。
執行時,將看到螢幕上出現一個帶有“Hello .NET Core!”文字的窗體。
在進一步嚮應用程式新增邏輯之前,讓我們花一點時間來討論 Visual Studio 中 WinForms 設計器檢視的當前狀態。
設定 .NET Core WinForms 應用的設計器
在 Visual Studio 中開啟 CLI 生成的專案時,可能會註意到缺少某些功能。最值得註意的是,目前沒有為 .NET Core WinForms 應用程式提供設計器檢視。雖然有計劃提供此功能,但尚未完成。
幸運的是,有一個解決方案可讓你至少在新增本機支援之前訪問設計器。現在,可建立包含 UI 檔案的 .NET Framework 專案。
透過這種方式就可以使用設計器編輯 UI 檔案,然後 .NET Core 專案將取用 .NET Framework 專案中的 UI 檔案。這使你能夠利用 UI 功能,同時仍然在 .NET Core 中構建應用程式。
以下是我為專案執行操作的方法。
除了所建立的 PullRequestHub 專案之外,還需要新增一個在 .NET Full-Framework 版本上執行的新 WinForms 專案。
將此專案命名為 PullRequestHub.Designer。建立新專案後,從 .NET Core 專案中刪除 Form1 檔案,只保留 Program.cs 類。
導航到 PullRequestHub.Designer 並將窗體檔案重新命名為 PullRequestForm。
現在將編輯 .NET Core 專案檔案,並新增以下程式碼,將兩個專案中的檔案關聯起來。這還將負責處理將來建立的任何其他窗體或資源:
<Compile Include=”..\PullRequestHub.Designer\**\*.cs” />
ItemGroup>
儲存專案檔案後,將看到 PullRequestForm 檔案出現在解決方案資源管理器中,你將能夠與它們進行互動。
如果要使用 UI 編輯器,需要確保從 .NET Core 專案中關閉 PullRequestForm 檔案,並從 .NET Framework 專案中開啟 PullRequestForm 檔案。
更改將在兩者中進行,但僅 .NET Framework 專案提供編輯器。
構建應用程式
讓我們開始嚮應用程式中新增一些程式碼。為了從 GitHub 檢索開放式拉取請求,我需要建立一個 HttpClient。
這就是 .NET Core 3.0 的用武之地,因為它提供了對新 HttpClientFactory 的訪問許可權。全框架版本中的 HttpClient 存在一些問題,包括使用 using 陳述句建立客戶端的問題。HttpClient 物件將被釋放,但底層套接字在一段時間內不會被釋放,預設情況下為 240 秒。如果套接字連線保持開啟狀態 240 秒,並且系統中的吞吐量很高,則系統可能會使所有空閑套接字達到飽和。發生這種情況時,新請求必須等待套接字釋放,這可能對效能產生一些非常嚴重的影響。
HttpClientFactory 有助於緩解這些問題。首先,它提供了一種在更中心位置預先配置客戶端實現的更簡單方法。它還為你管理 HttpClients 的生命週期,因此你不會遇到前面提到的問題。我們來瞭解如何在 WinForms 應用程式中執行此操作。
使用此新功能的最佳和最簡單的方法之一是透過依賴項註入。依賴項註入,或更普遍的控制反轉,是一種將依賴項傳遞到類中的技術。它也是減少類耦合和簡化單元測試的絕佳方法。例如,你將看到如何在程式啟動時建立 IHttpClientFactory 的實體,以便稍後在窗體中使用該物件。在以前的 .NET 版本中,這在 WinForms 中不太容易實現,這就是使用 .NET Core 的另一個優勢。
在 Program.cs 中,將建立一個名為 ConfigureServices 的方法。在本方法中,建立新的 ServiceCollection,以便可透過依賴項註入來使用服務。
需要先安裝最新的這兩個 NuGet 包:
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Http
然後新增圖 1 中所示的程式碼。
這將建立一個要在窗體中使用的新 IHttpClientFactory。
結果會得到一個客戶端,可以顯式地使用它來處理涉及 GitHub API 的請求。
圖 1 建立新的 IHttpClientFactory
private static void ConfigureServices()
{
var services = new ServiceCollection();
services.AddHttpClient();
services.AddHttpClient(“github”, c =>
{
c.BaseAddress = new Uri(“https://api.github.com/”);
c.DefaultRequestHeaders.Add(“Accept”, “application/vnd.github.v3+json”);
c.DefaultRequestHeaders.Add(“User-Agent”, “HttpClientFactory-Sample”);
c.DefaultRequestHeaders.Add(“Accept”, “application/json”);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
});
}
接下來,需要將實際的窗體類 PullRequestForm 註冊為單一實體。在本方法的末尾處,新增以下行:
services.AddSingleton<PullRequestForm>();
然後,需要建立 ServiceProvider 的實體。在 Program.cs 類的頂部,建立以下屬性:
private static IServiceProvider ServiceProvider { get; set; }
現在有了 ServiceProvider 屬性,請在 ConfigureServices 方法的末尾處新增一行來構建 ServiceProvider,如下所示:
ServiceProvider = services.BuildServiceProvider();
最後,完整的 ConfigureServices 方法應類似於圖 2 中的程式碼。
圖 2 ConfigureServices 方法
private static void ConfigureServices()
{
var services = new ServiceCollection();
services.AddHttpClient();
services.AddHttpClient(“github”, c =>
{
c.BaseAddress = new Uri(“https://api.github.com/”);
c.DefaultRequestHeaders.Add(“Accept”, “application/vnd.github.v3+json”);
c.DefaultRequestHeaders.Add(“User-Agent”, “HttpClientFactory-Sample”);
c.DefaultRequestHeaders.Add(“Accept”, “application/json”);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
});
services.AddSingleton();
ServiceProvider = services.BuildServiceProvider();
}
現在,需要在啟動時將窗體與容器連線起來。
應用程式執行時,這將呼叫 PullRequestForm 並提供可用的必要服務。
將 Main 方法更改為以下程式碼:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ConfigureServices();
Application.Run((PullRequestForm)ServiceProvider.GetService(typeof(PullRequestForm)));
}
感覺很棒!現在已完成所有連線。在 PullRequestForm 建構式中,註入剛連線好的 IHttpClientFactory 並將其分配給本地變數,如以下程式碼所示:
private static HttpClient _httpClient;
public PullRequestForm(IHttpClientFactory httpClientFactory)
{
InitializeComponent();
_httpClient = httpClientFactory.CreateClient(“github”);
}
現在有了 HttpClient,可使用它來呼叫 GitHub 以檢索拉取請求、問題等。這也使得後續幾個步驟略微棘手。來自 HttpClient 的呼叫將為非同步請求,如果你一直在使用 WinForms,你就知道接下來該怎麼做。接下來必須處理執行緒,並將排程更新傳送到 UI 執行緒。
為了開始檢索所有拉取請求,需在檢視中新增一個按鈕。透過這種方式,可在將來新增更多儲存庫或更多儲存庫組進行檢查。使用連線的設計器,將按鈕拖到窗體上,並將文字重新命名為“Microsoft”。
在此過程中,給按鈕取一個更具含義的名稱,如 RetrieveData_Button。需要系結到 RetrieveData_Button_Click 事件,但需要使用此程式碼將其設定為非同步:
private async void RetrieveData_Button_Click(object sender, EventArgs e)
{
}
在此處,需要呼叫檢索開放式 GitHub 拉取請求的方法。但首先,由於現在正在處理非同步呼叫,因此必須連線 SynchronizationContext。可透過新增新屬性並使用以下程式碼更新建構式來完成此操作:
private static HttpClient _httpClient;
private readonly SynchronizationContext synchronizationContext;
public PullRequestForm(IHttpClientFactory httpClientFactory)
{
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
_httpClient = httpClientFactory.CreateClient(“github”);
}
接下來,建立一個模型並將其命名為 PullRequestData,以便可以輕鬆地反序列化請求。相關程式碼如下:
public class PullRequestData
{
public string Url { get; set; }
public string Title { get; set; }
}
最後,建立一個名為 GetPullRequestData 方法。在本方法中,將向 GitHub API 發出請求並檢索所有開放式拉取請求。將反序列化 JSON 請求,因此請將最新版本的 Newtonsoft.Json 包新增到專案中。
程式碼如下:
private async Task> GetPullRequestData()
{
var gitHubResponse =
await _httpClient.GetStringAsync(
$”repos/dotnet/winforms/pulls?state=open”);
var gitHubData =
JsonConvert.DeserializeObject>(gitHubResponse);
return gitHubData;
}
現在可使用 RetrieveData_Button_Click 方法呼叫此方法。獲得所需的資料串列後,為每個標題建立標簽串列,以便在窗體上顯示。
獲得標簽串列後,可在 UpdateUI 方法中將它們新增到 UI 中。
圖 3 顯示了此步驟。
圖 3 從 RetrieveData_Button_Click 進行呼叫
private async void RetrieveData_Button_Click(object sender, EventArgs e)
{
var pullRequestData = await GetPullRequestData();
await Task.Run(() =>
{
var labelsToAdd = new List
然後,UpdateUI 方法將使用 synchronizationContext 更新 UI,如下所示:
public void UpdateUI(List)
{
synchronizationContext.Post(new SendOrPostCallback(o =>
{
foreach (var label in labels)
{
Controls.Add(label);
}
}), labels);
}
如果執行應用程式並單擊 Microsoft 按鈕,UI 將和 GitHub 上 dotnet/winforms 儲存庫中的所有開放式拉取請求的窗體一起更新。
現在輪到你了。正如本文的標題所說,為了使它成為一個真正的集中式拉取請求中心,讓我們更新此示例,以便從多個 GitHub 儲存庫中進行讀取。
這些儲存庫不需要來自 Microsoft 團隊,儘管觀察它們的行程十分有趣。
例如,微服務體系結構非常常見,在其中你可能擁有許多組成整個系統的儲存庫。鑒於一般而言,不長時間將分支和拉取請求單獨儲存是一個好主意,這樣的工具可以提高對開放式拉取請求的見解並提高整個系統的質量。
可設定一個 Web 應用,但又得擔心部署、執行、身份驗證等問題。
使用 . NET Core 中的 WinForms 應用程式,你無需擔心任何此類問題。
現在讓我們來看看使用 .NET Core 構建 WinForms 應用的最大優勢之一。
打包應用程式
過去,部署新的或更新的 WinForms 應用程式可能會導致與主機上安裝的 .NET Framework 版本相關的問題。透過 .NET Core 則可以獨立部署應用並從單個檔案夾執行應用,而不依賴於計算機上安裝的 .NET Framework 版本。
這意味著使用者無需安裝任何內容;他們可以僅執行應用程式。透過 .NET Core 還可以一次更新和部署一個應用,因為其包版本不會相互影響。
對於本文中的示例應用,需要為其獨立打包。請註意,獨立應用程式會更大,因為它們包含 .NET Core 庫。如果要部署到安裝了 .NET Core 最新版本的計算機中,則無需獨立部署應用。相反,可透過利用已安裝的 .NET Core 版本來減小已部署應用的大小。當不希望應用程式依賴於它將執行的環境時,可使用獨立選項。
若要在本地打包應用程式,需要確保在設定中啟用了開發人員樣式。嘗試執行打包專案時,Visual Studio 將進行提示並提供設定的連結,但若要直接啟用它,請轉到 Windows 設定,按 Windows 徽標鍵並搜尋“設定”。在搜尋框中鍵入“面向開發人員的設定”並選擇它。將看到啟用開發人員樣式的選項。選擇並啟用此選項。
大多數情況下,如果以前打包過 WinForms 應用程式,那麼建立獨立包的步驟則看起來比較熟悉。首先,建立一個新的 Windows 應用程式打包專案。將新專案命名為 PullRequestHubPackaging。當系統提示選擇標的和最低平臺版本時,請使用預設值並單擊“確定”。右鍵單擊應用程式併為 PullRequestHub 專案新增取用。
新增取用後,需要將 PullRequestHub 專案設定為入口點。完成後,在下次構建時很可能會看到以下錯誤:“如果 SelfContained 為 true,則專案 PullRequestHub 必須在專案檔案中指定 RuntimeIdentifiers。”
若要修複此錯誤,請編輯 PullRequestHub.csproj 檔案。在開啟本專案檔案時,你會註意到使用 .NET Core 的另一個優點,這是因為該專案檔案現在使用的是新的輕量級格式。在基於 .NET Framework 的 WinForms 專案中,專案檔案將更詳細地包含顯式預設值和取用,並將 NuGet 取用拆分為 packages.config 檔案。新的專案檔案格式會將包取用引入到專案檔案中,從而可以在一個位置管理所有依賴項。
在本檔案的第一個 PropertyGroup 節點中,新增以下行:
<RuntimeIdentifiers>win-x86RuntimeIdentifiers>
執行時識別符號用於標識執行應用程式的標的平臺,並由 .NET 包用來表示 NuGet 包中特定於平臺的資產。
新增該識別符號後,構建就應該成功了,可將 PullRequestHubPackaging 專案設定為 Visual Studio 中的啟動專案。
指示專案是獨立的設定是 PullRequestHubPackaging.wapproj 檔案中需要註意的一點。檔案中需要註意的程式碼部分如下:
<ItemGroup>
<ProjectReference Include=”..\PullRequestHub\PullRequestHub.csproj”>
<DesktopBridgeSelfContained>TrueDesktopBridgeSelfContained>
<DesktopBridgeIdentifier>$(DesktopBridgeRuntimeIdentifier)
DesktopBridgeIdentifier>
<Properties>SelfContained=%(DesktopBridgeSelfContained);
RuntimeIdentifier=%(DesktopBridgeIdentifier)
Properties>
<SkipGetTargetFrameworkProperties>True
SkipGetTargetFrameworkProperties>
ProjectReference>
ItemGroup>
在此處,可看到 DesktopBridgeSelfContained 選項已設定為 true,這使得 WinForms 應用程式可以與 .NET Core 二進位制檔案一起打包。
執行該專案時,它會將檔案轉儲到“win-x86”的檔案夾中,該檔案夾的路徑與此類似:
C:\Your-Path\PullRequestHub\PullRequestHub\bin\x86\Debug\netcoreapp3.0
在 win-x86 檔案夾中你會註意到有許多 DLL,這些 DLL 包含獨立應用執行所需的所有檔案。
你更有可能需要將應用部署為旁載應用程式或將其上載到 Microsoft Store。旁載將使用 appinstaller 檔案進行自動更新。
從Visual Studio 2017 Update 15.7 開始支援這些更新。
此外,還可以建立支援提交到 Microsoft Store 進行分發的包。
Microsoft Store 隨後會處理應用的所有程式碼簽名、分發和更新。
除了這些選項之外,還有一些正在進行的工作可以將應用程式打包成單個可執行檔案,從而無需使用 DLL 填充輸出目錄。
其他優點
透過 .NET Core 3.0,還可以利用 C# 8.0 的功能,包括可為 null 的取用型別、介面上的預設實現、使用樣式切換陳述句的改進以及非同步資料流。
若要啟用 C# 8.0,請開啟 PullRequestHub.csproj 檔案並將以下行新增到第一個 PropertyGroup:
<LangVersion>8.0LangVersion>
使用 .NET Core 和 WinForms 的另一個優點是兩個專案均為開源。
這使得你可以訪問原始碼、提交 bug、共享反饋併成為貢獻者。
請檢視 github.com/dotnet/winforms上的 WinForms 專案。
.NET Core 3.0 旨在為企業和公司在 WinForms 應用程式中進行的投資註入新的活力,而 WinForms 應用程式會繼續保持高效、可靠且易於部署和維護。
開發人員可以利用新的 .NET Core 特定類(如 HttpClientFactory)、採用 C# 8.0 功能(如可為 null 的取用型別)和打包獨立應用程式。
還可以訪問 .NET Core CLI 以及 .NET Core 附帶的所有效能改進。
朋友會在“發現-看一看”看到你“在看”的內容