透過 Cortana 以語音命令啟用前景應用程式
警告
自 Windows 10 May 2020 更新版 (版本 2004,代號「20H1」) 起,不再支援此功能。
除了在 Cortana 中使用語音命令來存取系統功能之外,您也可以使用來自您應用程式的特色和功能來擴充 Cortana。 使用語音命令,您的應用程式可以啟用到前景,並在應用程式內執行動作或命令。
當應用程式在前景處理語音命令時,會取得焦點並關閉 Cortana。 如果您想要的話,您可以啟用應用程式,並以背景工作的形式執行命令。 在此情況下,Cortana 會保留焦點,而您的應用程式會透過 Cortana 畫布和 Cortana 語音傳回所有意見反應和結果。
需要其他內容或使用者輸入的語音命令 (例如將訊息傳送至特定聯絡人) 最好在前景應用程式中處理,而基本命令 (例如列出即將來臨的旅程) 可以在 Cortana 中透過背景應用程式處理。
如果您想要使用語音命令在背景啟用應用程式,請參閱使用語音命令在 Cortana 中啟用背景應用程式。
注意
語音命令是具有特定意圖的單一語句,在語音命令定義 (VCD) 檔案中定義,透過 Cortana 針對已安裝的應用程式。
VCD 檔案會定義一或多個語音命令,每個命令都有唯一的意圖。
語音命令定義可能會因複雜度而異。 他們可以支援任何項目,從單一、限制語句到更有彈性、自然語言語句的集合,都表示相同的意圖。
為了示範前景應用程式功能,我們將使用 Cortana 語音命令範例中名為 Adventure Works 的旅程規劃和管理應用程式。
若要建立不含 Cortana 的新 Adventure Works 旅程,使用者將會啟動應用程式並瀏覽至 [新旅程] 頁面。 若要檢視現有的旅程,使用者將會啟動應用程式、瀏覽至 [即將來臨的旅程] 頁面,然後選取該旅程。
透過 Cortana 使用語音命令,使用者可以改為直接說「Adventure Works 新增旅程」或「在 Adventure Works 上新增旅程」,以啟動應用程式並瀏覽至 [新增旅程] 頁面。 反過來,說「Adventure Works,顯示我的倫敦之旅」將會啟動應用程式,並瀏覽至 [旅程詳細資料] 頁面,如下所示。
以下是新增語音命令功能,並使用語音或鍵盤輸入整合 Cortana 與應用程式的基本步驟:
- 建立 VCD 檔案。 這是一份 XML 文件,定義了使用者在啟用應用程式時,可以說出的用於起始動作或叫用命令的所有口頭命令。 請參閱 VCD 元素和屬性 v1.2。
- 啟動應用程式時,請在 VCD 檔案中註冊命令集。
- 處理啟用 by-voice-command、應用程式內的瀏覽,以及命令的執行。
提示
先決條件
如果您是開發通用 Windows 平台 (UWP) 應用程式的新手,請瀏覽這些主題以熟悉此處討論的技術。
使用者體驗指引
如需如何整合應用程式與 Cortana 和語音互動的資訊,請參閱 Cortana 設計指導方針,以取得設計實用且吸引人的語音啟用應用程式的實用秘訣。
在 Visual Studio 中使用專案建立新的解決方案
啟動 Microsoft Visual Studio 2015。
將顯示 Visual Studio 2015 起始頁。
在 [檔案] 功能表上選取 [新增]>[專案]。
[新增專案] 對話方塊隨即出現。 對話方塊的左窗格可讓您選取要顯示的範本類型。
在左側窗格中,展開已安裝的 > 範本 > Visual C# > Windows,然後選擇通用範本群組。 對話方塊的中央窗格會顯示通用 Windows 平台 (UWP) 應用程式的專案範本清單。
在中央窗格中,選取空白應用程式 (通用 Windows) 範本。
空白應用程式範本會建立編譯和執行的最低 UWP 應用程式,但不包含使用者介面控制項或資料。 在本教學課程的課程中,您會將控制項新增至應用程式。
在名稱文字方塊中,輸入您的專案名稱。 針對此範例,我們會使用「AdventureWorks」。
按一下 [確定] 以建立專案。
Microsoft Visual Studio 建立您的專案並顯示在方案總管中。
將圖影像資源新增至專案並在應用程式資訊清單中指定它們
UWP 應用程式可以根據特定設定和裝置功能自動選取最適當的影像 (高對比度、有效像素、地區設定等等)。 您只需要提供影像,並確定您在應用程式專案中針對不同資源版本使用適當的命名慣例和資料夾組織。 如果您未提供建議的資源版本、協助工具、當地語系化和影像品質可能會受到影響,視使用者的喜好設定、能力、裝置類型和位置而定。
如需高對比度和縮放比例影像資源的詳細資訊,請參閱圖塊和圖示資產的指導方針。
您可以使用限定詞來命名資源。 資源限定詞是資料夾和檔名修飾詞,可識別應使用特定資源版本的內容。
標準命名慣例為 foldername/qualifiername-value[_qualifiername-value]/filename.qualifiername-value[_qualifiername-value].ext
。 例如,images/logo.scale-100_contrast-white.png
只要使用根資料夾和檔名即可在程式碼中參考:images/logo.png
。 請參閱如何使用限定詞命名資源。
我們建議您在字串資源檔案上標示預設語言 (例如 en-US\resources.resw
),以及影像上的預設縮放比例 (例如 logo.scale-100.png
),即使您目前不打算提供當地語系化或多個解析度資源。 不過,我們建議您至少提供 100、200 和 400 個縮放比例的資產。
重要
Cortana 畫布標題區域中所使用的應用程式圖示是「Package.appxmanifest」檔案中指定的 Square44x44Logo 圖示。
建立 VCD 檔案
- 在 Visual Studio 中,以滑鼠右鍵按一下主要專案名稱,選取 [新增] >[新項目]。 新增 XML 檔案。
- 輸入 VCD 檔案的名稱 (在此範例中為「AdventureWorksCommands.xml」),然後按一下 [新增]。
- 在方案總管中,選取 VCD 檔案。
- 在屬性視窗中,將建置動作設為內容,然後將複製到輸出目錄設為如果較新則複製。
編輯 VCD 檔案
新增具有指向 https://schemas.microsoft.com/voicecommands/1.2
之 xmlns 屬性的 VoiceCommands 元素。
對於您的應用程式支援的每種語言,請建立一個 CommandSet 元素,其中包含您的應用程式支援的語音命令。
您可以宣告多個 CommandSet 元素,每個元素都有不同的 xml:lang 屬性,以便您的應用程式在不同的市場中使用。 例如,針對美國的應用程式可能會使用 CommandSet 表示英文,並使用 CommandSet 表示西班牙文。
警告
若要啟用應用程式並使用語音命令起始動作,應用程式必須註冊一個 VCD 檔案,其中包含 CommandSet,該命令集的語言與使用者為其裝置選擇的語音語言相符。 語音語言位於「設定」>「系統>語音」>「語音語言」。
為您想要支援的每個命令新增一個 Command 元素。 VCD 檔案中宣告的每個命令都必須包含下列資訊:
- 應用程式用來在執行階段辨識語音命令的 AppName 屬性。
- 範例元素,其中包含描述使用者如何叫用命令的片語。 當使用者說「我能說什麼?」、「幫助」或點選「查看更多」時,Cortana 會顯示此範例。
- ListenFor 元素,其中包含應用程式辨識為命令的單字或片語。 每個 ListenFor 元素都可以包含一或多個 PhraseList 元素的參考,其中包含與命令相關的特定單字。
注意
ListenFor 元素無法以程式設計方式修改。 不過,您可以透過程式設計方式修改與 ListenFor 元素相關聯的 PhraseList 元素。 應用程式應根據使用者使用應用程式時產生的資料集在執行階段修改 PhraseList 的內容。 請參閱動態修改 Cortana VCD 片語清單。
Feedback 元素,包含 Cortana 在啟動應用程式時顯示和說話的文字。
瀏覽元素表示語音命令將應用程式啟用至前景。 在此範例中,showTripToDestination
命令是前景工作。
VoiceCommandService 元素表示語音命令將應用程式啟用至背景。 這個專案的 Target 屬性值應該符合 package.appxmanifest 檔案中 uap:AppService 元素的 Name 屬性值。 在此範例中,whenIsTripToDestination
和 cancelTripToDestination
命令是將應用程式服務名稱指定為「AdventureWorksVoiceCommandService」的背景工作。
如需詳細資訊,請參閱VCD 元素和屬性 v1.2 參考。
以下是定義 Adventure Works 應用程式的 en-us 語音命令之 VCD 檔案的一部分。
<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="https://schemas.microsoft.com/voicecommands/1.2">
<CommandSet xml:lang="en-us" Name="AdventureWorksCommandSet_en-us">
<AppName> Adventure Works </AppName>
<Example> Show trip to London </Example>
<Command Name="showTripToDestination">
<Example> Show trip to London </Example>
<ListenFor RequireAppName="BeforeOrAfterPhrase"> show [my] trip to {destination} </ListenFor>
<ListenFor RequireAppName="ExplicitlySpecified"> show [my] {builtin:AppName} trip to {destination} </ListenFor>
<Feedback> Showing trip to {destination} </Feedback>
<Navigate />
</Command>
<Command Name="whenIsTripToDestination">
<Example> When is my trip to Las Vegas?</Example>
<ListenFor RequireAppName="BeforeOrAfterPhrase"> when is [my] trip to {destination}</ListenFor>
<ListenFor RequireAppName="ExplicitlySpecified"> when is [my] {builtin:AppName} trip to {destination} </ListenFor>
<Feedback> Looking for trip to {destination}</Feedback>
<VoiceCommandService Target="AdventureWorksVoiceCommandService"/>
</Command>
<Command Name="cancelTripToDestination">
<Example> Cancel my trip to Las Vegas </Example>
<ListenFor RequireAppName="BeforeOrAfterPhrase"> cancel [my] trip to {destination}</ListenFor>
<ListenFor RequireAppName="ExplicitlySpecified"> cancel [my] {builtin:AppName} trip to {destination} </ListenFor>
<Feedback> Cancelling trip to {destination}</Feedback>
<VoiceCommandService Target="AdventureWorksVoiceCommandService"/>
</Command>
<PhraseList Label="destination">
<Item>London</Item>
<Item>Las Vegas</Item>
<Item>Melbourne</Item>
<Item>Yosemite National Park</Item>
</PhraseList>
</CommandSet>
安裝 VCD 命令
您的應用程式必須執行一次,才能安裝 VCD。
注意
應用程式安裝不會保留語音命令資料。 為了確保應用程式的語音命令資料保持不變,請考慮在每次啟動或啟用應用程式時初始化 VCD 檔案,或維護指出 VCD 目前是否已安裝的設定。
在「app.xaml.cs」檔案:
新增以下 using 指示詞:
using Windows.Storage;
使用 async 修飾詞標記「OnLaunched」方法。
protected async override void OnLaunched(LaunchActivatedEventArgs e)
在 OnLaunched 處理常式中呼叫 InstallCommandDefinitionsFromStorageFileAsync 以註冊系統應辨識的語音命令。
在 Adventure Works 範例中,我們會先定義 StorageFile 物件。
然後,我們會呼叫 GetFileAsync,以我們的「AdventureWorksCommands.xml」檔案將其初始化。
這個 StorageFile 物件接著會傳遞至 InstallCommandDefinitionsFromStorageFileAsync。
try
{
// Install the main VCD.
StorageFile vcdStorageFile =
await Package.Current.InstalledLocation.GetFileAsync(
@"AdventureWorksCommands.xml");
await Windows.ApplicationModel.VoiceCommands.VoiceCommandDefinitionManager.
InstallCommandDefinitionsFromStorageFileAsync(vcdStorageFile);
// Update phrase list.
ViewModel.ViewModelLocator locator = App.Current.Resources["ViewModelLocator"] as ViewModel.ViewModelLocator;
if(locator != null)
{
await locator.TripViewModel.UpdateDestinationPhraseList();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Installing Voice Commands Failed: " + ex.ToString());
}
處理啟用和執行語音命令
指定您的應用程式回應後續語音命令啟用的方式 (至少啟動一次且已安裝語音命令集之後)。
確認您的應用程式已由語音命令啟動。
覆寫 Application.OnActivated 事件並檢查 IActivatedEventArgs.Kind 是否為 VoiceCommand。
判斷命令的名稱和說話內容。
從 IActivatedEventArgs 取得對 VoiceCommandActivatedEventArgs 物件的參考,並查詢 SpeechRecognitionResult 物件的 Result 屬性。
若要確定使用者所說的內容,請檢查 SpeechRecognitionSemanticInterpretation 字典中的 Text 值或已識別片語的語意屬性。
在您的應用程式中採取適當的動作,例如瀏覽至所需的頁面。
對於此範例,我們參考步驟 3:編輯 VCD 檔案中的 VCD。
取得語音命令的語音辨識結果之後,我們會從 RulePath 陣列中的第一個值取得命令名稱。 當 VCD 檔案定義多個可能的語音命令時,我們需要比較值與 VCD 中的命令名稱,並採取適當的動作。
應用程式可以執行的最常見動作是瀏覽到包含與語音命令內容相關的內容的頁面。 在此範例中,我們會瀏覽至 TripPage 頁面,並傳入語音命令的值、命令的輸入方式,以及辨識的「目的地」片語 (如果適用的話)。 或者,應用程式可以在瀏覽到頁面時向 SpeechRecognitionResult 傳送瀏覽參數。
您可以使用 commandMode 鍵從 SpeechRecognitionSemanticInterpretation.Properties 字典中瞭解啟動應用程式的語音命令是否實際上是說的,或者是否以文字形式輸入。 該索引鍵的值會是「語音」或「文字」。 如果該鍵的值為「語音」,請考慮在應用程式中使用語音合成 (Windows.Media.SpeechSynthesis) 為使用者提供語音意見反應。
使用 SpeechRecognitionSemanticInterpretation.Properties 尋找 ListenFor 元素的 PhraseList 或 PhraseTopic 條件約束中所說的內容。 字典索引鍵是 PhraseList 或 PhraseTopic 元素的 Label 屬性值。 在此處,我們將示範如何存取 {destination} 片語的值。
/// <summary>
/// Entry point for an application activated by some means other than normal launching.
/// This includes voice commands, URI, share target from another app, and so on.
///
/// NOTE:
/// A previous version of the VCD file might remain in place
/// if you modify it and update the app through the store.
/// Activations might include commands from older versions of your VCD.
/// Try to handle these commands gracefully.
/// </summary>
/// <param name="args">Details about the activation method.</param>
protected override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
Type navigationToPageType;
ViewModel.TripVoiceCommand? navigationCommand = null;
// Voice command activation.
if (args.Kind == ActivationKind.VoiceCommand)
{
// Event args can represent many different activation types.
// Cast it so we can get the parameters we care about out.
var commandArgs = args as VoiceCommandActivatedEventArgs;
Windows.Media.SpeechRecognition.SpeechRecognitionResult speechRecognitionResult = commandArgs.Result;
// Get the name of the voice command and the text spoken.
// See VoiceCommands.xml for supported voice commands.
string voiceCommandName = speechRecognitionResult.RulePath[0];
string textSpoken = speechRecognitionResult.Text;
// commandMode indicates whether the command was entered using speech or text.
// Apps should respect text mode by providing silent (text) feedback.
string commandMode = this.SemanticInterpretation("commandMode", speechRecognitionResult);
switch (voiceCommandName)
{
case "showTripToDestination":
// Access the value of {destination} in the voice command.
string destination = this.SemanticInterpretation("destination", speechRecognitionResult);
// Create a navigation command object to pass to the page.
navigationCommand = new ViewModel.TripVoiceCommand(
voiceCommandName,
commandMode,
textSpoken,
destination);
// Set the page to navigate to for this voice command.
navigationToPageType = typeof(View.TripDetails);
break;
default:
// If we can't determine what page to launch, go to the default entry point.
navigationToPageType = typeof(View.TripListView);
break;
}
}
// Protocol activation occurs when a card is clicked within Cortana (using a background task).
else if (args.Kind == ActivationKind.Protocol)
{
// Extract the launch context. In this case, we're just using the destination from the phrase set (passed
// along in the background task inside Cortana), which makes no attempt to be unique. A unique id or
// identifier is ideal for more complex scenarios. We let the destination page check if the
// destination trip still exists, and navigate back to the trip list if it doesn't.
var commandArgs = args as ProtocolActivatedEventArgs;
Windows.Foundation.WwwFormUrlDecoder decoder = new Windows.Foundation.WwwFormUrlDecoder(commandArgs.Uri.Query);
var destination = decoder.GetFirstValueByName("LaunchContext");
navigationCommand = new ViewModel.TripVoiceCommand(
"protocolLaunch",
"text",
"destination",
destination);
navigationToPageType = typeof(View.TripDetails);
}
else
{
// If we were launched via any other mechanism, fall back to the main page view.
// Otherwise, we'll hang at a splash screen.
navigationToPageType = typeof(View.TripListView);
}
// Repeat the same basic initialization as OnLaunched() above, taking into account whether
// or not the app is already active.
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active.
if (rootFrame == null)
{
// Create a frame to act as the navigation context and navigate to the first page.
rootFrame = new Frame();
App.NavigationService = new NavigationService(rootFrame);
rootFrame.NavigationFailed += OnNavigationFailed;
// Place the frame in the current window.
Window.Current.Content = rootFrame;
}
// Since we're expecting to always show a details page, navigate even if
// a content frame is in place (unlike OnLaunched).
// Navigate to either the main trip list page, or if a valid voice command
// was provided, to the details page for that trip.
rootFrame.Navigate(navigationToPageType, navigationCommand);
// Ensure the current window is active
Window.Current.Activate();
}
/// <summary>
/// Returns the semantic interpretation of a speech result.
/// Returns null if there is no interpretation for that key.
/// </summary>
/// <param name="interpretationKey">The interpretation key.</param>
/// <param name="speechRecognitionResult">The speech recognition result to get the semantic interpretation from.</param>
/// <returns></returns>
private string SemanticInterpretation(string interpretationKey, SpeechRecognitionResult speechRecognitionResult)
{
return speechRecognitionResult.SemanticInterpretation.Properties[interpretationKey].FirstOrDefault();
}