前言

WinUI 基于 Windows App SDK(就是头文件、库和工具集),使用 XAML (前端)和 C# 或 C++ (后端)创建出符合 Windows 用户期望 Fluent Design 外观和风格的应用。

介于这是聪明的巨硬又一次做出的愚蠢决定,以后很可能被抛弃,学习纯属娱乐。

本文为作者跟着官方教程制作一个便签App(WinUINotes)所记录的笔记(放心吧,你可以不用去看官方文档了,我把文档内容中译中了,方便想阅读本文的人理解,一起学习)

我已经跟着微软的教程成功新建并构建了项目,如果想要使用本文入门WinUI,需要先查看此教程快速入门:设置环境并创建 WinUI 3 project,通过此教程学习如何利用 Visual Studio 创建项目并构建项目。

其实学习 Visual Studio 并不是错误的选择,这个IDE自带开发环境,帮忙自动配置好,非常方便。

新建项目

打开 Visual Studio ,点击右侧的新建项目按钮

在这里搜索“winui”模板

可以发现有两个模板,简单来说,本文选择 Blank App, Packaged (WinUI 3 in Desktop)(WinUI空白应用(已打包))

  • 选择“Blank App, Packaged (WinUI 3 in Desktop)”单项目,应用和打包配置在一起。
  • 选择“Blank App, Packaged with Windows Application Packaging Project”双项目,应用和打包器分开。

打包器就是把复杂项目所有东西放到一起,统一签名和打包,生成 .msix 或 .msixbundle 安装包。

根据下方图片输入"WinUINotes",然后点击“ 创建

项目文件用途

点击菜单栏的"视图 - 解决方案管理器"

在解决方案资源管理器中可以看到 WinUI 项目的文件

Assets 文件夹:可以存放图片等资源文件

App.xaml 和 App.xaml.cs程序入口点(程序的启动逻辑),可以控制全局样式,启动逻辑等。

MainWindow.xaml 和 MainWindow.xaml.cs应用的主窗口(用户看到的第一个界面),用界面层(XAML 标记语言写)和 逻辑层(C#写)

Package.appxmanifest :Visual Studio 自动生成的清单文件,包项目配置文件(这个不重要)

修改界面

由于我之前写过 HTML ,所以这里用标签指代 <object> 方便理解。

打开 MainWindow.xaml

可以看到默认是这样的👇

<!--?xml version="1.0" encoding="utf-8"?-->
<window x:class="WinUINotes.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:WinUINotes" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:ignorable="d" title="WinUINotes">

    <window.systembackdrop>
        <micabackdrop>
    </micabackdrop></window.systembackdrop>

    <grid>

    </grid>
</window>

在 MainWindow.xaml ,可以控制窗口的内容,就是图中的这片区域:

窗口的顶部区域由 Windows 系统控制,它包括标题栏,标题控件(最小/最大/关闭按钮)、应用图标、标题和拖动区域。 它还包括包围窗口外侧的框架。

XAML入门语法

众所不周知,HTML实际就是面向对象编程,因为每个元素都可以添加修改属性(

在XAML里面,这也是一样的。既然没学过XML,那就直接理解XAML好了,不用看两者区别了。

这里与HTML及其相似,元素在XAML中叫做标记大多数标签是成对出现的,即一个开标签(如 <div>)和一个闭标签(如 </div>),自闭合也是可以用的,例如 <Canvas />,它们之间的内容就是元素(标记)的内容。

一个 XAML 元素标签就代表创建一个对象

<!-- 创建一个按钮对象 -->
<Button />

<!-- 创建一个文本框对象 -->
<TextBox />

可以给对象设置属性

XAML 也可以在标签里添加属性,例如简单的属性语法 <object attribute="value"> 也就是 属性名="值" ,如果属性过于复杂或者想要让格式更加整齐,可以使用复杂的属性语法。形式为 <类型.属性名> 嵌套在标签内:

<object>
    <object.property>
        value
    </object.property>
</object>

先不要在语法这里浪费时间与热情,等下边做边学,会讲到的awa

给界面添加一些内容

<Window.. > 标签的内容替换为以下代码:

<window.systembackdrop>
    <micabackdrop kind="Base">
</micabackdrop></window.systembackdrop>

<grid>
    <grid.rowdefinitions>
        <!-- Title Bar -->
        <rowdefinition height="Auto">
        <!-- App Content -->
        <rowdefinition height="*">     
    </rowdefinition></rowdefinition></grid.rowdefinitions>
    <titlebar x:name="AppTitleBar" title="WinUI Notes">
        <titlebar.iconsource>
            <fonticonsource glyph="">
        </fonticonsource></titlebar.iconsource>
    </titlebar>

    <!-- App content -->
    
</grid>

现在看起来是这样的:

按 Ctrl + S 或点击工具栏中的“保存”图标都可以保存文件。

保存后点击顶部的编译按钮

可以看到成功构建了窗口,窗口也显示了 TitleBar 元素,就是标题栏下方的图标和"WinUI Notes"文字

去掉自带的标题栏

其实,我们是想把这个原本的难看的系统标题栏替换掉,换成 TitleBar 元素内容。

需要在 MainWindow.xaml.cs 写一些代码来替换掉系统标题栏。

打开 MainWindow.xaml.cs .....?这个文件被隐藏了,需要在 MainWindow.xaml 代码编辑窗口中按下 F7 ,或者在编辑窗口右键并选择“查看代码”也是可以的。

其实在解决方案资源管理器展开 MainWindow.xaml 也可以看到 MainWindow.xaml.cs qwq

可以看到 MainWindow.xaml.cs 默认内容是这样的:

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

namespace WinUINotes
{
    /// <summary>
    /// An empty window that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

将 MainWindow 函数的代码替换为:

public MainWindow()
{
    this.InitializeComponent();

    // ↓ 添加了这些内容 ↓
    // 隐藏默认系统标题栏
    ExtendsContentIntoTitleBar = true;
    // 用 WinUI TitleBar 替换系统标题栏
    SetTitleBar(AppTitleBar);
    // ↑ 添加了这些内容 ↑
}

这里的 AppTitleBar 对应了之前在 MainWindow.xaml 写的 TitleBar 的参数。

按下 F5 可以直接调试编译()

保存并编译一下,可以看到成功替换了标题栏:

学到这里很厉害啦,可以休息一下了,等会再来学习后面的内容..

创建一个新的页面

现在,我们将创建一个页面,允许用户编辑笔记,然后编写代码以保存或删除笔记。

在解决方案资源管理器的项目名"WinUINotes"处右键点击,在菜单中选择"添加 - 新建项"

点击左下角的"显示所有模板"

选择"空白页"模板,修改名称为 NotePage.xaml,点击添加

给新的页面添加内容

打开 NotePage.xaml ,可以看到默认内容是这样的👇

<?xml version="1.0" encoding="utf-8"?>
<Page
    x:Class="WinUINotes.NotePage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WinUINotes"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>

    </Grid>
</Page>

 将 <Grid> ... </Grid> 元素中的内容替换为以下代码:

<Grid Padding="16" RowSpacing="8">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="400"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <TextBox x:Name="NoteEditor"
         AcceptsReturn="True"
         TextWrapping="Wrap"
         PlaceholderText="Enter your note"
         Header="New note"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         Width="400"
         Grid.Column="1"/>

    <StackPanel Orientation="Horizontal"
            HorizontalAlignment="Right"
            Spacing="4"
            Grid.Row="1" Grid.Column="1">
        <Button Content="Save" Style="{StaticResource AccentButtonStyle}"/>
        <Button Content="Delete"/>
    </StackPanel>
</Grid>

现在看起来应该是这样的:

回到 MainWindow.xaml,将 Frame 的内容修改为

<Frame x:Name="rootFrame" Grid.Row="1" SourcePageType="local:NotePage"/>

Frame 本身不显示内容,而是负责在多个 Page(页面)之间切换。

现在来编译一下看看效果 :)

现在我们可以来研究一下这个页面的构成了。

XAML基础

回顾我们在 MainWindow.xaml 写的代码,可以注意到我们把控件写在了 Grid (网格)元素中:

  • Grid网格(布局容器,用于按行和列排列子元素)
  • RowDefinition行定义(定义网格中某一行的属性,如高度)

可以看到,我们把网格的每一行的属性写在了 <Grid.RowDefinitions> 元素里面,有两个 <RowDefinitions>,也就是创建了2个行。

行是从0开始数的,在子元素中通过 Grid.Row="哪一行" 属性来指定该元素所处的行。可以看看下面代码的注释,方便理解~

    <Grid>
        <!-- Grid.RowDefinitions 定义网格的行 -->
        <Grid.RowDefinitions>
            <!-- 标题栏行:高度自适应(Auto) -->
            <!-- 从上往下定义,现在规定第0行的高度 -->
            <RowDefinition Height="Auto" />
            <!-- 内容行:占满剩余的所有空间(*) -->
            <!-- 从上往下定义,现在规定第1行的高度 -->
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        
        <!-- 以下为子元素(也就是内容) -->
        <!-- 现在是第一个子元素,没写Grid.Row属性默认放在第0行 -->
        <TitleBar x:Name="AppTitleBar"
              Title="WinUI Notes">
            <TitleBar.IconSource>
                <FontIconSource Glyph="&#xF4AA;"/>
            </TitleBar.IconSource>
        </TitleBar>

        <!-- 内容 -->
        <!-- 现在是第二个子元素,Grid.Row属性为1放在第1行 -->
        <Frame x:Name="rootFrame" Grid.Row="1" SourcePageType="local:NotePage"/>
    </Grid>

现在再查看 NotePage.xaml 中的代码,同样可以看到我们把所有子元素写在了 Grid (网格)元素中:

  • RowDefinition行定义(定义网格中某一行的属性,如高度)
  • Grid.ColumnDefinitions列定义(定义网格中某一列的属性,如宽度)

可以数一下,有两个 <RowDefinitions> ,三个 <ColumnDefinitions>,所以这个网格规定了 2 行 3 列。

我们给 <RowDefinitions> (行定义)设置了 Height="*" (高度="占满剩余空间")以及 Height="Auto" (高度="高度自适应")属性,给 <ColumnDefinitions> (列定义)设置了 Width="*" (宽度="占满剩余空间")以及 Width="400" (宽度="400")属性。

你可能注意到了,宽度="400"是什么...?

其实这里代表宽度为 400epx,在 XAML 不用 px(像素)来表达布局尺寸和间距,而是使用 epx (有效像素),这个 epx 单位是一个虚拟的度量单位(而 px 是实际存在的物理的像素单位),它会自适应调整间距,只要你设置一个合适的值即可。

通过这张图也可以清晰看到网格的行列,以及每个格子。(顶部的一行是在 MainWindow.xaml 规定的,内容区域的两行是在 NotePage.xaml 规定的)

我们继续研究 NotePage.xaml 。

 重要(如果你可以理解)

XAML 命名空间 (xmlns) 映射 是与 C# using 语句对应的 XAML 映射。 在应用项目的 XAML 页面中,为您映射了前缀 local:xmlns:local="using:WinUINotes")。 它被映射到引用为包含 x:Class 属性和代码而创建的同一命名空间,以便涵盖包括 App.xaml 在内的所有 XAML 文件。 只要在此同一命名空间中定义要在 XAML 中使用的任何自定义类,就可以使用 local: 前缀来引用 XAML 中的自定义类型。

剩下的内容以后再更细。。。