65.9K
CodeProject 正在变化。 阅读更多。
Home

编程 Windows 10 桌面:UWP 焦点(14/N)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (8投票s)

2017 年 12 月 22 日

CPOL

26分钟阅读

viewsIcon

11646

downloadIcon

929

UWP 入门(告别 WinForms)第 14 章:添加通过弹出确认对话框删除条目的功能,添加 AccessKeys(键盘快捷键),并修复我们 DailyJournal 应用中的一些错误。

引言

我们正在继续开发我们的 DailyJournal 应用。

我们还需要对 DailyJournal 应用进行一些收尾工作,以使其更易于使用,代码更简洁。

本章工作清单

我们需要

  1. 修复尝试修改之前创建的条目时,数据会保存到新条目中的错误。

  2. 添加删除条目(及其关联的条目文件)的功能

  3. 修复 EntriesListView,使其在添加新条目(首次保存)时刷新。目前,ListView 仅在您更改选定日期时刷新。

  4. 为我们的功能添加快捷键

在对应用程序进行这些最后润色之后,我们将利用本书的其余部分来构建一个应用程序,该应用程序将检查您可以添加到 UWA(通用 Windows 应用程序)的所有可用控件(通过 XAML),以便您更好地了解哪些是可用(或不可用)的,并为这些控件提供基本的入门 XAML。

背景

您可以阅读此前的所有章节

编程 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)[^]

亚马逊有印刷版或 Kindle 版

您也可以在亚马逊上购买本书的前 8 章打印版或 Kindle 版。

通过 UWP 编程 Windows 10:学习为桌面编程通用 Windows 应用程序 (Program Win10) [^]

 

 

 

 

 

我们先来修复我们的 bug。

入门

如果您没有之前的代码,您可以获取 DailyJournal_v028,这是我们在第 12 和 13 章中留下的代码。此外,请记住所有源代码都可以在 Github 上获得:https://github.com/raddevus/Win10UWP

错误修复

我最近注意到,如果您点击之前创建的条目并尝试编辑并保存,您实际上会在同一天创建一个新条目,其中包含您输入的新数据。

经过进一步调试,我发现 MainPage.xaml.cs 中的代码在将现有条目列表添加到 currentJournalEntries 集合时是错误的。

错误的代码行在 LoadEntriesByDate() 方法中

currentJournalEntries.Add(
  new JournalEntry(reb,
  Path.Combine(appHomeFolder.Path),
  YMDDate, entryText));


 

在构建新的 JournalEntry 时,此代码行未发送现有文件名的值。

这是上一版本 (DailyJournal_v028) 代码中的疏忽。

我们现在来修复它。

解决问题很容易

我们只需要将 filename 添加到 JournalEntry 构造函数中,这样它就可以正确设置,而不是为 JournalEntry 生成新的文件名。显然,由于文件已经存在,我们不需要生成新的文件名。文件名已在该代码段中使用,因此它可以将文件数据加载到 RichEditBox 中,因此我们可以直接获取该代码并将其添加到 JournalEntry 构造函数调用中。
 

currentJournalEntries.Add(
  new JournalEntry(reb,
  Path.Combine(appHomeFolder.Path),
  YMDDate, entryText,
  System.IO.Path.GetFileName(f)));

您可以看到我们添加的行与我们在第 77 行调用 LoadFileFromStorage() 时进行的调用相同。

该更改修复了将更改保存到先前创建的条目的问题。这将大大推动我们在本章中正在进行的工作,因此完成它很好。

构建、运行和测试

您可以从 DailyJournal_v029 项目获取更新的代码,然后构建、运行和测试它。


添加删除功能

让我们通过添加一个新的 CommandButton 来添加删除功能。我还将在其他两个项目和删除按钮之间添加一个垂直分隔符,以提供更多空间,减少用户意外点击删除按钮的可能性。当然,我们还需要添加某种“您确定吗?”的警告。

如果您回到 Microsoft 的在线文档并搜索“删除”一词,您会看到 Microsoft 为删除按钮提供了一个垃圾桶图标。

https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.symbol

 

我们将在 XAML 中为新按钮使用该符号。

<AppBarSeparator/>
<AppBarButton
  x:Name="DeleteEntryButton"
  Icon="Delete"
  Label="Delete"
  ToolTipService.ToolTip="Save (Ctrl+D)" >
</AppBarButton>

垂直分隔符称为 AppBarSeparator,您可以看到我们在一行中添加了该新元素,它在垃圾桶图标的左侧创建了线条。

现在我们可以为删除按钮的点击事件添加一个新的事件处理程序。

添加事件处理程序

我确信您还记得添加事件处理程序是多么容易。

  1. 选择我们要添加事件处理程序的元素(通常在 XAML 视图中选择最容易)。

  2. 点击“属性”窗口中的[闪电]按钮(通常位于 Visual Studio 的右下角)。

  3. 向下滚动直到您看到 Click 事件处理程序及其关联的文本框。

  4. 双击 Click 事件处理程序关联的文本框,Visual Studio 将添加事件处理程序并打开 MainPage.xaml.cs,以便您可以将代码添加到事件处理程序中。

 

一旦 Visual Studio 打开文件并将光标置于 DeleteEntryButton_Click 方法中,代码就相对简单了。

我们可以直接将删除功能添加到 DeleteEntryButton_Click 方法中,但这会打破我们对 SoC(关注点分离)的关注,因为 JournalEntry 应该像我们决定它应该保存自身一样删除自身。

保持关注点分离的最佳方法是让 JournalEntry 自己完成删除备份文件的工作。这样我们就可以直接在 JournalEntry 上调用名为 Delete() 的方法,然后就完成了。这遵循了我们让 JournalEntry 保存自身时使用的模式(我们只需调用 Save())。

添加 JournalEntry 删除方法

切换到 JournalEntry 类(打开 JournalEntry.cs 文件),我们将为新的 Delete() 方法添加一个存根。

这是我最初添加到 JournalEntry 类中的代码

public bool Delete()
{
  return false;
}

为什么要添加布尔返回值?

我添加了那个返回 false 只是为了 Visual Studio 不会给我愤怒的红色波浪线。

我希望返回一个 boolean (true / false) 值,因为我正在考虑调用者可能需要知道一个项目是否被删除,以便它可以决定是否需要运行其他功能。

我们删除了文件吗?

接下来,我们需要添加一个检查,以查看关联的 Entry 文件是否存在。

如果存在,我们将其删除并返回 true。

如果不存在,我们不执行任何操作并返回 false。

这是我们将在当前返回 false 的行之前添加的代码

String targetFile = Path.Combine(AppHomeFolderPath, YMFolder, FileName);

if (File.Exists(targetFile))
{
  File.Delete(targetFile);
  return true;
}

第 46 行,我创建了一个局部变量来保存我们正在寻找的目标文件的完整路径。

我创建局部变量是因为我将在两个地方使用它,如果我不将其存储在某个地方,我就必须两次调用 Path.Combine()

之后,我们使用库方法 File.Exists() 检查文件是否存在。

如果存在,我们将其删除。实际上,在调用 File.Delete() 之前我们不必检查文件是否存在,因为 File.Delete 是一个故障安全方法,这意味着如果文件不存在,它 simply 不做任何事情,也不会抛出任何异常。

我们检查文件是否存在只是为了知道我们是否要向原始调用者返回 true 或 false。

但是,等等,还有更多!

如果我们需要做的只是删除文件,这将满足我们的删除需求。然而,我们之前决定所有条目文件都将按顺序编号,并且我们决定应用程序本身将始终保证此顺序,以便在创建新条目(及关联的条目文件)时,编号将保持连续。

删除时处理文件条目编号

现在我们正在删除条目和关联的条目文件,我们可能会创建一个问题,例如,我们删除 3 个条目中的第 2 个条目,然后我们会有文件号 1 和 3,但没有 2。我们需要编写一个方法来处理这个问题,以便在文件被删除时,现有文件上的条目号将相应地更新。

让我们添加一个新的私有方法,名为 UpdateFileNumbers()。我们将传入一个整数值,它将代表被删除条目的起始条目号。该数字以下的条目不需要重命名。

这是我们要添加的整个方法。

private void UpdateFileNumbers(int startAtNumber)
{
  string targetDirectory = Path.Combine(AppHomeFolderPath, YMFolder);

  if (Directory.Exists(targetDirectory))
  {
     String[] allCurrentFiles = Directory.GetFiles(targetDirectory,
     String.Format("{0}*.rtf", YMDDate), SearchOption.TopDirectoryOnly);
     allCurrentFiles = allCurrentFiles.Skip(startAtNumber).ToArray();

     foreach (String f in allCurrentFiles)
     {
        (++startAtNumber).ToString("000"));
        File.Move(f, Path.Combine(targetDirectory,targetFileName));
     }
  }
}

第 61 行,您可以看到我们简单地设置了我们将要处理的 targetDirectory。这与我们在 GenerateFileName() 中使用的代码完全相同。

接下来,在第 62 行,我们确保目录确实存在,以确保我们发现现有文件的调用不会抛出异常。然后我们获取现有文件的列表(请记住,我们删除的那个已经消失了)。

我们再次使用 Skip 方法跳过所有编号较小的条目文件,因为我们不需要重命名它们。

最后,在第 67 行,我们遍历所有剩余的文件。每次循环时,我们都会使用 startAtNumber 生成新的 targetFileName(第 69 行),每次使用 ++ 运算符在使用前递增。

第 71 行,我们调用 File.Move() 方法,该方法获取源文件名并将其重命名为我们的 targetFileName。您可能期望该方法被称为 rename,但 Move 是用于移动和重命名文件的约定。

现在,我们只需要在每次删除文件时调用此方法即可。

我们将该行代码添加回 JournalEntry.Delete() 方法中,就在我们在 JournalEntry.cs 文件中创建此方法之前。

添加的代码只有一行,但我添加了两行注释并将代码分成了多行,以便我们更容易看到它。

UpdateFileNumbers(Convert.ToInt16(FileName.Substring(FileName.Length - 7, 3))-1);


完成删除功能

有了这些代码,我们就可以通过再添加几行代码来调用方法,从而完全完成删除功能。

现在我们已经添加了这个公共方法,我们可以很容易地从 DeleteEntryButton_Click 方法中调用它。保存您对 JournalEntry 类的更改并切换回 MainPage.xaml.cs,让我们继续对该方法进行更改。

Visual Studio 对我们很有帮助,因为现在当我们回到 DeleteEntryButton_Click 方法并开始输入代码时,我们应该会得到一些帮助。

当您开始输入 currentJournalEntries.currentJournalEntry. 并在 currentJournalEntry 后面输入点时,Visual Studio 应该会尝试为您提供一些 Intellisense 帮助。

 

您可以看到,现在除了以前可用的公共成员外,您还拥有 Delete 方法。

我们将调用包装在一个 if() 语句中,这样如果方法确实删除了文件,我们就可以做一些额外的工作。

if (currentJournalEntries.currentJournalEntry.Delete())
{

}

在该 if() 语句中,我们实际上想要运行当用户在 MainCalendar 中单击新日期时执行的功能。我们希望它这样做,以便条目将再次初始化,从而删除已删除条目对应的 PivotItem。

但是,我们需要运行的代码目前包含在 MainCalendar_SelectedDatesChanged 事件处理程序中。

 

由于我们不想调用该处理程序,但我们确实希望该功能再次运行,因此我们需要将代码从该处理程序移动到其自己的方法中,以便我们可以从多个地方调用它。

让我们从事件处理程序中剪切该代码,并将其放入 MainPage.xaml.cs 文件中的一个新方法中,我们将命名为 InitializeEntryItems

我将新方法直接添加到 MainCalendar_SelectedDatesChanged() 事件处理程序上方,然后再次将对新方法的调用添加到事件处理程序中,这样一切都将像以前一样继续工作。

 


此外,由于该方法现在已经分离出来,我们现在可以轻松地在 DeleteEntryButton_Click 处理程序中调用该功能。让我们回到该方法并添加我们的新方法调用。

我们只需添加对新 InitializeEntryItems() 方法的调用即可。

 

又一个 Bug

当我构建并广泛测试此功能时,我发现现有代码中存在一个错误。这是一个非常小的错误,当您尝试删除没有条目的日期上的条目时发生。当您这样做时,应用程序将不正确地删除 currentJournalEntries.currentJournalEntry 中的第一个条目(无论当时哪个是当前的)。

此问题发生是因为 AddDefaultEntry() 方法没有像它应该的那样将默认条目设置为 currentJournalEntry

修复此问题只需一行代码,因此让我们转到 AddDefaultEntry() 方法(仍在 MainPage.xaml.cs 文件中)并添加这一行代码来修复此问题。

 

currentJournalEntries.GetSelectedEntry("Entry1");

当然,这是默认条目,所以我们总是将 EntryHeader 设置为“Entry1”。在这里,我们只需要确保调用 GetSelectedEntry() 方法,以便正确设置 currentJournalEntry。

通过此修复,我们的删除功能已完成并运行良好。

构建、运行、测试:开发者的三部曲

获取 DailyJournal_v030 代码并构建、运行和测试它。

作为测试创建三个条目

如果您创建三个条目(Entry1、Entry2、Entry3),然后删除 Entry2,例如,您会看到关联的 entry3 条目文件将被重命名/重新编号为 002。

在屏幕上,您会看到只有两个条目,用户焦点将设置为 Entry1。

一切都运行良好。但是,我们需要添加一种方法来询问用户她是否真的想删除该条目,并要求她确认删除,以便她有机会改变主意。

让我们来看看我们将如何做到这一点。

我通过阅读 Microsoft 文档进行了研究:https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/dialogs

ContentDialog 或 Flyout

该文档有助于解释何时应该使用 ContentDialogFlyoutFlyout 是一种更简单的消息,用于向用户显示一些相关信息,用户可以通过点击表单上的其他任何地方并使 Flyout 消失来轻松忽略它。相比之下,ContentDialog 要求用户通过在对话框上接受或取消来与对话框进行交互。在用户响应对话框之前,她无法与应用程序进一步交互。对于我们删除条目的情况,ContentDialog 是更好的选择,因此我们将继续沿着这条路径前进。

Microsoft 文档中有一个不错的示例,所以我将其作为我们将要完成的工作的模板。该代码可以直接运行,所以让我们添加它,尝试一下,然后根据我们的需求进行修改。

private async void DisplayLocationPromptDialog()
{
   ContentDialog locationPromptDialog = new ContentDialog
   {
       Title = "Allow AppName to access your location?",
       Content = "AppName uses this information to help you find places, connect with friends, and more.",
       CloseButtonText = "Block",
       PrimaryButtonText = "Allow"
   };

   ContentDialogResult result = await locationPromptDialog.ShowAsync();
}

 

让我们将这个新方法直接放到 MainPage.xaml.cs 文件的底部,就在 DeleteEntryButton_Click 方法之后。

 

Microsoft 示例代码中的 Bug

该方法无需其他更改即可工作,但 Microsoft 提供的示例实际上存在一个错误。真的。

代码引用了 CloseButtonText(据说是 ContentDialog 的一个属性),但要么 Microsoft 自文档创建以来更改了属性名称,要么文档作者弄错了,因为该属性不存在。该属性应该命名为:SecondaryButtonText,所以我们首先更改它。

一旦你做出这个改变,我们就可以使用代码了。让我们从 DeleteEntryButton_Click 方法中调用这段代码,看看它会做什么。

我们只需要一行代码来调用该方法,我们将其添加为 DeleteEntryButton_Click 的第一行。

DisplayLocationPromptDialog();


 

警告:显然,此代码没有我们将来会使用的正确文本,也不会阻止您的项目被删除。要测试此功能,我建议您单击没有创建条目的日期上的[删除]按钮。这样,实际上不会删除任何内容,但您将能够看到此 ContentDialog 出现。

 

添加该行代码后,您可以运行应用程序并通过单击[删除]按钮尝试新功能。当您运行应用程序并单击[删除]按钮时,您将看到类似以下内容

 


 

您会注意到您无法点击 ContentDialog 后面或周围的任何地方。使用 ContentDialog,应用程序会强制您选择其中一个按钮以继续。这在我们的情况下是合理的,因为我们希望用户接受或取消条目的删除。

键盘快捷键

还有两个默认键可以响应对话框:<ESC><ENTER>

按下<ESC>按钮将自动选择对话框的“取消”选项。按下<ENTER>按钮将自动选择对话框的“接受”选项。

DisplayLocationPromptDialog 有什么作用?

DisplayLocationPromptDialog 方法实际上做了三件事

  1. 设置一个 ContentDialog

  2. 显示对话框

  3. 获取返回值,该值取决于用户点击了哪个按钮

对话框的显示和返回值的获取都通过那一行(我们之前代码清单的第 276 行)完成。

在 Visual Studio 中使用断点

让我们在该行添加一个断点,我将向您展示在调试模式下运行应用程序时如何检查返回值的(值和类型)。

要添加断点,您只需单击 Visual Studio 代码编辑器中的装订线区域即可。

 

点击行号(276)的左侧,就会添加一个断点。当该行代码运行时,断点将导致应用程序停止。现在,使用调试运行应用程序(按<F5>)并点击[删除]按钮。

当您按照这些步骤操作,在点击删除按钮后,Visual Studio 将弹出到屏幕顶部,并显示如下所示

 

 

指令指针、即时窗口、局部变量

请注意,黄色箭头指向即将执行的代码行。另请注意,右下角的窗口(默认位置)是一个名为“即时窗口”的窗口。在调试期间,我们将看到我们可以在其中编写代码来检查值。我稍后会向您展示如何操作。

另请注意,有一个名为“局部变量”的选项卡,它将显示局部变量(方法和正在运行的类中的局部变量),以便我们可以轻松检查值。

点击“局部变量”选项卡,我们可以查看一些局部变量的值。

我们最感兴趣的是 result

 

查看局部变量的值

您可以看到 result 当前设置为 None,这意味着尚未点击任何按钮。我还想让您看到结果的类型为 ContentDialogResult,它是一个枚举。枚举基本上是一个专门的类,它将一些友好的名称映射到整数值。在我们的例子中,ContentDialogResult 有三个可能的值

  1. 主(Primary)

  2. 次(Secondary)

这些映射到用户点击的按钮。

继续按 <F10> 键,进一步深入代码。当您这样做时,应用程序将重新出现在屏幕上并显示 ContentDialog

当它出现时,点击“[允许]”按钮,应用程序将再次消失,Visual Studio 将弹出,您将停在该方法的最后一个括号上,就在代码返回到调用方法之前。

 

同样,您可以看到指令指针当前位于何处,因为 Visual Studio 用黄色箭头突出显示它。

您还可以看到在“局部变量”窗口中,result 变量现在保存了 Primary 的值,因为我们点击了我们定义为主要按钮的按钮。当然,如果您点击了另一个按钮,那么 result 中的值将是 Secondary

这就是我们如何确定用户点击了哪个按钮。

即时窗口

如果我们想检查 ContentDialogResult 枚举中可能的值,我们可以使用“即时”窗口。点击“即时”窗口内部并输入:ContentDialogResult.(末尾有一个点)。

 

现在,您可以看到 ContentDialogResult 在枚举中提供了 3 个值。您还可以看到我已经悬停在 Primary 值上,以显示它的整数值为 1。None 等于 0,Secondary 等于 2。

这让您了解了如何在代码运行时检查值,所以现在让我们稍微修改代码,使其更适合我们的目的。

您可以通过转到“[调试]”菜单并选择“[停止调试]”菜单项来停止应用程序。

 

根据我们的目的修改代码

我们需要更改显示的消息和按钮文本,以匹配我们在应用程序中正在做的事情。我们还需要更改方法名称,使其更具意义(DisplayDeleteConfirmationDialog)。

让我们将代码更改为如下所示。

private async void DisplayDeleteConfirmationDialog()
{

  ContentDialog confirmDialog = new ContentDialog
  {
     Title = "Are you sure you want to delete this entry?",
     Content = "If you choose Delete, the entry file will be permanently removed.",
     PrimaryButtonText = "Delete",
     SecondaryButtonText = "Cancel"
  };
  ContentDialogResult result = await confirmDialog.ShowAsync();
}

 

这替换了之前的方法名。另请注意,我当然也更改了 DeleteEntryButton_Click 方法中的方法调用,因为方法名已更改。

 


 

我们可以运行代码,现在消息和按钮更有意义了,但无论点击哪个按钮,应用程序仍然做同样的事情并删除文件。

构建、运行、尝试

您可以获取最新代码,只需查看新的对话框消息和按钮显示出来即可。获取 DailyJournal_v031 代码并试用。

现在,当您运行应用程序并测试删除功能时,至少所有文本都是正确的,并且与用户实际执行的操作相关。


 

修改返回以确定做什么

我们现在要做的是修改 DisplayDeleteConfirmationDialog 方法的返回,以便我们可以使用返回的值来决定应用程序是否应该继续删除条目文件。

然而,由于 DisplayDeleteConfirmationDialog 是一个 async 方法,将会有一些挑战需要解决。

我做的第一件事是更改代码的最后一行,这样我们就不再设置 result 变量,而是简单地返回 ContentDialogResult 值。我们将更改第 276 行,使其看起来如下所示

return await confirmDialog.ShowAsync();

当然,该方法现在需要返回该类型,而不是 void,所以我们也更改了方法签名。然而,当我们这样做时,Visual Studio Intellisense 警告我们有些不对劲。

消息告诉我们,异步方法必须返回三样东西之一(voidTaskTask<T>,其中 T 是任何类型。这使得我们很容易修复。我们所要做的就是将我们的 ContentDialogResult 包装在 Task<> 中。

我们的方法签名现在将是

private async Task<ContentDialogResult> DisplayDeleteConfirmationDialog()

 

代码再次构建并运行,但我们仍然没有使用返回值。让我们在 DeleteEntryButton_Click 中调用该方法的地方进行更改,然后一切都将正常工作。

我们只需更改调用方法的代码行,以便我们现在将返回值捕获到一个可以使用的变量中。

ContentDialogResult result = DisplayDeleteConfirmationDialog();

当您修改该行代码时,Intellisense 将再次警告您并尝试提供帮助。

 

Intellisense 告诉我们,由于我们正在调用的方法是 async 方法,因此我们需要使用 await 运算符。在 Intellisense 文本的“用法”部分中,您可以看到我们只需添加 await 关键字即可解决问题。当然,一旦我们这样做,我们还需要更改方法签名,使其也成为 async 方法。因此,让我们现在就进行这两个更改。

 

现在,该方法是有效的,但是,我们仍然需要添加代码,以便只有当用户点击 Primary(删除)按钮时才删除条目文件。

现在我们已经准备好一切,做这项工作非常容易。

我们所要做的就是将现有代码包装在一个 if() 语句中,该语句检查结果是否等于 ContentDialogResult.Primary

if (result == ContentDialogResult.Primary)


 

就是这样。删除代码现在将按您预期的方式工作。

它运行得非常好。如果您没有代码,请获取 DailyJournal_v032 并试用。

如果您点击“删除”,然后选择“[取消]”按钮或按下<ESC>键,则条目不会被删除。

如果您点击“删除”,然后选择“[删除]”按钮或按下<ENTER>键,则条目将被删除。

修复保存条目的小问题

我们还需要在 SaveEntryButton_Click 上进行另一个修复,这将允许我们修复本章顶部列表中第三个项目。

该方法一直异步调用 JournalEntry.Save() 方法而没有等待结果。这意味着如果我们尝试刷新项目列表,它将无法一致地工作,因为该代码可能在异步 Save() 方法完成之前运行。

我们想在 SaveEntryButton_Click 中更改的代码在第 216 行。我们只想在该行添加 await 关键字,以便该方法在继续之前等待保存完成。

 

 

当我们添加 await 关键字时,Intellisense 警告我们,我们正在等待的方法不能返回 void。如果您点击“[显示潜在修复]”链接,它将为您提供将 Save() 方法的返回类型更改为 Task 的选项。

 

 

我点击了选项,让 Visual Studio 施展魔法。请记住,它会对 JournalEntry 类的 Save() 方法进行此更改。

我切换过去查看代码,看到了这一团糟。

 

 

嗯,这确实能编译,但在编辑器中看起来确实一团糟。

我把它折叠成一行。

 

现在我们可以切换回 MainPage.xaml.cs 中的 SaveEntryButton_Click 方法并完成我们的修复。这非常容易。我们只需要添加三行代码来修复保存问题,以便将条目添加到 EntriesListView Count 中。

var selectedIdx = rootPivot.SelectedIndex;
InitializeEntryItems();
rootPivot.SelectedIndex = selectedIdx;

 

第 218 行,我们创建一个新的局部变量来保存存储在 rootPivotSelectedIndex 属性中的整数值。该值代表当前选定的 PivotItem (EntryX)。我们需要存储它,因为我们将调用 InitializeEntryItems() 方法,它将在重新加载条目文件时重置所有条目项目。在我们调用 InitializeEntryItems() 之后,列表视图中的条目计数将是正确的,因为它将完成计算条目的代码,其中包括我们新创建(保存)的条目。最后一行确保用户被放回到她当前正在编辑的 EntryX 项目上。

代码中仍然存在一些小问题

此代码存在几个问题

  1. 对于更新先前保存的项目,不需要调用 InitializeEntryItems(),因为没有创建新项目。

  2. 用户的 RichEditBox 中的光标会移动到第一行和第一个字符,即使她可能正在 RichEditBox 的底部行键入。

我们可以稍后修复这些问题,或者您可以自己尝试修复它们。

获取代码并运行

获取代码并试用。获取 DailyJournal_v033 并试用代码。它更接近您期望的完整应用程序。

让我们完成列表中的最后一个项目,并结束本章。

AccessKey 创建键盘快捷键

这些键盘快捷键实际上称为 AccessKeys。

添加它们非常简单,因为我们所要做的就是将 AccessKey 属性添加到每个 AppBarButton 的 XAML 中。

让我们修改我们所有的三个 AppBarButtons 并添加适当的 AccessKeys

每个只需一个简单设置

新条目:AccessKey = “N”

保存条目:AccessKey = “S”

删除条目:AccessKey= “D”


 

您可以看到我也修改了每个按钮的 TooltipService.Tooltip,以表明 ALT 键加上 AccessKey 值将执行该功能。以前这些都显示为 CTRL+KEY,但这些 AccessKeys 默认使用 ALT 键。

一旦您添加了这段代码,您只需键入 ALT+N 即可创建新条目,ALT+S 即可保存条目,ALT+D 即可删除条目。当然,所有功能都像您单击按钮一样运行,因此在 ALT+D 的情况下,确认对话框仍会弹出。

此外,当您悬停在每个按钮上时,将出现一个工具提示,告知用户她可以使用哪些键来运行该功能。

在示例中,我将鼠标悬停在“保存”按钮上,然后弹出了工具提示。

获取代码并运行

获取 DailyJournal_v034 并试用。当您按下 ALT+N 时,将创建一个新条目,您可以在其中输入内容,然后按下 ALT+S,它将被保存。它运行得非常好,因为您现在只需使用键盘。

本章的工作量很大,但我们涉及了许多领域,学到了很多东西,并使应用程序变得更好了。

 

历史

2017-12-22:首次发布

© . All rights reserved.