學習.NET MAUI Blazor(三)、創建.NET MAUI Blazor應用並使用AntDesignBlazor

来源:https://www.cnblogs.com/gmval/archive/2022/12/29/17013095.html
-Advertisement-
Play Games

擴展方法名:Filter 支持參數:實體類、JObject 擴展代碼: //白色風車 public static class EntityFrameworkCoreExtensions { private static DbCommand CreateCommand(DatabaseFacade f ...


大致瞭解了Blazor和MAUI之後,嘗試創建一個.NET MAUI Blazor應用。
需要註意的是: 雖然都叫MAUI,但.NET MAUI.NET MAUI Blazor 並不相同,MAUI還是以xaml為主,而MAUI Blazor則是以razor為主。

這個系列還是以MAUI Blazor為主,要創建一個MAUI Blazor應用,需要安裝Visual Studio 2022 17.3 或更高版本,併在安裝程式上,勾選.NET Multi-platform App UI 開發!最好是升級到最新的.NET 7。
在這裡插入圖片描述

目錄

創建.NET MAUI Blazor應用

打開Visual Studio 2022,選擇創建新項目

在這裡插入圖片描述
在搜索框輸入MAUI,選擇.NET MAUI Blazor應用,點下一步

在這裡插入圖片描述
給項目起一個好聽的名字,選擇項目存在的位置,點下一步

在這裡插入圖片描述
選擇目標框架,這裡選擇的是.NET 7,點擊創建
在這裡插入圖片描述
等待創建項目及其依賴項還原。完成後的目錄結構如下:
在這裡插入圖片描述

.NET MAUI Blazor 需要註意的地方

.NET MAUI Blazor 運行在WebView2上,WebView2是微軟推出的新一代用於桌面端混合開發的解決方案。它可以讓本地應用程式(WinForm、WPF、WinUI、Win32)、移動應用程式(MAUI)輕鬆嵌入Web技術。WebView2 控制項使用 Microsoft Edge 作為呈現引擎在客戶端應用程式及App中顯示 Web 內容。使用 WebView2 可以將 Web 代碼嵌入到客戶端應用程式及App中的不同部分,或在單個 WebView 實例中構建所有本機應用程式。

可以這麼看MAUI Blazor, .NET MAUI 包含 BlazorWebView 控制項,該控制項運行將 Razor 組件呈現到嵌入式 Web View 中。 通過結合使用 .NET MAUI 和 Blazor,可以跨移動設備、桌面設備和 Web 重覆使用一組 Web UI 組件。

說人話就是,它就是一個Hybrid App(混合應用) !

調試.NET MAUI Blazor

在windows上調試 MAUI Blazor應用,需要Windows 10 1809及更高版本上,並打開開發者模式。
windows 11上,位於設置->隱私和安全性->開發者選項->開發人員模式
在這裡插入圖片描述
在這裡插入圖片描述
點擊Windows Machine,運行程式!
在這裡插入圖片描述
如無意外,運行成功!
在這裡插入圖片描述
這時,MAUI Blazor使用的是bootstrap樣式以及open-iconic圖標。
wwwroot/index.html中也可以看到

<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />

現在已經有個很多基於Blazor的組件庫,所以暫時把預設的bootstrap替換成第三方組件庫,這裡使用的是AntDesignBlazor

使用AntDesignBlazor 組件庫

安裝依賴:

PM> NuGet\Install-Package AntDesign.ProLayout -Version 0.13.1

註入AntDesign

MauiProgram.cs註入AntDesign 服務與設置基本配置,完整的MauiProgram.cs代碼

using Microsoft.Extensions.Logging;
using MauiBlazorApp.Data;

namespace MauiBlazorApp;

public static class MauiProgram
{
	public static MauiApp CreateMauiApp()
	{
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
			});

		builder.Services.AddMauiBlazorWebView();

#if DEBUG
		builder.Services.AddBlazorWebViewDeveloperTools();
		builder.Logging.AddDebug();
#endif

		builder.Services.AddSingleton<WeatherForecastService>();
        //註入AntDesign
        builder.Services.AddAntDesign();
		//基本配置
		builder.Services.Configure<ProSettings>(settings =>
		{
            settings.NavTheme = "light";
            settings.Layout = "side";
            settings.ContentWidth = "Fluid";
			settings.FixedHeader = false;
			settings.FixSiderbar = true;
            settings.Title = "DotNet寶藏庫";
			settings.PrimaryColor = "daybreak";
			settings.ColorWeak = false;
			settings.SplitMenus= false;
			settings.HeaderRender= true;
			settings.FooterRender= false;
			settings.MenuRender= true;
			settings.MenuHeaderRender= true;
			settings.HeaderHeight = 48;

		});
		return builder.Build();
	}
}

配置項都寫上了。參數含義從表達的意思就能看出來,不做註釋了!

引入樣式

打開wwwroot/index.html。由於我們使用的是AntDesign,所以需要改造下index.html,修改後內容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
    <title>DotNet寶藏庫</title>
    <base href="/" />
    
    <link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet" />
    <link rel="stylesheet" href="_content/AntDesign.ProLayout/css/ant-design-pro-layout-blazor.css" />
</head>

<body>

    <div class="status-bar-safe-area"></div>

    <div id="app">
        <style>
            html,
            body,
            #app {
                height: 100%;
                margin: 0;
                padding: 0;
            }

            #app {
                background-repeat: no-repeat;
                background-size: 100% auto;
            }

            .page-loading-warp {
                padding: 98px;
                display: flex;
                justify-content: center;
                align-items: center;
            }

            .ant-spin {
                -webkit-box-sizing: border-box;
                box-sizing: border-box;
                margin: 0;
                padding: 0;
                color: rgba(0, 0, 0, 0.65);
                font-size: 14px;
                font-variant: tabular-nums;
                line-height: 1.5;
                list-style: none;
                -webkit-font-feature-settings: 'tnum';
                font-feature-settings: 'tnum';
                position: absolute;
                display: none;
                color: #1890ff;
                text-align: center;
                vertical-align: middle;
                opacity: 0;
                -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
                transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
                transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
                transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
            }

            .ant-spin-spinning {
                position: static;
                display: inline-block;
                opacity: 1;
            }

            .ant-spin-dot {
                position: relative;
                display: inline-block;
                font-size: 20px;
                width: 20px;
                height: 20px;
            }

            .ant-spin-dot-item {
                position: absolute;
                display: block;
                width: 9px;
                height: 9px;
                background-color: #1890ff;
                border-radius: 100%;
                -webkit-transform: scale(0.75);
                -ms-transform: scale(0.75);
                transform: scale(0.75);
                -webkit-transform-origin: 50% 50%;
                -ms-transform-origin: 50% 50%;
                transform-origin: 50% 50%;
                opacity: 0.3;
                -webkit-animation: antSpinMove 1s infinite linear alternate;
                animation: antSpinMove 1s infinite linear alternate;
            }

            .ant-spin-dot-item:nth-child(1) {
                top: 0;
                left: 0;
            }

            .ant-spin-dot-item:nth-child(2) {
                top: 0;
                right: 0;
                -webkit-animation-delay: 0.4s;
                animation-delay: 0.4s;
            }

            .ant-spin-dot-item:nth-child(3) {
                right: 0;
                bottom: 0;
                -webkit-animation-delay: 0.8s;
                animation-delay: 0.8s;
            }

            .ant-spin-dot-item:nth-child(4) {
                bottom: 0;
                left: 0;
                -webkit-animation-delay: 1.2s;
                animation-delay: 1.2s;
            }

            .ant-spin-dot-spin {
                -webkit-transform: rotate(45deg);
                -ms-transform: rotate(45deg);
                transform: rotate(45deg);
                -webkit-animation: antRotate 1.2s infinite linear;
                animation: antRotate 1.2s infinite linear;
            }

            .ant-spin-lg .ant-spin-dot {
                font-size: 32px;
                width: 32px;
                height: 32px;
            }

            .ant-spin-lg .ant-spin-dot i {
                width: 14px;
                height: 14px;
            }
            .status-bar-safe-area {
                display: none;
            }

            @supports (-webkit-touch-callout: none) {
                .status-bar-safe-area {
                    display: flex;
                    position: sticky;
                    top: 0;
                    height: env(safe-area-inset-top);
                    background-color: #f7f7f7;
                    width: 100%;
                    z-index: 1;
                }

                .flex-column, .navbar-brand {
                    padding-left: env(safe-area-inset-left);
                }
            }
            @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
                .ant-spin-blur {
                    background: #fff;
                    opacity: 0.5;
                }
            }

            @-webkit-keyframes antSpinMove {
                to {
                    opacity: 1;
                }
            }

            @keyframes antSpinMove {
                to {
                    opacity: 1;
                }
            }

            @-webkit-keyframes antRotate {
                to {
                    -webkit-transform: rotate(405deg);
                    transform: rotate(405deg);
                }
            }

            @keyframes antRotate {
                to {
                    -webkit-transform: rotate(405deg);
                    transform: rotate(405deg);
                }
            }
        </style>
        <div style="
          display: flex;
          justify-content: center;
          align-items: center;
          flex-direction: column;
          min-height: 420px;
          height: 100%;
        ">
            <div class="page-loading-warp">
                <div class="ant-spin ant-spin-lg ant-spin-spinning">
                    <span class="ant-spin-dot ant-spin-dot-spin">
                        <i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i>
                    </span>
                </div>
            </div>
            <div style="display: flex; justify-content: center; align-items: center;">
                 <div class="loading-progress-text"></div>
            </div>
        </div>
    </div>

   

    <script src="_framework/blazor.webview.js" autostart="false"></script>
    <script src="_content/AntDesign/js/ant-design-blazor.js"></script>
</body>

</html>

加入命名空間

_Imports.razor添加AntDesign命名空間:

@using System.Net.Http
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using MauiBlazorApp
@using MauiBlazorApp.Shared
//引入AntDesign
@using AntDesign

設置容器

Main.razor中加入<AntContainer />

<Router AppAssembly="@typeof(Main).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
<!--設置容器-->
<AntContainer />

修改 MainLayout

  • 修改MainLayout.razor
  • MainLayout.razor中預設佈局刪除
  • 引入AntDesign.ProLayout
  • 設置佈局為AntDesign.ProLayout
  • 構造菜單、頁腳的鏈接、版權
  • wwwroot目錄下新建個文件夾images,把提前準備好的logo放進去

完整代碼如下:

@using AntDesign.ProLayout
@inherits LayoutComponentBase

<AntDesign.ProLayout.BasicLayout 
    Logo="@("images/logo.png")"
    MenuData="MenuData">
    <ChildContent>
        @Body
    </ChildContent>
    <FooterRender>
        <FooterView Copyright="MauiBlazorApp" Links="Links"></FooterView>
    </FooterRender>
</AntDesign.ProLayout.BasicLayout>
<SettingDrawer />

@code
{
    private readonly MenuDataItem[] MenuData =
        {
        new MenuDataItem
        {
            Path = "/",
            Name = "Home",
            Key = "Home",
            Icon = "home"
        },
        new MenuDataItem
        {
            Path = "/Counter",
            Name = "Counter",
            Key = "Counter",
            Icon = "plus"
        },
        new MenuDataItem
        {
            Path = "/FetchData",
            Name = "FetchData",
            Key = "FetchData",
            Icon = "cloud"
        }
    };
    private readonly LinkItem[] Links =
    {
        new LinkItem
        {
            Key = "DotNet寶藏庫",
            Title = "基於Ant Design Blazor",
            Href = "https://antblazor.com",
            BlankTarget = true
        }
    };
}

這時可以把項目中無用的內容刪除掉了,如Shared/NavMenu.razorwwwroot/css文件。
由於刪除掉了css文件夾,頁面元素肯定沒有樣式了。那麼就簡單的改造下預設的幾個頁面!

改造預設頁面

index.razor

打開Pages/Index.razor,將演示組件SurveyPrompt 刪掉。順便把Shared/SurveyPrompt.razor也刪除掉。將<h1>Hello, world!</h1>
替換為Ant Design組件。

@page "/"

<Title Level="1">Hello,DotNet寶藏庫</Title>

<br />
<Text Type="success">歡迎關註我的公眾號!</Text>

Counter.razor

打開 Pages/Counter.razor,將代碼改為如下:

@page "/counter"

<Title Level="2">HCounter</Title>
<Divider />
<p role="status">Current count: @currentCount</p>

<Button @onclick="IncrementCount" Type="primary">AntDesign 按鈕</Button>
@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

FetchData.razor

打開Pages/FetchData.razor,將數據表格替換為Ant Design,刪除頁面所有代碼,替換為Ant Design的示例!

@page "/fetchdata"
@using System.ComponentModel
@using AntDesign.TableModels
@using System.Text.Json

@using MauiBlazorApp.Data
@inject WeatherForecastService ForecastService

<Table @ref="table"
       TItem="WeatherForecast"
       DataSource="@forecasts"
       Total="_total"
       @bind-PageIndex="_pageIndex"
       @bind-PageSize="_pageSize"
       @bind-SelectedRows="selectedRows"
       OnChange="OnChange">
    <Selection Key="@(context.Id.ToString())" />
    <PropertyColumn Property="c=>c.Id" Sortable />
    <PropertyColumn Property="c=>c.Date" Format="yyyy-MM-dd" Sortable />
    <PropertyColumn Property="c=>c.TemperatureC" Sortable />
    <PropertyColumn Title="Temp. (F)" Property="c=>c.TemperatureF" />
    <PropertyColumn Title="Hot" Property="c=>c.Hot">
        <Switch @bind-Value="@context.Hot"></Switch>
    </PropertyColumn>
    <PropertyColumn Property="c=>c.Summary" Sortable />
    <ActionColumn>
        <Space>
            <SpaceItem><Button Danger OnClick="()=>Delete(context.Id)">Delete</Button></SpaceItem>
        </Space>
    </ActionColumn>
</Table>
<br />
<p>PageIndex: @_pageIndex | PageSize: @_pageSize | Total: @_total</p>

<br />
<h5>selections:</h5>
@if (selectedRows != null && selectedRows.Any())
{
    <Button Danger Size="small" OnClick="@(e => { selectedRows = null; })">Clear</Button>

    @foreach (var selected in selectedRows)
    {
        <Tag @key="selected.Id" Closable OnClose="e=>RemoveSelection(selected.Id)">@selected.Id - @selected.Summary</Tag>
    }
}

<Button Type="@ButtonType.Primary" OnClick="()=> { _pageIndex--; }">Previous page</Button>
<Button Type="@ButtonType.Primary" OnClick="()=> { _pageIndex++; }">Next Page</Button>

@code {
    private WeatherForecast[] forecasts;
    IEnumerable<WeatherForecast> selectedRows;
    ITable table;
    int _pageIndex = 1;
    int _pageSize = 10;
    int _total = 0;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await GetForecastAsync(1, 50);
        _total = 50;
    }
    public class WeatherForecast
    {
        public int Id { get; set; }

        [DisplayName("Date")]
        public DateTime? Date { get; set; }

        [DisplayName("Temp. (C)")]
        public int TemperatureC { get; set; }

        [DisplayName("Summary")]
        public string Summary { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public bool Hot { get; set; }
    }
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };
    public void OnChange(QueryModel<WeatherForecast> queryModel)
    {
        Console.WriteLine(JsonSerializer.Serialize(queryModel));
    }
    public Task<WeatherForecast[]> GetForecastAsync(int pageIndex, int pageSize)
    {
        var rng = new Random();
        return Task.FromResult(Enumerable.Range((pageIndex - 1) * pageSize + 1, pageSize).Select(index =>
        {
            var temperatureC = rng.Next(-20, 55);
            return new WeatherForecast
                {
                    Id = index,
                    Date = DateTime.Now.AddDays(index),
                    TemperatureC = temperatureC,
                    Summary = Summaries[rng.Next(Summaries.Length)],
                    Hot = temperatureC > 30,
                };
        }).ToArray());
    }
    public void RemoveSelection(int id)
    {
        var selected = selectedRows.Where(x => x.Id != id);
        selectedRows = selected;
    }

    private void Delete(int id)
    {
        forecasts = forecasts.Where(x => x.Id != id).ToArray();
        _total = forecasts.Length;
    }
}

運行效果:

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

在這裡插入圖片描述

總結

暫無,下次再會

歡迎大家關註我的微信公眾號,一起進步,一起成長
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 安裝anaconda,進行數據標註 1.安裝前準備:下好安裝包和所需文件 https://www.aliyundrive.com/s/XyH2JQ5TjCz 提取碼: 3c2w 2.運行anaconda安裝包,解壓labelimg-master文件 3.把resources.py文件放到/label ...
  • 這篇文章更進一步,會結合電商前後臺API系統,把Go語言的知識點應用到商業項目中,讓大家結合實際的場景去理解,這樣應該對大家更有幫助! ...
  • 摘要:本文主要講解ACE去霧演算法、暗通道先驗去霧演算法以及霧化生成演算法。 本文分享自華為雲社區《[Python圖像處理] 三十.圖像預處理之圖像去霧詳解(ACE演算法和暗通道先驗去霧演算法)丨【拜托了,物聯網!】》,作者:eastmount 。 一.圖像去霧 隨著社會的發展,環境污染逐漸加劇,越來越多的城 ...
  • 前言 今天給大家介紹的是Python爬取Top100電影榜單數據保存csv文件,在這裡給需要的小伙伴們代碼,並且給出一點小心得。 首先是爬取之前應該儘可能偽裝成瀏覽器而不被識別出來是爬蟲,基本的是加請求頭,但是這樣的純文本數據爬取的人會很多,所以我們需要考慮更換代理IP和隨機更換請求頭的方式來對To ...
  • Git 分散式版本控制工具 Git最常用命令 | 命令名稱 | 作用 | | | | |git init|初始化本地庫| |git add ./文件名|把代碼添加到暫存區| |git commit -m '日誌'|把暫存區的文件添加到本地庫| |git push 鏈接 分支名|把本地庫的代碼載入到遠 ...
  • 一些開源項目包含了各種編程的最佳實踐供人參考學習和借鑒。但是也有一些開源項目雖然初衷是好的。但是包含了一些代碼的壞實踐。特別是對於一部分剛入行的大學生來說,可能會給到一些錯誤的示範。於是在此列舉一些項目中的壞實踐。 1.方法的用意判斷是與否卻返回字元串的“0”或者“1” 如果一個方法明確返回是與否這 ...
  • 體驗方法引用 通過方法引用,來使用已經存在的方案。 定義一個介面,裡面有一個抽象方法: public interface Printable { void printString(String s); } 定義一個測試類: public class PrintDemo { public static ...
  • IHostedService和BackgroundService 前言 平時寫代碼的時候,大家多多少少都會遇到定時任務的要求,今天介紹兩種定時任務的寫法,嚴格來說其實是一種。 相對來說比較粗糙,請多多指教。 方法一 IHostedService 簡介 ######## IHostedService ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...