编程 Windows 10 桌面:UWP 焦点(第 15 章)
UWP 入门(从 WinForms 转向)第 15 章:最后一章深入探讨各种 XAML Windows 控件,展示示例并处理简单的布局。
- AllControls_v001 - 22 KB
- AllControls_v002 - 22.1 KB
- AllControls_v003 - 22.5 KB
- AllControls_v004 - 22.6 KB
- AllControls_v005 - 22.8 KB
- AllControls_v006 - 22.8 KB
- AllControls_v007 - 23.5 KB
引言
请记住,您也可以在我的 Github 仓库 https://github.com/raddevus/Win10UWP 获取所有源代码。
按功能分类的控件
我们将查看的控件列表将由 这份微软文档 指导,该文档按功能列出了 UWP 控件。
我这样做有两个原因
- 	了解如何设置和使用 XAML 控件是我们在 UWP 范式下开发应用程序的重要组成部分 
- 	虽然您可以在网上找到一些示例,但我发现了两个主要问题 
- XAML 示例很少(如果有的话)提供快照。这很烦人。在《通过 UWP 编程 Windows 10》中,您将获得源代码和快照,它们将显示预期结果。这样,以后当您想将源代码用作起点时,您就会知道源代码最初会如何渲染。
- 	当前的微软示例(Github 上有很多)无法使用当前版本的 Visual Studio 2017 构建。他们用于源代码的项目样式不再适用,这真的很烦人。 
背景
您可以阅读此前的所有章节
- 编程 Windows 10:UWP 焦点(1/N)[^]
- 编程 Windows 10:UWP 焦点(2/N)[^]
- 编程 Windows 10:UWP 焦点(3/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(4/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(5/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(6/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(7/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(8/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(9/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(10/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(11/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(12/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(13/N)[^]
- 编程 Windows 10 桌面:UWP 焦点(14/N)[^]
 亚马逊有印刷版或 Kindle 版
亚马逊有印刷版或 Kindle 版
您也可以在亚马逊上购买本书的前 8 章的印刷版或 Kindle 版
通过 UWP 编程 Windows 10:学习为桌面编写通用 Windows 应用程序 (Program Win10) [^]
此外,本书的第二部分(第 9-15 章)也将很快推出印刷版和 Kindle 版。
创建 AllControls 应用程序
首先,我将创建一个名为 AllControls 的新通用 Windows 应用程序。

我们已经看到了页面顶部的 CommandBar,所以我们也在底部添加一个,看看如何实现。
让我们在页面上,紧挨着项目模板放置的 <Grid> 元素下方,添加以下 XAML。
<Page.BottomAppBar>
  <CommandBar>
     <AppBarButton Icon="Like" Label="Like" />
     <AppBarButton Icon="Message" Label="Message" />
     <AppBarButton Icon="OpenFile" Label="Open File" />
     <AppBarToggleButton Icon="Accept" Label="Accept" />
  </CommandBar>
</Page.BottomAppBar>

底部命令栏
您可以看到一个 CommandBar 已添加到我们页面的底部。
我添加了一些随机按钮,包括一个点赞、一个消息、一个打开文件,最后是一个接受按钮。
请注意,接受按钮(打钩)是一个 AppBarToggleButton,因此用户可以点击该按钮表示它被选中或开启,再次点击表示该项目未选中或关闭。
构建、运行并尝试切换按钮
如果您已经进行了这些更改,请继续构建应用程序,以便我们尝试切换按钮。
当然,其他按钮没有设置事件处理程序,所以它们不会做任何事情。
如果您运行应用程序,AcceptToggleButton 将以与其他按钮相同的颜色(在我们的示例中为灰色)开始,但如果您点击切换按钮一次,您会看到它变成高亮选中的颜色并保持不变。

让我们添加一个事件处理程序和一些代码,以便当“接受”按钮被选中时,会弹出一个 ContentDialog。请注意,这不是 Click 事件。相反,此操作只会在“接受”按钮被 Checked 时发生,而不是在它未被选中时发生。
首先,为 Checked 事件添加事件处理程序。

让我们添加方法 DisplayToggleDialog(),您可以将其用作您以后项目的基本 ContentDialog 模板。
ContentDialog toggleDialog = new ContentDialog
{
     Title = "You've Toggled the Item",
     Content = "The Item is On",
     PrimaryButtonText = "Ok"
  };
  ContentDialogResult result = await toggleDialog.ShowAsync();
}
在这种情况下,对话框只是一个警告,并且只有一个按钮,所以我们根本不会得到返回值。
当接受按钮被选中时,我们调用该方法。
DisplayToggleDialog();

构建并运行
获取 AllControls_v001 以获取我们目前所做的所有更改。
当您运行应用程序并单击接受按钮时,ContentDialog 将弹出。

试用浮出控件
现在,当用户点击 OpenFile CommandBar 按钮时,让我们添加一个 Flyout。
Flyout 有点不同,它是在 XAML 本身中定义的。ContentDialogs 也可以用这种方式定义,但这是在 UWP 中描述 Flyout 的唯一方式。
要将 <Flyout> 添加到我们的按钮,我们实际上是将以下 XAML 作为嵌套元素添加到 OpenFile 按钮上。
<FlyoutBase.AttachedFlyout>
  <Flyout>
     <TextBlock Text="This is some text in a flyout."  />
  </Flyout>        
</FlyoutBase.AttachedFlyout>
但是,如您所知,大多数情况下,当我们在 Visual Studio 设计编辑器中添加元素时,它会将其设置为单行元素。我们只需要首先将我们的 AppBarButton 更改为双标签元素。它目前看起来像
<AppBarButton Icon="OpenFile" Label="Open File" />
我们需要删除那个尾部的斜杠 (/),创建一个结束标签,并将我们的 Flyout 代码添加到标签之间
<AppBarButton Icon="OpenFile" Label="Open File" >
  <FlyoutBase.AttachedFlyout>
     <Flyout>
        <TextBlock Text="This is some text in a flyout."  />
     </Flyout>        
  </FlyoutBase.AttachedFlyout>
</AppBarButton>
之后,我们只需要为该按钮添加 Click 事件,以便在点击按钮时,我们可以使 Flyout 出现。

显示浮出控件的代码
当按钮被点击时显示 Flyout 的代码非常简单。
切换到 MainPage.xaml.cs 并导航到 AppBarButton_Click 事件,然后添加以下代码
FlyoutBase.ShowAttachedFlyout((FrameworkElement)sender);
我们已将传入参数 sender 转换为 FrameworkElement,它是一个 AppBarButton。ShowAttachedFlyout 方法知道该按钮有一个关联的 Flyout,它是我们刚刚添加的嵌套元素。因此,当点击该按钮时,浮出控件就会出现。
再次构建并运行
您必须亲眼所见才能相信。获取 AllControls_v002 代码并试一试。启动应用程序,点击 OpenFile 按钮,您就会看到消息。

轻量级关闭
Flyout 被认为是轻量级关闭,这意味着您只需单击应用程序中的任何其他位置,消息就会消失。
其他零碎东西
让我们尝试一个 Slider 控件。它非常容易使用。
Slider控件
我将在我们的空 Grid 中添加以下 XAML。
       <StackPanel>
       <Slider x:Name="rectSizeSlider" Header="RectangleSize" Minimum="1"
               Maximum="100"
               Value="20" Width="200" />
       <Rectangle x:Name="DarkBlueRect" Fill="DarkBlue" Width="10" Height="10"/>
       </StackPanel>
它添加了一个 StackPanel、一个 Slider 和一个 Rectangle。
我添加 StackPanel 只是为了让这两个控件彼此靠近。
当您添加 XAML 时,Visual Studio 会在 DesignView 中为您渲染控件。

当然,您会看到基本的滑块和小的(10x10)矩形。我添加这个是为了让您在应用程序运行时实时看到滑块执行操作。
您可以看到我为 Slider 控件设置了一些属性(XAML 属性)。
- Name:- rectSizeSlider
- Header:- RectangleSize将作为控件上方的文本标签显示
- Minimum: 1,是滑块可以设置的最小值
- Maximum: 100,是滑块可以设置的最大值
- Value: 20 代表- Slider的当前- Value
- Width: 设置- Slider控件的宽度。
现在,我们需要添加 ValueChanged 事件处理程序。我确信您知道如何操作,所以现在就让您来完成。
一旦您添加了 rectSizeSlider_ValueChanged 事件处理程序并进入 MainPage.xaml.cs,我们就可以添加代码,该代码将在用户每次移动 Slider 时调整我们的矩形大小。
我们只需要添加一行代码来完成我们的简单工作。
DarkBlueRect.Width = DarkBlueRect.Height = rectSizeSlider.Value;
然而,rectSizeSlider_ValueChanged 事件处理程序会在 DarkBlueRect 实例化之前被触发,所以我们需要将该行代码包装在一个检查中,以确保我们不会尝试设置 null 对象的属性值。否则,应用程序会在启动时崩溃。
if (DarkBlueRect != null)
{
  DarkBlueRect.Width = DarkBlueRect.Height = rectSizeSlider.Value;
}

由于我们将 Rectangle 命名为 DarkBlueRect,我们可以在代码中轻松引用它。
这行代码表示获取 rectSizeSlider.Value 并将 DarkBlueRect.Height 设置为该值,然后将 DarkBlueRect.Width 设置为 DarkBlueRect.Height。这使得所有值都相同。由于 DarkBlueRect 将具有相同的 Width 和 Height,它将始终是一个正方形。
构建、运行、尝试滑块
继续构建代码并来回滑动 Slider。如果您需要代码,请获取 AllControls_v003 项目。您会看到矩形大小随着 Slider 上的值而改变。

您可以看到我在 Slider 设置为 79 时拍摄了一张快照。当然,Rectangle 的大小也更大了,因为它的 Height 和 Width 属性也已设置为 79。
让我们在 Slider 和 Rectangle 周围添加一个边框,以便我们了解如何使用 Border 控件。
使用边框控件
一个 border 只能有一个子项,所以我们必须将 Border 放置在 StackPanel(其中包含另外两个控件)的周围。
当您首次添加 Border 元素时,您在 DesignView 中将完全看不到任何内容。这是添加 XAML 元素的挑战之一。
您在 DesignView 中看不到任何内容的原因是 Border 没有 BorderBrush(颜色)和 BorderThickness(默认为 0)。
BorderBrush 和 BorderThickness
但是,如果您添加 BorderBrush(颜色)和 BorderThickness,您将在 DesignView 中看到 Border 元素出现。
当我们用以下 Border XAML 包裹 StackPanel 时,您会看到一个橙色边框出现。
<Border BorderBrush="Orange" BorderThickness="5"></Border>

控件的边界在 XAML 中不那么明显
这也暴露出我们 XAML 中将不断遇到的另一个问题/挑战。每次我们都必须创建布局,因为正如您所见,将 StackPanel 直接添加到 Grid 导致 Grid 扩展并占据整个页面。这并不理想,而且很容易忘记它正在发生,因为我们通常不会在控件上设置边框来快速了解这些边界在哪里。
当然,我们可以通过创建 Grid.RowDefinition 和 Grid.ColumnDefinition 并将我们的 Border 元素分配给特定的 Row 和 Column 来解决这个问题。
现在让我们来做,这样我就可以向您展示如何轻松地使您的网格行和列具有相等的大小。
等大小的网格行和列
在我们的例子中,我们希望将页面分成四个相等大小的区域。
我们可以通过向我们的 Grid 添加以下 Grid.RowDefinitions 和 Grid.ColumnDefinitions 来非常简单地做到这一点。
<Grid.RowDefinitions>
  <RowDefinition Height="*"/>
  <RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
  <ColumnDefinition Width="*"/>
  <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
一旦您将这些定义添加到您的 XAML,您就会看到 Border 收缩到页面的左上角区域。

如果别无他法,边框可以帮助设计师可视化边界。但当然,它们也可以帮助用户将多个控件视为一个组,这些控件将协同工作。
获取代码
您可以获取 AllControls_v004 并运行它,现在可以看到一个更专注的界面。这将允许我们创建页面部分,当我们继续通过示例代码时,我们将在此处添加其他控件。

添加另一个边框
让我们添加另一个包含 StackPanel 的 Border,但我们会将 BorderBrush 更改为 Purple,以便您可以区分我们添加的第一个和这一个。
在另一个 Border 元素之后直接添加以下 XAML
<Border BorderBrush="Purple" BorderThickness="5" >
  <StackPanel>
  </StackPanel>
</Border>
当您这样做时,DesignView 的实际作用可能并不直观。

它将新的 Border 元素直接堆叠在旧的上面。这是因为我们没有为任何一个 Border 元素分配 Grid.Row 和 Grid.Column,所以它们会自动被添加为 Grid.Row = “0” 和 Grid.Column = “0”。
让我们更改我们新添加的 Border,使其明确为 Grid.Row=”1” 和 Grid.Column=”0”。
一旦您做出该更改,它可能看起来更符合您的预期。

我知道我们过去曾提及此事,但我重申这一点,因为这就是 UWP 开发者的生活。我也不确定我对它的看法,但我想它与 HTML/CSS/JavaScript/Angular/TypeScript/Microsoft ASP.NET MVC 开发者的生活并没有那么大的不同。然而,它与 WinForms 开发者的生活大相径庭。
现在,让我们将以下项目添加到新的 StackPanel 中
- ToggleSwitch
- ToggleButton
- 文本框
- PasswordBox
- 文本框
要添加这些,我只需在 Toolbox(通常在 Visual Studio 的 DesignView 模式下位于左侧)中抓取每一个,然后将其拖放到 DesignView 中紫色边框内的 Page 上。

当您拖放控件时,Visual Studio 会自动为其中一些控件添加一些属性,因此我们第二个 StackPanel 的 XAML 现在看起来像
<StackPanel>
  <ToggleSwitch Header="ToggleSwitch" HorizontalAlignment="Stretch"
      VerticalAlignment="Stretch"/>
         <ToggleButton Content="ToggleButton" HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"/>
         <TextBox TextWrapping="Wrap" Text="TextBox"/>
         <PasswordBox  />
         <TextBox TextWrapping="Wrap" Text="TextBox"/>
   </StackPanel>

Content 和 Header 属性
请注意,通常如果 Control 包含文本或其中包含文本(例如带有文本的 Button),那么您将 Content 属性设置为您希望在 Button 上看到的文本。
当然,TextBox 打破了这个模式,它的属性名为 Text,但这有点不同,因为用户可以更改该文本,而 Content 不能被用户更改。
Header 属性
此外,请注意,其他控件(Slider、ToggleSwitch 和 TextBox)都有一个名为 Header 的属性,它会在控件上方显示一些文本,其功能类似于标签,指示控件的用途。在 WinForms 开发时代,我们必须为每个控件添加单独的 Label 控件,以便在用户界面中添加这些提示。
修改和添加标题
现在让我们更改标题,使它们更具意义,并为 TextBox 添加一些标题,以便用户对它们的用途有一个初步的了解。
这是我们第二个 StackPanel 的更新 XAML
<StackPanel>
  <ToggleSwitch Header="Contact Me" HorizontalAlignment="Stretch"
     VerticalAlignment="Stretch"/>
  <ToggleButton Content="Alert Me" HorizontalAlignment="Stretch"
     VerticalAlignment="Stretch"/>
  <TextBox Header="First name:" TextWrapping="Wrap" Text="TextBox"/>
  <PasswordBox Header="Password:" />
  <TextBox Header="Last name:" TextWrapping="Wrap" Text="TextBox"/>
</StackPanel>

进行这些更改后,我们来构建并运行,这样我们就可以查看不同的控件,我将向您展示此布局出现的一个问题以及如何解决它。
获取代码
如果您需要最新代码,请访问 AllControls_v005。
当我运行代码时,应用程序会记住我之前运行时的尺寸,并且它非常小。

并非所有控件都可见
问题在于,现在用户无法访问某些控件,因为它们呈现在屏幕之外的位置。
我可以最大化应用程序再次看到它们,但有更好的方法来解决这个问题,所以让我们先这样做。
使用 ScrollViewer
解决这个问题就像将我们的 StackPanel 包裹在一个 ScrollViewer 中一样简单。

一旦我们这样做并再次运行,您会看到当光标移入该 StackPanel 时,一个垂直滚动条将自动出现。这是一个非常好的简单解决方案。

这是正在使用的控件的视图。
上一张图片显示的是默认未选中的 ToggleSwitch 和 ToggleButton,下一张图片显示它们都已选中。

我已将 TextBoxes 中的文本更改为包含假名和姓氏,并且我在密码框中输入了一个您无法看到的密码。
密码框右侧的眼睛图标表示用户可以单击(并按住)以查看密码框中的值。
当点击时,它看起来像下面这样

最新代码
为了确保您拥有最新代码,如果需要,您可以获取 AllControls_v006(其中包含 ScrollViewer)。
另一个边框和更多控件
让我们添加另一个 Border 并将其分配到 Grid.Row 零和 Grid.Column 一,并将其颜色设为绿色。
由于我们将添加一些控件,我们也将添加一个带有 StackPanel 的 ScrollViewer。
       <Border BorderBrush="Green" BorderThickness="5" 
       Grid.Row="0" Grid.Column="1">
           <ScrollViewer>
               <StackPanel>              
               </StackPanel>
           </ScrollViewer>
       </Border>
让我们添加以下控件
- ComboBox
- 两个 CheckBox
- 三个 RadioButton
- 文本框
我将做一些工作来调整 XAML 布局,所以我将直接提供最终的 XAML。这些控件还将包含几个事件处理程序,以确保当您进行 RadioButton 选择或 ComboBox 选择时,一些代码会运行。
<Border BorderBrush="Green" BorderThickness="5" Grid.Row="0" Grid.Column="1">
           <ScrollViewer>
               <StackPanel>
                   <ComboBox x:Name="PriceComboBox" Header="Price" Width="150"
                             HorizontalAlignment="Left"  VerticalAlignment="Top"
                             Margin="20" SelectionChanged="ComboBox_SelectionChanged">
                       <x:String>4.39</x:String>
                       <x:String>5.68</x:String>
                       <x:String>7.02</x:String>
                   </ComboBox>
                   <StackPanel >
                       <CheckBox x:Name="AddFeeCheckBox"  Margin="20 3 3 3" 
                            Content="Add Fee ($5.00)" Click="AddFeeCheckBox_Click" />
                       <CheckBox x:Name="AddTaxesCheckBox" Margin="20 3 3 3" 
                           Content="Include Taxes (x 7.5%)" 
                           Click="AddTaxesCheckBox_Click" />
                   </StackPanel>
                   <StackPanel>
                       <RadioButton x:Name="AlignLeftRadio" IsChecked="True" 
                           Content="Align Left" Margin="20 3 3 3" 
                           Checked="RadioButton_Checked"/>
                       <RadioButton x:Name="AlignCenterRadio" 
                           Content="Align Center" Margin="20 3 3 3" 
                           Checked="RadioButton_Checked"/>
                       <RadioButton x:Name="AlignRightRadio" 
                           Content="Align Right" Margin="20 3 3 3" 
                           Checked="RadioButton_Checked"/>
                   </StackPanel>
                   <StackPanel>
                       <TextBox x:Name="FinalCostTextBox" PlaceholderText="final cost" 
                            HorizontalAlignment="Left"  Width="250"/>
                   </StackPanel>
               </StackPanel>
           </ScrollViewer>
       </Border>

这里没有太多新内容,但我确实想指出 TextBox 上的 PlaceholderText 属性。它提供了一种方法,可以向用户提示 TextBox 正在寻找哪种类型的值。在这种情况下,我们将其设置为“最终成本”。当 TextBox 不包含任何文本时,会显示该文本。但是,它不是 TextBox 的 Text 值,它只是一个 UI 元素,没有值。
我基本上添加了四个事件处理程序,以便当点击复选框或单选按钮时,就会发生一些事情。此外,当用户从 ComboBox 中选择一个值时,FinalCostTextBox 将被设置为一个值。
所有代码都非常简单,所以我将让您自行查看每个事件处理程序,以了解其功能。
以下是应用程序运行时的情况。
获取最终代码
您可以获取 AllControls 的最终代码,即 AllControls_v007。

大量材料
再次,我们涵盖了大量材料,我尝试为您提供一些基本示例,以帮助您开始 XAML 布局。
您应该能够利用您在这里学到的知识,并通过一些研究,现在构建您想要的任何类型的 UWP 应用程序。
旅程结束
我希望您享受了 UWP 开发之旅,并希望您能回来阅读我的下一本书,那本书应该类似,因为我将深入研究使用 Xamarin 的移动应用程序开发世界。
敬请期待,感谢阅读我的书!
历史
- 2017年12月29日:首次发布




