使用 .NET MAUI 创建跨平台应用

内容纲要

介绍

.NET MAUI 是一个多平台框架,用于使用 C# 和 XAML 创建本机桌面和移动应用。.NET MAUI 是多平台应用程序用户界面的首字母缩写。使用 .NET MAUI,您可以设计可在 Windows、Android、iOS、iPadOS 和 macOS 上运行的移动应用程序。

假设您为一家全国性连锁杂货店工作。该连锁店希望通过移动和桌面应用程序扩展其忠诚度计划。新应用程序允许一键拨号到商店,并且当用户在商店时,还会推送有关特别优惠的通知。因此,该应用程序需要访问某些硬件功能。

您的任务是确定技术并构建概念验证。您将 .NET MAUI 确定为可能的技术选择。.NET MAUI 使您能够重用相同的应用程序代码和标记来创建用户界面 (UI),并轻松访问特定于硬件和平台的功能,如电话拨号程序和位置服务。此外,借助 .NET MAUI,您可以利用已与之合作的开发人员的 C# 技能。

学习目标

在本模块中,您将:

  • 了解 .NET MAUI 的基本体系结构
  • 创建 .NET MAUI 应用程序
  • 为 .NET MAUI 支持的平台定义共享 UI
  • 从 Visual Studio 部署 .NET MAUI 应用
  • 使用 .NET MAUI 访问平台 API

先决条件

  • 安装了 .NET MAUI 工作负载的 Visual Studio 2022
  • 熟悉 C# 和 .NET

描述 .NET MAUI 体系结构

用于跨平台应用开发的常见模式是从用户界面分解业务逻辑,然后为每个平台开发单独的用户界面和 UI 逻辑。虽然每种类型的设备的业务逻辑保持不变,但驱动应用和呈现数据的代码可能会有所不同。这种差异是由于设备提供的功能、API 和特性不同。以这种方式构建多平台应用不仅涉及处理单独的 SDK,还涉及完全不同的语言和工具集。

.NET MAUI 的目的是简化多平台应用开发。使用 .NET MAUI,您可以使用单个项目创建多平台应用程序,但如有必要,您可以添加特定于平台的源代码和资源。.NET MAUI 的主要目的是使您能够在单个代码库中实现尽可能多的应用程序逻辑和 UI 布局。

在本单元中,您将了解 .NET MAUI 的体系结构,以及构建 .NET MAUI 应用程序所需的工具。

什么是 .NET MAUI 技术堆栈?

.NET 提供了一系列特定于平台的框架来创建应用:.NET for Android、.NET for iOS(和 iPadOS)、.NET for Mac 和 WinUI 3(利用 Windows App SDK)。这些框架都可以访问相同的 .NET 6 基类库 (BCL)。此库提供用于创建和管理资源的功能,以及用于从代码中抽象出基础设备的详细信息的功能。BCL 依赖于 .NET 运行时来为代码提供执行环境。对于Android,iOS(和iPadOS)和macOS,该环境由Mono实现,Mono是.NET运行时的开源实现。在 Windows 上,Win32 执行相同的角色,只是它针对 Windows 平台进行了优化。

虽然 BCL 使在不同类型的设备上运行的应用程序能够共享通用的业务逻辑,但各种平台具有不同的方式来定义应用程序的用户界面。这些平台提供了不同的模型,用于指定用户界面的元素如何通信和相互操作。您可以使用特定于平台的相应框架(Android 版 .NET、iOS 版 .NET、Mac 版 .NET 或 WinUI 3)为每个平台分别制作 UI,但此方法要求您为每个单独的设备系列维护一个代码库。.NET MAUI 提供了一个单一框架,用于为移动和桌面应用程序构建 UI。使用此框架创建 UI(由下图中的箭头 1 指示),.NET MAUI 负责将其转换为相应的平台(箭头 2)。

有时可能需要实现特定于平台的功能。在这些情况下,您可以在特定于平台的框架中调用方法,如下图中的箭头 3 突出显示的那样,

.NET MAUI 如何工作?

.NET MAUI 从 UI 元素的逻辑描述中抽象出 UI 元素的实现。可以使用 XAML(一种基于 XML 的平台中立语言)来描述 UI。例如,下面的 XAML 片段显示了按钮控件的说明:

<Button Text="Click me"
        SemanticProperties.Hint="Counts the number of times you click"
        Clicked="OnCounterClicked"
        HorizontalOptions="Center" />

此示例定义按钮的标签(“单击我”),并指定在用户选择按钮时应运行名为“OnCounterClicked”的方法。其他属性可以修改按钮和文本的布局;在此示例中,文本以按钮为中心。语义属性为有视觉障碍的用户提供对辅助功能的支持。

.NET MAUI 始终为目标设备生成本机代码,因此您可以获得最佳性能。.NET MAUI 使用特定于每个平台和 UI 元素的“处理程序”来执行操作。例如,如果将应用的 iOS 作为目标,则 .NET MAUI 处理程序会将此代码映射到 iOS UIButton。如果你在Android上运行,你会得到一个Android AppCompatButton。这些处理程序通过 .NET MAUI 提供的特定于控件的接口(如按钮的 IButton)间接访问。

备注

如果你愿意,还可以使用 C# 代码动态创建 UI。此方法使您能够根据环境修改布局。例如,如果用户没有适当的授权级别,您可能不希望显示某些控件。

.NET MAUI 使访问常用控件(如按钮)变得容易。其他常见控件(如文本输入字段、标签和日期选取器)也同样简单。但是,单个控件不足以为创建丰富的应用程序提供良好的平台。NET MAUI还提供:

  • 用于设计页面的复杂布局引擎。
  • 用于创建丰富导航类型的多个页面类型,如抽屉。
  • 支持数据绑定,实现更优雅、更易于维护的开发模式。
  • 创建自定义处理程序以增强 UI 元素呈现方式的功能。
  • 直接访问本机 API,并抽象出与 UI 分开的移动和桌面应用的许多常见需求。基本功能库使应用能够访问 GPS、加速度计以及电池和网络状态等内容。通过此库,还可以获得数十种通用的移动开发传感器和服务。

.NET MAUI 开发的要求

若要创建 .NET MAUI 应用,你当前需要 Visual Studio 版本 17.3 预览版(Visual Studio 2022 的最新预览版),并安装以下工作负荷:

  • .NET 多平台应用 UI 开发

此外,如果要构建 .NET MAUI Blazor 应用程序,则必须安装 ASP.NET 和 Web 开发工作负荷。

在 Visual Studio 中创建 .NET MAUI 项目

安装并配置 .NET MAUI 工具后,可以使用 Visual Studio 生成 .NET MAUI 应用。

在本单元中,您将了解 Visual Studio 中 .NET MAUI 模板的结构。你将使用此模板创建跨平台移动和桌面应用。

如何开始

若要使用 Visual Studio 创建新的 .NET MAUI 项目,请在“创建新项目”对话框中,选择“.NET MAUI”项目类型,然后选择“.NET MAUI 应用”模板:

按照向导中的步骤命名项目并指定位置。

新创建的 .NET MAUI 项目包含如下所示的项:

.NET MAUI 项目结构和应用程序启动

项目内容包括以下各项:

文件定义应用程序将在 XAML 布局中使用的应用程序资源。默认资源位于文件夹中,为 .NET MAUI 的每个内置控件定义应用范围的颜色和默认样式。在这里,您将看到两个资源字典合并在一起:Resources

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyMauiApp"
             x:Class="MyMauiApp.App">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

App.xaml.cs.这是 App.xaml 文件的代码隐藏。此文件定义 App 类。此类在运行时表示应用程序。此类中的构造函数创建一个初始窗口并将其分配给属性;此属性确定应用程序开始运行时显示的页面。此外,此类还使您能够重写常见的与平台无关的应用程序生命周期事件处理程序。事件包括 、 和 。这些处理程序被定义为基类的成员。下面的代码显示了示例:MainPageOnStartOnResumeOnSleepApplication 

备注

您还可以在应用首次开始运行时覆盖特定于平台的生命周期事件。稍后将对此进行描述。

namespace MyMauiApp;

public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        MainPage = new AppShell();
    }

    protected override void OnStart()
    {
        base.OnStart();
    }

    protected override void OnResume()
    {
        base.OnResume();
    }

    protected override void OnSleep()
    {
        base.OnSleep();
    }
}

文件是 .NET MAUI 应用程序的主要结构。.NET MAUI 提供了许多对多平台应用有益的功能,包括应用样式、基于 URI 的导航和布局选项,包括应用程序根目录的浮出控件导航和选项卡。默认模板提供单个页面(或 ),该页面在应用启动时会膨胀。ShellShellContent

<?xml version="1.0" encoding="UTF-8" ?>
  <Shell
      x:Class="MyMauiApp.AppShell"
      xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
      xmlns:local="clr-namespace:MyMauiApp"
      Shell.FlyoutBehavior="Disabled">

      <ShellContent
          Title="Home"
          ContentTemplate="{DataTemplate local:MainPage}"
          Route="MainPage" />

  </Shell>

文件包含用户界面定义。MAUI 应用程序模板生成的示例应用程序包含三个标签、一个按钮和一个图像。控件使用括在 .该控件使控件垂直排列(在堆栈中),如果视图太大而无法在设备上显示,则 提供滚动条。目的是将此文件的内容替换为自己的 UI 布局。如果你有多页应用,还可以定义更多 XAML 页。VerticalStackLayoutScrollViewVerticalStackLayoutScrollView

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.MainPage">

    <ScrollView>
        <VerticalStackLayout 
            Spacing="25" 
            Padding="30,0" 
            VerticalOptions="Center">

            <Image
                Source="dotnet_bot.png"
                SemanticProperties.Description="Cute dot net bot waving hi to you!"
                HeightRequest="200"
                HorizontalOptions="Center" />

            <Label 
                Text="Hello, World!"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" />

            <Label 
                Text="Welcome to .NET Multi-platform App UI"
                SemanticProperties.HeadingLevel="Level2"
                SemanticProperties.Description="Welcome to dot net Multi platform App U I"
                FontSize="18"
                HorizontalOptions="Center" />

            <Button 
                x:Name="CounterBtn"
                Text="Click me"
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

MainPage.xaml.cs。这是页面的代码隐藏。在此文件中,您可以定义由页面上的控件触发的各种事件处理程序和其他操作的逻辑。示例代码为页面上的按钮的事件实现一个处理程序。该代码只是递增一个计数器变量,并在页面上的标签中显示结果。作为 MAUI Essentials 库的一部分提供的语义服务支持辅助功能。该类的静态方法指定当用户选择按钮时屏幕阅读器宣布的文本:ClickedAnnounceSemanticScreenReader

namespace MyMauiApp;

public partial class MainPage : ContentPage
{
    int count = 0;

    public MainPage()
    {
        InitializeComponent();
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;

        if (count == 1)
            CounterBtn.Text = $"Clicked {count} time";
        else
            CounterBtn.Text = $"Clicked {count} times";

        SemanticScreenReader.Announce(CounterBtn.Text);
    }
}

MauiProgram.cs。每个本机平台都有一个不同的起点来创建和初始化应用程序。您可以在项目中的“平台”文件夹下找到此代码。此代码是特定于平台的,但最后它调用静态类的方法。您可以使用该方法通过创建应用程序生成器对象来配置应用程序。至少需要指定哪个类描述您的应用程序。您可以使用应用程序构建器对象的泛型方法来执行此操作;类型参数指定应用程序类。应用程序生成器还提供了用于注册字体、为依赖关系注入配置服务、为控件注册自定义处理程序等任务的方法。以下代码显示了使用应用程序生成器注册字体的示例:CreateMauiAppMauiProgramCreateMauiAppUseMauiApp

namespace MyMauiApp;

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

        return builder.Build();
    }
}

平台。此文件夹包含特定于平台的初始化代码文件和资源。有适用于Android,iOS,MacCatalyst和Windows的文件夹。在运行时,应用以特定于平台的方式启动。大部分启动过程都是由 MAUI 库的内部结构抽象出来的,但这些文件夹中的代码文件提供了一种机制,用于挂接您自己的自定义初始化。重要的一点是,当初始化完成时,特定于平台的代码将调用该方法,然后该方法将创建并运行该对象,如前所述。例如,Android 文件夹中的 MainApplication.cs 文件、iOS 和 MacCatalyst 文件夹中的 AppDelegate.cs 文件以及 Windows 文件夹中的 App.xaml.cs 文件都包含以下替代:MauiProgram.CreateMauiAppApp

protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();

下图说明了 .NET MAUI 应用启动时的控制流:

项目资源

主项目的 .csproj 文件包括几个值得注意的部分。初始指定项目面向的平台框架,以及应用程序标题、ID、版本、显示版本和支持的操作系统等项。您可以根据需要修改以下属性:PropertyGroup

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

    <PropertyGroup>
		<TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
		<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
		<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
		<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
		<OutputType>Exe</OutputType>
		<RootNamespace>MyMauiApp</RootNamespace>
		<UseMaui>true</UseMaui>
		<SingleProject>true</SingleProject>
		<ImplicitUsings>enable</ImplicitUsings>

		<!-- Display name -->
		<ApplicationTitle>MyMauiApp</ApplicationTitle>

		<!-- App Identifier -->
		<ApplicationId>com.companyname.mymauiapp</ApplicationId>
		<ApplicationIdGuid>272B9ECE-E038-4E53-8553-E3C9EA05A5B2</ApplicationIdGuid>

		<!-- Versions -->
		<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
		<ApplicationVersion>1</ApplicationVersion>

		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
		<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
	</PropertyGroup>
    ...

</Project>

通过初始属性组下方的部分,你可以为应用加载时(出现第一个窗口)显示的初始屏幕指定图像和颜色。您还可以设置应用程序使用的字体、图像和资源的默认位置。ItemGroup

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

    ...

   <ItemGroup>
		<!-- App Icon -->
		<MauiIcon Include="Resources\appicon.svg" 
                  ForegroundFile="Resources\appiconfg.svg" 
                  Color="#512BD4" />

		<!-- Splash Screen -->
		<MauiSplashScreen Include="Resources\appiconfg.svg" 
                          Color="#512BD4" 
                          BaseSize="128,128" />

		<!-- Images -->
		<MauiImage Include="Resources\Images\*" />
		<MauiImage Update="Resources\Images\dotnet_bot.svg" 
                   BaseSize="168,208" />

		<!-- Custom Fonts -->
		<MauiFont Include="Resources\Fonts\*" />

		<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
		<MauiAsset Include="Resources\Raw\**" 
                   LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
	</ItemGroup>

    ...

</Project>

在 Visual Studio 的“解决方案资源管理器”窗口中,可以展开“资源”文件夹以查看这些项。您可以将应用程序所需的任何其他字体、图像和其他图形资源添加到此文件夹和子文件夹中。

当应用开始运行时,应将添加到 fonts 文件夹中的任何字体注册为应用构建器对象。回想一下,MauiProgram 类中的 CreateMauiApp 方法使用该方法执行此操作:ConfigureFonts

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

        ...
    }
}

在此示例中,该方法将字体与名称 相关联。在设置页面的 XAML 说明或应用程序资源字典中的项的格式时,可以指定此字体:AddFontOpenSansRegular

<Application ...">
    <Application.Resources>
        <ResourceDictionary>
            ...
            <Style TargetType="Button">
                ...
                <Setter Property="FontFamily" Value="OpenSansRegular" />
                ...
            </Style>

        </ResourceDictionary>
    </Application.Resources>
</Application>

使用 Android 中的“资源”文件夹,以及 Android 和 iOS 平台特定资源的“平台”文件夹下的 iOS 文件夹。

练习:创建您的第一个 .NET MAUI 应用程序

在本练习中,你将开始为连锁杂货店构建 .NET MAUI 应用。你将使用该模板生成默认应用,并在 Windows 和 Android 模拟器中运行它。您将在后面的练习中修改此应用程序,以自定义用户界面并添加杂货店连锁店应用程序所需的功能。

创建新项目

打开 Visual Studio 并创建一个新的解决方案。此操作将在 Visual Studio 中打开“新建项目”向导。 提示若要创建 .NET MAUI 应用,需要最新的 Visual Studio 2022 17.3 预览版。.NET MAUI 项目模板在早期版本的 Visual Studio 中不是可用的选项。

选择“MAUI”应用类型,选择“.NET MAUI 应用”模板,然后选择“下一步”。

“配置新项目”页上,将项目命名为“Phoneword”,并将其保存在所选位置。选择“创建”以创建应用。

检查解决方案结构

  1. 在“解决方案资源管理器”窗口中,展开“Phoneword”项目。展开“资源”文件夹及其子文件夹,展开 App.xaml 节点、AppShell.xaml 节点和 MainPage.xaml 节点。
  1. 在项目中,请注意以下事项:
    • 资源”文件夹包含所有平台使用的共享字体、图像和资源。
    • MauiProgram.cs文件包含配置应用程序的代码,并指定应使用 App 类来运行应用程序。
    • App.xaml.cs 文件(App 类的构造函数)创建 AppShell 类的新实例,然后该实例显示在应用程序窗口中。
    • AppShell.xaml 文件包含应用程序的主布局和主页的主页
    • MainPage.xaml 文件包含页面的布局。此布局包括标题为“单击我”的按钮的 XAML 代码,以及显示dotnet_bot.png文件的图像。还有另外两个标签。
    • MainPage.xaml.cs文件包含页面的应用程序逻辑。具体来说,MainPage 类包括一个名为 OnCounterClicked 的方法,该方法在用户点击“单击我”按钮时运行。

在 Windows 上构建和运行应用程序

在 Visual Studio 工具栏中,选择 Windows Machine 配置文件。从“框架”下拉列表框中的列表中选择 .net6.0-windows 框架。

“调试”菜单上,选择“启动调试”。此操作将在 Windows 上生成、部署和运行应用:

验证应用的 Windows 版本是否已启动。多次选择“单击我”按钮。按钮文本应随着计数随每次点击而递增而更新。

在“视图”菜单上,选择“解决方案资源管理器”。在“解决方案资源管理器”窗口中,展开 MainPage.xaml 节点并打开 MainPage.xaml.cs代码隐藏文件。

在 OnCounterClicked 方法中,更改递增 count 变量的语句,改为向此变量添加 5:

private void OnCounterClicked(object sender, EventArgs e)
 {
 	count+=5; // update this

 	if (count == 1)
 		CounterBtn.Text = $"Clicked {count} time";
 	else
 		CounterBtn.Text = $"Clicked {count} times";

 	SemanticScreenReader.Announce(CounterBtn.Text);
 }

在 Visual Studio 工具栏中,选择“热重装”按钮:

切换回应用,然后选择“单击我”按钮。验证计数现在是否递增了 5。 

备注

Visual Studio 的热重载功能使你能够在应用在调试模式下运行时修改代码。您无需停止应用即可看到更改。除了修改代码外,你还可以对页面的 XAML 标记进行更改,这些更改将在正在运行的应用中可见。

关闭应用并返回到 Visual Studio。

在安卓系统上构建和运行应用程序

在 Visual Studio 工具栏中,选择“Phoneword”项目。

“工具”菜单上,选择“Android”,然后选择“Android 设备管理器”。

“Android 设备管理器”窗口中,选择“+ 新建”。在“新建设备”窗口中,选择 Pixel 3a (+ Store) 基本设备,选择 API 30 操作系统,然后点击“创建”。等待下载各种库并配置设备。

创建设备后,返回到 Visual Studio。

在 Visual Studio 工具栏的“调试配置”下拉列表框中,选择“Android 模拟器”配置文件,然后选择pixel_3a api_30设备。这是您刚刚创建的设备。

使用pixel_3a api_30配置文件开始调试。此操作将在 Android 设备上生成、部署和运行应用。

当应用开始在模拟器上运行时,点击“单击我”按钮并检查应用是否以与在 Windows 上完全相同的方式运行。

返回到 Visual Studio 并停止调试。

将可视控件添加到 .NET MAUI 应用

使用 .NET MAUI 模板创建应用程序后,下一步是添加用户界面并实现初始 UI 逻辑。

在本单元中,您将了解有关 .NET MAUI 应用程序和导航结构的构建基块的详细信息。

.NET MAUI 项目中有哪些内容?

回顾一下,.NET MAUI 项目最初包含:

  • MauiProgram.cs文件,其中包含用于创建和配置 Application 对象的代码。
  • App.xaml 和 App.xaml.cs提供 UI 资源并为应用程序创建初始窗口的文件。
  • AppShell.xaml 和 AppShell.xaml.cs文件,用于指定应用程序的初始页面并处理用于导航路由的页面的注册。
  • MainPage.xaml 和 MainPage.xaml.cs文件,用于定义初始窗口中默认显示的页面的布局和 UI 逻辑。

可以根据需要向应用添加更多页面,还可以创建更多类来实现应用所需的业务逻辑。

.NET MAUI 项目还包含一组默认的应用程序资源(如图像、图标和字体)以及每个平台的默认引导代码。

应用程序类

该类将 .NET MAUI 应用程序作为一个整体表示。它从 继承一组默认行为。从前面的单元中回想一下,它是由每个平台的引导代码实例化和加载的类。反过来,类的构造函数通常会创建类的实例并将其分配给属性。正是此代码控制用户通过 中定义的内容看到的第一个屏幕。AppMicrosoft.Maui.Controls.ApplicationAppAppAppShellMainPageAppShell

App 类还包含:

  • 用于处理生命周期事件的方法,包括将应用发送到后台时(即,当它不再是前台应用时)。
  • 用于为应用程序创建新的方法。默认情况下,.NET MAUI 应用程序具有单个窗口,但您可以创建和启动其他窗口,这在桌面和平板电脑应用程序中非常有用。Windows

Shell

.NET 多平台应用 UI (.NET MAUI) Shell 通过提供大多数应用所需的基本功能来降低应用开发的复杂性,这些功能包括:

  • 用于描述应用的视觉层次结构的单个位置。
  • 常见的导航用户体验。
  • 一种基于 URI 的导航方案,允许导航到应用中的任何页面。
  • 集成的搜索处理程序。

在 .NET MAUI Shell 应用中,应用的可视层次结构在子类化 Shell 类的类中描述。此类可以由三个主要的分层对象组成:

  • 浮出控件项或选项卡栏。浮出控件表示浮出控件中的一个或多个项,应在应用的导航模式需要浮出控件时使用。TabBar 表示底部选项卡栏,当应用的导航模式以底部选项卡开头且不需要浮出控件时,应使用 TabBar。
  • Tab,表示分组内容,可通过底部选项卡导航。
  • ShellContent,表示每个选项卡的 ContentPage 对象。

这些对象不表示任何用户界面,而是应用视觉层次结构的组织。Shell 将获取这些对象并为内容生成导航用户界面。

页面

页面是 .NET MAUI 中 UI 层次结构的根。到目前为止,您看到的解决方案包括一个名为 的类。此类派生自 ,这是最简单和最常见的页面类型。内容页面仅显示其内容。.NET MAUI 还有其他几种内置页面类型,包括:ShellMainPageContentPage

  • TabbedPage:这是用于选项卡导航的根页面。选项卡式页面包含子页面对象;每个选项卡一个。
  • FlyoutPage:此页面使您能够实现大纲/细节样式的演示文稿。浮出控件页包含项的列表。选择项目时,将显示显示该项目详细信息的视图。

其他页面类型可用,主要用于在多屏幕应用中启用不同的导航模式。

视图

内容页通常显示视图。视图使您能够以特定方式检索和显示数据。内容页的默认视图是 ,它按原样显示项目。如果缩小视图,则在调整视图大小时,项目可能会从显示中消失。A 使您能够在滚动窗口中显示项目;如果缩小窗口,则可以上下滚动以显示项目。A 是一个可滚动视图,使用户能够在项目集合中轻扫。可以从命名数据源检索数据,并使用模板作为格式呈现每个项。还有许多其他类型的视图可用。ContentViewScrollViewCarouselViewCollectionView

控件和布局

视图可以包含单个控件,如按钮、标签或文本框。(严格来说,视图本身就是一个控件,因此一个视图可以包含另一个视图。但是,限制为单个控件的用户界面不是很有用,因此控件位于布局中。布局定义控件相对于彼此显示的规则。布局也是一个控件,因此您可以将其添加到视图中。如果您查看默认的 MainPage.xaml 文件,您将看到此页面/视图/布局/控件层次结构的实际运行情况。在此 XAML 代码中,该元素只是另一个控件,使你能够微调其他控件的布局。VerticalStackLayout

<ContentPage ...>
    <ScrollView ...>
        <VerticalStackLayout>
            <Image ... />
            <Label ... />
            <Label ... />
            <Button ... />
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

用于定义布局的一些常用控件包括:

  • VerticalStackLayout和 ,优化了堆栈布局,这些布局在从上到下或从左到右的堆栈中布置控件。也可用 ,它具有名为 的属性 ,可以将其设置为 或 。在平板电脑或手机上,通过在应用程序代码中修改此属性,您可以在用户旋转设备时调整显示:HorizontalStackLayoutStackLayoutStackOrientationHorizontalVertical
  • AbsoluteLayout,允许您设置控件的确切坐标。
  • FlexLayout,这与不同之处在于,它使您能够包装它所包含的子控件(如果它们不适合单个行或列)。此布局还提供了对齐和适应不同屏幕大小的选项。例如,当垂直排列时,FlexLayout 控件可以将其子控件向左、向右或居中对齐。水平对齐时,控件可以对齐以确保均匀的间距。您可以使用 内部的水平显示一系列可水平滚动的帧(每个帧本身可以是垂直排列的):StackLayoutFlexLayoutScrollViewFlexLayout
  • Grid,它根据我们设置的列和行位置布置其控件。您可以定义列和行大小以及跨度,因此网格布局不一定具有“棋盘外观”。

下图总结了这些常见布局类型的关键属性:

所有控件都有属性。可以使用 XAML 设置这些属性的初始值。在许多情况下,可以在应用程序的 C# 代码中修改这些属性。例如,处理默认 .NET MAUI 应用中“单击我”按钮事件的代码如下所示:Clicked

int count = 0;
private void OnCounterClicked(object sender, EventArgs e)
{
    count+=5;

    if (count == 1)
        CounterBtn.Text = $"Clicked {count} time";
    else
        CounterBtn.Text = $"Clicked {count} times";

    SemanticScreenReader.Announce(CounterBtn.Text);
}

此代码修改页中 CounterBtn 控件的属性。您甚至可以在代码中创建整个页面,视图和布局;您不必使用 XAML。例如,请考虑以下页面的 XAML 定义:Text

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Phoneword.MainPage">

    <ScrollView>
        <VerticalStackLayout>
            <Label Text="Current count: 0"
                Grid.Row="0"
                FontSize="18"
                FontAttributes="Bold"
                x:Name="CounterLabel"
                HorizontalOptions="Center" />

            <Button Text="Click me"
                Grid.Row="1"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

C# 中的等效代码如下所示:

public partial class TestPage : ContentPage
{
    int count = 0;
    
    // Named Label - declared as a member of the class
    Label counterLabel;

    public TestPage()
    {       
        var myScrollView = new ScrollView();

        var myStackLayout = new VerticalStackLayout();
        myScrollView.Content = myStackLayout;

        counterLabel = new Label
        {
            Text = "Current count: 0",
            FontSize = 18,
            FontAttributes = FontAttributes.Bold,
            HorizontalOptions = LayoutOptions.Center
        };
        myStackLayout.Children.Add(counterLabel);
        
        var myButton = new Button
        {
            Text = "Click me",
            HorizontalOptions = LayoutOptions.Center
        };
        myStackLayout.Children.Add(myButton);

        myButton.Clicked += OnCounterClicked;

        this.Content = myScrollView;
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;
        counterLabel.Text = $"Current count: {count}";

        SemanticScreenReader.Announce(counterLabel.Text);
    }
}

C# 代码更详细,但提供了额外的灵活性,使你能够动态调整 UI。

要在应用程序开始运行时显示此页面,请将 中的类设置为 main :TestPageAppShellShellContent

<ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:TestPage}"
        Route="TestPage" />

调整布局

在控件周围添加一点喘息空间很有用。每个控件都有一个布局遵循的属性。您可以将利润视为将其他人推开的控制。Margin

所有布局还具有一个属性,该属性可防止其任何子项靠近布局的边界。一种思考方式是,所有控件都在一个框中,并且该框具有填充的墙壁。Padding

另一个有用的空格设置是 或 的属性。这是布局的所有子项之间的空间。这是控件自身边距的累加,因此实际的空格将是边距加间距。SpacingVerticalStackLayoutHorizontalStackLayout

练习:创建电话号码转换器应用

在本练习中,你将为杂货店应用构造 UI,并实现此 UI 背后的逻辑。

你将生成一个 UI,该 UI 利用 .NET MAUI 的 UI 功能和 .NET MAUI 软件包来拨打电话。

该应用将允许用户在输入字段中键入文本,并将该文本转换为数字。它将使用电话键盘上显示的字母作为翻译的基础。例如,字母 cab 转换为 222,因为数字 2 包含所有三个字母 abc

你将继续使用在上一练习中创建的 Phoneword 解决方案。

将新的 C# 源文件添加到应用

  1. 在 Visual Studio 中打开 Phoneword 解决方案(如果尚未打开)。
  2. 在“解决方案资源管理器”窗口中,右键单击 Phoneword 项目,选择“添加”,然后选择“”。
  3. 在“添加新项”对话框中,将类文件命名为 PhonewordTranslator.cs,然后选择“添加”。

添加翻译逻辑

将类文件的内容替换为以下代码。该类中的静态方法会将数字从字母数字文本转换为常规数字电话号码。ToNumberPhonewordTranslator

using System.Text;

namespace Core;

public static class PhonewordTranslator
{
    public static string ToNumber(string raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
            return null;

        raw = raw.ToUpperInvariant();

        var newNumber = new StringBuilder();
        foreach (var c in raw)
        {
            if (" -0123456789".Contains(c))
                newNumber.Append(c);
            else
            {
                var result = TranslateToNumber(c);
                if (result != null)
                    newNumber.Append(result);
                // Bad character?
                else
                    return null;
            }
        }
        return newNumber.ToString();
    }

    static bool Contains(this string keyString, char c)
    {
        return keyString.IndexOf(c) >= 0;
    }

    static readonly string[] digits = {
        "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"
    };

    static int? TranslateToNumber(char c)
    {
        for (int i = 0; i < digits.Length; i++)
        {
            if (digits[i].Contains(c))
                return 2 + i;
        }
        return null;
    }
}

创建用户界面

在 Phoneword 项目中打开 MainPage.xaml 文件。

删除控件及其内容,只留下控件:ScrollViewContentPage

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="Phoneword.MainPage">

</ContentPage>

将垂直方向、间距为 15 个单位、填充为 20 个单位的控件添加到 ContentPage:VerticalStackLayout

<ContentPage ... >
    <VerticalStackLayout Spacing="15" Padding="20">

    </VerticalStackLayout>
</ContentPage>

将控件添加到堆栈布局:Label

<ContentPage ... >
    <VerticalStackLayout ...>
        <Label Text = "Enter a Phoneword"
               FontSize ="20"/>
    </VerticalStackLayout>
</ContentPage>

将控件添加到标签下方的 StackLayout。控件提供了一个文本框,用户可以在其中输入数据。在此代码中,该属性为控件指定一个名称。稍后将在应用程序的代码中引用此控件:EntryEntryx:Name

<ContentPage ... >
    <VerticalStackLayout ...>
        <Label .../>
        <Entry x:Name = "PhoneNumberText"
               Text = "1-555-NETMAUI" />
    </VerticalStackLayout>
</ContentPage>

将两个控件添加到垂直堆栈布局中,位于 Entry 控件之后。这两个按钮当前不执行任何操作,第二个按钮最初处于禁用状态。您将在下一个任务中添加代码来处理这两个按钮的事件:ButtonClicked

<ContentPage ... >
    <VerticalStackLayout ...>
        <Label .../>
        <Entry ... />
        <Button x:Name = "TranslateButton"
                Text = "Translate"
                Clicked = "OnTranslate"/>
        <Button x:Name = "CallButton"
                Text = "Call"
                IsEnabled = "False"
                Clicked = "OnCall"/>
    </VerticalStackLayout>
</ContentPage>

响应翻译按钮点击

在“解决方案资源管理器”窗口中,展开 MainPage.xaml 文件,然后打开 MainPage.xaml.cs代码隐藏文件。

在类中,删除变量和方法。该类应如下所示:MainPagecountOnCounterClicked

namespace Phoneword;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }
}

将字符串变量和以下方法添加到类中,在构造函数之后。该方法从控件的属性中检索电话号码,并将其传递给您之前创建的类的静态方法。translatedNumberOnTranslateMainPageOnTranslateTextEntryToNumberPhonewordTranslator

public class MainPage : ContentPage
{
    ...
    string translatedNumber;

    private void OnTranslate(object sender, EventArgs e)
    {
        string enteredNumber = PhoneNumberText.Text;
        translatedNumber = Core.PhonewordTranslator.ToNumber(enteredNumber);

        if (!string.IsNullOrEmpty(translatedNumber))
        {
            // TODO:
        }
        else
        {
            // TODO:
        }
    }
}

备注

您将在下一步中填写此代码中缺少的 TODO 位。

在该方法中,添加代码以更改“呼叫”按钮的属性,以便在成功转换电话号码时包括电话号码。您可以使用存储在已翻译号码字段中的值。此外,根据成功的翻译启用和禁用该按钮。例如,如果返回 null,则禁用该按钮,但如果成功,则启用它。OnTranslateTextTranslateNumber

private void OnTranslate(object sender, EventArgs e)
{
    string enteredNumber = PhoneNumberText.Text;
    translatedNumber = Core.PhonewordTranslator.ToNumber(enteredNumber);

    if (!string.IsNullOrEmpty(translatedNumber))
    {
        CallButton.IsEnabled = true;
        CallButton.Text = "Call " + translatedNumber;
    }
    else
    {
        CallButton.IsEnabled = false;
        CallButton.Text = "Call";
    }
}

为 CallButton 按钮创建事件方法

将事件处理方法添加到类的末尾。请注意,此方法将使用异步操作,因此请将其标记为:OnCallMainPageasync

public class MainPage : ContentPage
{

    ...
    async void OnCall(object sender, System.EventArgs e)
    {

    }
}

在该方法中,使用静态 Page.DisplayAlert 方法提示用户询问他们是否要拨打该号码。OnCall的参数是标题、消息和用于“接受”和“取消”按钮文本的两个字符串。它返回一个布尔值,指示是否按下了“接受”按钮以关闭对话框。DisplayAlert

async void OnCall(object sender, System.EventArgs e)
{
    if (await this.DisplayAlert(
        "Dial a Number",
        "Would you like to call " + translatedNumber + "?",
        "Yes",
        "No"))
    {
        // TODO: dial the phone
    }
}

测试应用程序

在 Visual Studio 工具栏中,选择 Windows 计算机配置文件并开始调试。

点击“翻译”按钮将默认文本转换为有效的电话号码。“呼叫”按钮上的标题应更改为“呼叫 1-555-6386284”:

轻点“呼叫”按钮。验证是否出现要求您确认操作的提示。选择“”。

返回到 Visual Studio 并停止调试。

拨打电话号码

在 MainPage.xaml.cs代码隐藏文件中,编辑 OnCall 方法并将 TODO 注释替换为以下块:try/catch

async void OnCall(object sender, System.EventArgs e)
{
    if (await this.DisplayAlert(
        "Dial a Number",
        "Would you like to call " + translatedNumber + "?",
        "Yes",
        "No"))
    {
        try
        {
            PhoneDialer.Open(translatedNumber);
        }
        catch (ArgumentNullException)
        {
            await DisplayAlert("Unable to dial", "Phone number was not valid.", "OK");
        }
        catch (FeatureNotSupportedException)
        {
            await DisplayAlert("Unable to dial", "Phone dialing not supported.", "OK");
        }
        catch (Exception)
        {
            // Other error has occurred.
            await DisplayAlert("Unable to dial", "Phone dialing failed.", "OK");
        }
    }
}

Microsoft.Maui.ApplicationModel.Communication 命名空间中的 PhoneDialer 类为 Windows、Android、iOS(和 iPadOS)和 macOS 平台提供了电话拨号功能(以及其他功能)的抽象。静态 Open 方法尝试使用电话拨号程序呼叫作为参数提供的号码。以下步骤演示如何更新 Android 应用程序清单以使 Android 能够使用电话拨号程序。Windows、iOS 和 MacCatalyst 应用程序遵循相同的一般原则,只是您在清单中指定了不同的操作系统相关功能。

在“解决方案资源管理器”窗口中,展开“平台”文件夹,展开 Android 文件夹,然后打开此文件夹中的 AndroidManifest.xml文件。

清单节点内,在此节点的现有内容之后添加以下 XML 代码段。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
    <queries>
        <intent>
            <action android:name="android.intent.action.DIAL" />
            <data android:scheme="tel"/>
        </intent>
    </queries>
</manifest>

保存文件。

在 Visual Studio 工具栏中,选择 Android Emulators/Pixel 3a - API 30(或类似)配置文件并开始调试。

当应用出现在模拟器中时,输入电话号码(或接受默认值),点击“翻译”,然后点击“呼叫”。

“拨打号码”警报中,选择“”。验证 Android 电话拨号器是否与您在应用中提供的号码一起显示。

返回到 Visual Studio 并停止调试。

在本练习中,你已使用页面和视图向应用程序添加了自定义 UI。您还添加了对使用 Android 中提供的特定于平台的 API 进行调用的支持。

总结

.NET MAUI 提供了一种创建可在 Windows、Android、macOS 和 iOS 设备上运行的跨平台应用的方法。

在本模块中,您将:

  • 学习了 .NET MAUI 的基本体系结构
  • 创建 .NET MAUI 应用程序
  • 为 .NET MAUI 支持的平台定义了共享 UI
  • 从 Visual Studio 部署了 .NET MAUI 应用程序
  • 使用 .NET MAUI 访问平台 API

你构建了一个在 Windows 和 Android 上运行的 .NET MAUI 应用。您还使用 .NET MAUI API 在两个平台上访问电话拨号程序。此应用充当简介中定义的要求的概念证明。具体来说,您打算创建一个具有以下特点的应用程序:

  • 在 Windows、Android 和其他受支持的设备上运行。
  • 最大限度地缩短开发时间。
  • 可以访问电话拨号器。

给TA打赏
共{{data.count}}人
人已打赏
.NET MAUI

MAUI使用Masa blazor组件库

2022-6-22 11:34:15

.NET MAUI

在 .NET MAUI 应用中使用 REST Web 服务

2022-6-24 19:26:33

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索