dotnet 融合 Avalonia 和 UNO 框架

lindexi發表於2024-06-23

現在在 .NET 系列裡面,勢頭比較猛的 UI 框架中,就包括了 Avalonia 和 UNO 框架。本文將告訴大家如何嘗試在一個解決方案裡面融合 Avalonia 和 UNO 兩個框架,即在一個程序裡面跑起來兩個框架

開始之前先聊會背景故事

我比較看好 Avalonia 的現在和 UNO 的未來。但是我不怎麼想在 Avalonia 的基礎上造基礎庫和設施。我大概是在 2017 年的時候就參與了 Avalonia 的開發,但是隨著更深入的投入發現了 Avalonia 團隊的一些問題,那會感覺到 Avalonia 更像是一個玩具、一個實驗場,而不是一個可產品化的應用。核心原因在於有一些意見上沒能和我達成一致。一個框架開發需要比較全面的能力和知識,有一些知識屬於特定領域的。但是 Avalonia 團隊裡面缺乏這部分知識,且很多時候都是拍腦袋按照自己想法進行實現的。這就導致了一些專業的模組實現過於奇怪。其輸入法、文字相關、觸控相關部分實現都比較糟心。介面佈局方式,以及一些基礎實現和我預期相差較大。再加上版本之間的 API 穩定性和行為穩定性,導致了我不想在 Avalonia 的基礎上進行投入基礎庫的製造和基礎設施的搭建

好在 2023 的下半年(準確來說是3月,但是剛開始沒有什麼影響),進了一位 CEO 到 Avalonia 團隊,在這個期間給 Avalonia 帶來極大的提升,直接從一個玩具級提升到產品級。這個過程中 Avalonia 做了相當多的工作,包括進行了大規模的重構,大量基礎設施的建設,最佳化了非常多的開發除錯的能力。整體開發 Avalonia 起來也是非常舒坦,且有了支援大型專案的能力。得益於 Avalonia 非常長的開源時間作為底蘊,從 2013 年開源至今,在 Avalonia 框架裡面積累了大量的跨平臺經驗,特別是在 Linux 的桌面端應用上的經驗,進行了非常多的適配。再加上 2023 的下半年進了 Mike James 作為 Avalonia 的 CEO 角色,讓 Avalonia 有了非常多的資源投入,以及拉動了非常多相關方的支援,使得 Avalonia 迎來一大波激進的最佳化。最佳化方面包括了框架底層到上層 API 的重構,也包括了拉來了 JetBrains 的 ReSharper 和 Rider 的官方支援使得開發除錯等各種方面的有了非常大的最佳化

可以這麼說,整個 Avalonia 的開發體驗,從 2023 的下半年可以和之前作為一個巨大的劃分點。讓我打分的話,之前是不及格,現在是 90 分

但是 Mike James 在 2023 的 11 月 跑路了,這就使得從 2024 的上半年開始,整個 Avalonia 的混亂程度又上來了。好在現在 Mike James 又回到 Avalonia 團隊了,期待後續 Avalonia 團隊的進步

以下是從 https://theorg.com/org/avalonia-ui/org-chart/mike-james 裡面複製的 Mike James 簡介,可以看到他是很厲害的且有經驗的

Mike James has a diverse work experience in the technology industry. Mike is currently serving as the Chief Executive Officer of Avalonia UI since March 2023. Prior to that, they worked at Microsoft from 2016 to 2023, where they held various roles including Senior Developer Advocate, Technical Solutions Professional, and Program Manager II. From 2013 to 2017, Mike worked at Xamarin as a Developer Evangelist/Advocate and a Customer Support Engineer. Mike started their career at Pharos Architectural Controls Ltd in 2010 as a Development Support Technician.

這也就是為什麼我看好 Avalonia 的現在的原因。當然了 Mike James 是一個原因,客套來說其整個團隊也都功不可沒。那接下來繼續聊一下 UNO 框架

整個 UNO 框架起初是建立在 WinUI 的側邊的,即在現有的 WinUI 或 UWP 應用裡面,使用 UNO 框架將其構建出跨平臺的版本。這樣做的策略是 UNO 框架可以複用 UWP 的基礎設施和 API 設計。從一開始上就規避了 Avalonia 裡面混亂的 API 設計和基礎設施。但是缺點也很明顯,就是 WinUI 的 API 設計比 Win32 前輩差太多了,且 UWP 也砍掉了大量的 WPF 能力,導致了 UNO 被 WinUI 所拖累。再加上 UNO 開源時間還短,距今僅有 6 年時間,再加上 UNO 同時在啃食全平臺,即移動端 和 WASM 和桌面端,導致了完善程度不如 Avalonia 高

但 UNO 的優勢在於有強有力的控制管理,這和以前(特指 2023 之前)的容易受到社群投毒的 Avalonia 有著巨大的不同,其交付能力有所保證。其次是 UNO 的整體開發團隊投入和卷的程度看起來比 Avalonia 更大得多。最後是使用了 WinUI 的 API 組織方式進行兜底,以及參考了 WinUI3 的設計,確保了很多專業性模組上的實現正確性。最後一點是和 Avalonia 策略上的差別,在 UNO 上是寧可不實現也儘量不給出知識性錯誤的實現方式,而 Avalonia 則是別人有我就得有,不管是否水土不服。再加上 WinUI 和 MAUI 團隊對 UNO 的幫襯,讓 UNO 的整體技術更加全面。這就是我比較看好 UNO 的未來的原因

那如何現在我就需要開發呢?我敢不敢全用 UNO 呢?如果是桌面端的話,不敢,因為現在的 UNO 在桌面端完全不夠 Avalonia 打的。選 Avalonia 呢,但是我的基礎庫和基礎設施還是需要造的,一旦選 Avalonia 就意味著我有大量的測試實驗需要做,去測試 Avalonia 的各種行為,且可能在下個 Avalonia 版本釋出之後,我的這些測試實驗和基礎庫就全都白乾了,因為 Avalonia 就進行了不相容的修改變更了。嗯…在 Avalonia 的 11.0 和 11.1 已經幹了這件事了。既然我看好 UNO 的未來,那不如基礎庫就在 UNO 的基礎上造咯。即使我說 UNO 在桌面端完全不夠 Avalonia 打的,但是作為基礎庫所需的基礎能力,還是能夠提供的

於是我就選擇了上層應用使用 Avalonia 做,底層一些基礎設施使用 UNO 做。我就有了兩個框架在一個應用裡面,於是就有了本文的 融合 Avalonia 和 UNO 框架到一個解決方案裡面。以下是 Avalonia 對此的評價:

Avalonia, unlike Uno, isn't a direct reimplementation of any existing framework but a modern, cross-platform toolkit inspired by WPF. Avalonia focuses on rendering the entire UI independently, akin to Flutter, ensuring consistent, high-quality visuals on all platforms. In contrast, Uno aims to replicate the UWP & WinUI API across platforms, utilising native primitives similar to MAUI. This makes Avalonia ideal for creating a uniform, flexible UI design across every platform. We advise developers to try both technologies when considering Avalonia vs Uno Platform and decide which they prefer.

我就聽了他 We advise developers to try both technologies 半句話,好的,那就嘗試兩個框架咯

為什麼不是合併 Avalonia 和 UNO 框架到一個專案裡面?這個想法太可怕了,這兩個框架都是進行了大量且深度的黑科技研發的,能夠在一個解決方案裡面共存能活就好了

以下是我給出的最簡的讓 Avalonia 和 UNO 框架跑在一個程序上的方法

分別新建 Avalonia 和 UNO 框架兩個專案的最簡模式,其中 Avalonia 框架命名為 AvaloniaIDemo 專案,將 UNO 框架命名為 UnoDemo 框架。我特別推薦大家先拿空專案進行測試,玩明白了再修改複雜的專案

最簡跑起來的方法就是讓 UnoDemo 專案引用 AvaloniaIDemo 專案,這時候看起來構建什麼的都沒有問題。為了測試將 Avalonia 跑起來,修改 UnoDemo 專案的 MainPage.xaml 檔案,新增一個按鈕,點選這個按鈕可以將 Avalonia 框架跑起來,程式碼如下

<Page x:Class="UnoDemo.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:UnoDemo"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <StackPanel
        HorizontalAlignment="Center"
        VerticalAlignment="Center">
    <TextBlock AutomationProperties.AutomationId="HelloTextBlock"
          Text="Hello Uno Platform"
          HorizontalAlignment="Center" />
    <Button x:Name="RunAvaloniaButton" HorizontalAlignment="Center" Content="Run Avalonia" Click="RunAvaloniaButton_OnClick"/>
  </StackPanel>
</Page>

後臺程式碼實現如下

    private void RunAvaloniaButton_OnClick(object sender, RoutedEventArgs e)
    {
        // Create the new Thread to run the Avalonia 
        
        var thread = new Thread(() =>
        {
            AvaloniaIDemo.Program.Main([]);
        })
        {
            IsBackground = true,
            Name = "Avalonia main thread"
        };
        if (OperatingSystem.IsWindows())
        {
            thread.SetApartmentState(ApartmentState.STA);
        }
        thread.Start();
    }

這裡需要新建一個後臺執行緒給 Avalonia 跑,因為 Avalonia 一跑就會卡住執行緒,只有在 Avalonia 應用退出時才會退出卡住執行緒邏輯

額外說明為什麼不用 Task 的方式跑,而是選擇 Thread 的原因,這是因為 Task 預設走執行緒池,執行緒池可不開森你拿一個執行緒跑長時間的任務,這樣會佔用執行緒池的資源。對於此業務情況下,需要長時間執行的,那就是自己開 Thread 更好

以上就是最基礎的實現方法了

本文以上程式碼放在 githubgitee 上,可以使用如下命令列拉取程式碼

先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 6ef2308fd744d276c4db076ac58efaad7b0d1c25

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 6ef2308fd744d276c4db076ac58efaad7b0d1c25

獲取程式碼之後,進入 AvaloniaIDemo/WawjejokeniDejeyaibeweji 資料夾,即可獲取到原始碼

可以試試拉取我的程式碼,開啟 AvaloniaIDemo\WawjejokeniDejeyaibeweji\AvaloniaIDemo.sln 檔案,然後切換 UnoDemo 作為啟動專案,選擇 UnoDemo(Desktop) 而不是 UnoDemo(WinAppSDK Packaged) 跑起來,再試試點選按鈕看看是否能夠彈出 AvaloniaIDemo 的視窗

此時跑起來的應用是 Avalonia+WPF+Uno 三個框架的,哈哈,為什麼這裡會加上 WPF 呢?這是因為 UNO 在 Windows 的底層就是 WPF 框架承接。而 Avalonia 是自己對接 Win32 層,沒有中間商

可以看到本文的這個方式做的是比較淺的融合,視窗級相互引用而已。更深層次的融合現在可行性不高,歡迎大家自行摸索

以下是我的更多踩坑經驗

找不到 SDK 專案新增不上來

如果一開始新建的 sln 檔案是對 Avalonia 專案的,那麼將可以在新增現有 UNO 專案時,發現 VisualStudio 不給新增,提示報錯資訊如下

找不到指定的 SDK "Uno.Sdk" 專案無法新增

這個原因是在 sln 檔案相同的資料夾下找不到包含 Uno.Sdk 定義的 global.json 檔案。只需在 sln 檔案相同的資料夾下放一個 global.json 檔案,裡面的內容程式碼大概如下

{
  // To update the version of Uno please update the version of the Uno.Sdk here. See https://aka.platform.uno/upgrade-uno-packages for more information.
  "msbuild-sdks": 
  {
    "Uno.Sdk": "5.2.161"
  }
}

以上的 5.2.161 版本號,還請修改為你建立 UNO 專案時的選用版本號。或者直接將 UNO 專案的 global.json 檔案複製過去也可以

這是因為在此版本時,新建的 UNO 專案的 csproj 專案檔案裡使用了 UNO 自己製作的 Uno.Sdk 而不是 Microsoft.NET.Sdk 框架

以下為 UNO 專案的 csproj 專案檔案的示例程式碼

<Project Sdk="Uno.Sdk">
    ... 忽略其他程式碼
</Project>

以下為其他控制檯或 Avalonia 專案的 csproj 專案檔案的示例程式碼

<Project Sdk="Microsoft.NET.Sdk">
    ... 忽略其他程式碼
</Project>

可以看到 Sdk 屬性的不同

無法在 Avalonia 專案引用 UNO 專案

為什麼在本文例子裡面是使用 UNO 專案引用 Avalonia 專案,而不是反過來呢?這是因為在 UNO 的 5.2 版本里面,自創了名為 netx.xx-desktop 的框架。從 dotnet 設計上說,自己建立框架也是可行的,畢竟 dotnet 裡面就有了 netx.xx-windows 等框架,用於區分平臺

在 UNO 裡,確實使用 netx.xx-desktop 框架可以讓內部開發更加便利,實現桌面端的跨平臺和移動端等的區分

但是這也導致了與其他現有設施對接時候的難點。現在 Avalonia 依然使用的是純 dotnet 專案,這讓 Avalonia 的構建非常簡單且穩定。大家都知道,對構建過程更多的定製就一般意味著會有更多詭異的問題,現在的 UNO 就是這樣。整體的構建不僅被 WinUI 拖累,還會有自己建立的框架的坑。即使 UNO 團隊有專職的測試人員也架不住開發者複雜的開發環境投毒

如果讓 Avalonia 專案引用 UNO 專案,將會構建失敗,錯誤資訊如下

error NU1201: 專案 UnoDemo 與 net8.0 (.NETCoreApp,Version=v8.0) 不相容

其原因就是 UNO 使用的是 net8.0-desktop 框架,而 Avalonia 專案是 net8.0 框架的。從 dotnet 的 SDK 設計約束上就是 net8.0-desktop 框架範圍比 net8.0 框架更大,不能讓更小範圍的框架引用更大的範圍,這就是失敗的原因

釋出 Linux 平臺失敗

釋出 linux 平臺時,需要先在 Avalonia 專案裡面進行一次釋出,釋出引數需要和 UNO 專案的相同。如在 AvaloniaIDemo 裡面,選用 Release 加 linux-x64 的獨立釋出方式進行釋出,再在 UNO 專案也選用 Release 加 linux-x64 的獨立釋出方式進行釋出,如此才能釋出成功

否則如果只是立刻在 UNO 專案裡釋出,則可能遇到 未能找到後設資料檔案 錯誤,失敗資訊大概如下

未能找到後設資料檔案“C:\lindexi\Code\AvaloniaIDemo\WawjejokeniDejeyaibeweji\AvaloniaIDemo\obj\Release\net8.0\linux-x64\ref\AvaloniaIDemo.dll”

這個原因大概是 Avalonia 也對釋出做了些科技,導致的不相容問題。更細節我沒有繼續研究

經過我的測試,如此方式釋出之後,可以在 Ubuntu 和 UOS 兩個 Linux 系統上執行,且工作符合預期

讓 Avalonia 依賴 net8.0 的 Uno 專案

由於 Uno 不僅可以跑 net8.0-desktop 框架,作為全平臺支援的 UI 框架,天生也就支援單跑 net8.0 框架。嘗試修改 UNO 的 csproj 專案檔案為如下程式碼

<Project Sdk="Uno.Sdk">
  <PropertyGroup>
    <TargetFrameworks>
      net8.0-windows10.0.19041;
      net8.0-desktop;
      net8.0;
    </TargetFrameworks>

    ... 忽略其他程式碼
</Project>

此時就滿足了給 Avalonia 引用的基礎條件了,然而此時卻會發現 Avalonia 經常無法建立生成程式碼,這是因為 Avalonia 所做的黑科技剛好和 Uno 所作的衝突,從而導致 Avalonia 無法成功從 axaml 生成程式碼

同時也存在了許多型別衝突,進一步導致了專案難以構建。如我的以下變更: https://github.com/lindexi/lindexi_gd/commit/f7032fc9a952a073d1e4cdb5d81955e38019ac3c 將會難以進行構建

即使透過僅引用程式集解決了引用問題,也會面臨著執行不起來的問題。這是因為 Uno 只有在 desktop 下才複製真正的桌面執行時依賴,如 Uno.UI.Runtime.Skia.Wpf.dllUno.UI.Runtime.Skia.X11.dll 檔案,缺少了這些檔案的 Uno 程式集是無法正常執行的

且如果你的 IDE 是 Rider 的話,更會出現問題。在 Rider 裡面,只會構建所需的框架,即使只對 UnoDemo 構建 net8.0 框架,而無視 net8.0-desktop 框架。儘管 Rider 這個是為了最佳化構建速度,但是也帶來了更多問題,如現在就無法透過複製 net8.0-desktop 框架的內容到輸出路徑進行替換從而解決執行的問題

解決 Rider 的不構建的方法是採用解決方案的依賴的方式

型別衝突

由於 Avalonia 存在大量的型別和 UNO/WinUI 相同,這將會導致如此引用將會很難寫程式碼

比如寫一個 Thickness 都可能遇到以下錯誤資訊

MainPage.xaml.cs(15,9,15,18): error CS0104: “Thickness”是“Avalonia.Thickness”和“Microsoft.UI.Xaml.Thickness”之間的不明確的引用

想要更好的解決是再新建一個作為入口的程式集,這個程式集依然是 UNO 框架的,分別引用 AvaloniaIDemo 和 UnoDemo 專案,只在此入口程式集做啟動和實現對接,其他的事情都不要做

為了更好的實現對接,那一般還需要一個純 dotnet 專案,這個專案是 API 定義專案,用於讓互相不引用的 AvaloniaIDemo 和 UnoDemo 透過此 API 定義專案進行抽象對接

如此大家也可以看到透過這個方式開發具備一定的複雜性

接下來我將告訴大家這個方法

新入口程式集

看起來再新建一個程式集作為入口程式集也不錯,此方式只是搭建稍微有點複雜而已,但能夠確保 Avalonia 和 Uno 專案更具獨立性

新建一個名為 AppDemo 的控制檯專案,再新建一個名為 LibDemo 的控制檯專案。斷開 UnoDemo 和 AvaloniaIDemo 的聯絡。讓 UnoDemo 和 AvaloniaIDemo 專案都引用 LibDemo 專案。讓 AppDemo 同時引用 UnoDemo 和 AvaloniaIDemo 專案

完成引用之後的專案引用關係如下圖

為了讓 AppDemo 控制檯專案能夠正確的引用上 UnoDemo 專案,需要修改專案檔案,修改之後的程式碼大概如下

<Project Sdk="Uno.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0-desktop</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <UnoSingleProject>true</UnoSingleProject>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\AvaloniaIDemo\AvaloniaIDemo.csproj" />
    <ProjectReference Include="..\UnoDemo\UnoDemo\UnoDemo.csproj" />
  </ItemGroup>

</Project>

核心就是設定 <Project Sdk="Uno.Sdk"> 和設定框架版本以及加上 UnoSingleProject 屬性

編輯 LibDemo 控制檯專案,去掉 OutputType 定義,讓其成為基礎庫專案,修改之後的 csproj 專案檔案的程式碼如下

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

現在 UnoDemo 和 AvaloniaIDemo 專案相互之間不引用,為了能夠間接呼叫到,就需要讓兩個專案都依賴抽象

在 LibDemo 專案裡面提供簡單的介面和靜態型別用於被注入,程式碼如下

namespace LibDemo;

public interface IAppRunner
{
    void Run();
}

以上的 IAppRunner 用於給具體的框架使用,無論是 Avalonia 先啟動也會,還是 Uno 先啟動也好,只要後被啟的框架實現此介面。再注入到下面定義的 Runner 靜態方法裡面,即可完成被呼叫的依賴

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LibDemo;

public static class Runner
{
    public static void SetAppRunner(IAppRunner appRunner)
    {
        _appRunner = appRunner;
    }

    public static void Run() => _appRunner?.Run();

    private static IAppRunner? _appRunner;
}

在本文例子裡面依然是 Uno 先起來,然後調起 Avalonia 專案,根據上文的設計,需要在 AvaloniaIDemo 專案裡面定義具體的實現邏輯,程式碼如下

using LibDemo;

namespace AvaloniaIDemo;

public static class Initializer
{
    public static void InitAssembly()
    {
        Runner.SetAppRunner(new AppRunner());
    }
}

file class AppRunner : IAppRunner
{
    public void Run()
    {
        Program.Main([]);
    }
}

以上的 file class AppRunner 使用到了 file 關鍵字,這是表示當前的 AppRunner 型別只有在當前檔案可訪問。透過此關鍵字可以更大程度進行控制訪問範圍

如此即可在 AppDemo 裡面,透過呼叫 InitAssembly 方法完成 AvaloniaIDemo 專案的初始化,程式碼如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AppDemo;

internal class Program
{
    [STAThread]
    public static void Main(string[] args)
    {
        AvaloniaIDemo.Initializer.InitAssembly();

        ... // 忽略其他程式碼
    }
}

完成 AvaloniaIDemo 的注入之後,在 UnoDemo 即可使用抽象的依賴呼叫,如以下程式碼

    private void RunAvaloniaButton_OnClick(object sender, RoutedEventArgs e)
    {
        var thread = new Thread(() =>
        {
            Runner.Run();
        })
       
        ... // 忽略其他程式碼
    }

可以看到 UnoDemo 從原本的具體的 AvaloniaIDemo.Program.Main([]); 換成了間接的 Runner.Run(); 呼叫

修改之後的 UnoDemo 的 MainPage.xaml.cs 的所有程式碼如下

using LibDemo;

namespace UnoDemo;

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
    }
    
    private void RunAvaloniaButton_OnClick(object sender, RoutedEventArgs e)
    {
        // Create the new Thread to run the Avalonia 

        var thread = new Thread(() =>
        {
            Runner.Run();
        })
        {
            IsBackground = true,
            Name = "Avalonia main thread"
        };
        if (OperatingSystem.IsWindows())
        {
            thread.SetApartmentState(ApartmentState.STA);
        }
        thread.Start();
    }
}

完成基礎邏輯之後,即可在 AppDemo 將 Uno 應用啟動,程式碼如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AppDemo;

internal class Program
{
    [STAThread]
    public static void Main(string[] args)
    {
        AvaloniaIDemo.Initializer.InitAssembly();

        UnoDemo.Program.Main(args);
    }
}

接下來為了讓整體構建更加簡單,需要修改讓 Avalonia 專案去掉 OutputType 屬性,保持輸出為基礎庫方式

<OutputType>WinExe</OutputType>

修改之後的 AvaloniaIDemo 的 csproj 專案檔案的程式碼如下

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
    <ApplicationManifest>app.manifest</ApplicationManifest>
    <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
  </PropertyGroup>


  <ItemGroup>
    <PackageReference Include="Avalonia" Version="11.0.6" />
    <PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
    <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.6" />
    <PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.6" />
    <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.6" />
  </ItemGroup>


  <ItemGroup>
    <ProjectReference Include="..\LibDemo\LibDemo.csproj" />
  </ItemGroup>
</Project>

可以看到 AvaloniaIDemo 專案沒有對 UnoDemo 專案進行任何的引用

如此即可嘗試直接 F5 執行。以及釋出之後執行

以上方式我在 Windows 上 F5 直接執行成功,釋出到 Ubuntu 和 UOS 上也能執行成功,看起來屬於坑比較少的方式

本文以上程式碼放在 githubgitee 上,可以使用如下命令列拉取程式碼

先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 6cc41fa8c98001d034a59d20cad83386ae16b5aa

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 6cc41fa8c98001d034a59d20cad83386ae16b5aa

獲取程式碼之後,進入 AvaloniaIDemo/WawjejokeniDejeyaibeweji 資料夾,即可獲取到原始碼

相關文章