WPF 使用 Windows 10 APIs - 2

WPF 使用 Windows 10 APIs - 1 介紹如何包裝 WPF 成爲 Bridge App 與如何跟 UWP App 互動,這篇繼續補充 WPF 成爲 Bridge App 後,原本 Win32 程式的特性怎麽對齊。

Integrate your packaged desktop application with Windows 10 介紹重點:

  • 讓 WPF 已經被建立的捷徑能繼續使用,例如:Start menu 或 Task bar 上面的捷徑
    在 Package.appxmanifest 中補上:
    <Extension Category="windows.desktopAppMigration">
        <DesktopAppMigration>
            <DesktopApp AumId="[your_app_aumid]" />
            <DesktopApp ShortcutPath="[path]" />
        </DesktopAppMigration>
    </Extension>
  • 讓 packaged application 取代原本 desktop application 預設處理的檔案類型
    需要先拿到每一個程式專屬的 programmatic identify (ProgID),並將它與 file association 綁定在一起。
    <Extension Category="windows.fileTypeAssociation">
        <FileTypeAssociation Name="[AppID]">
             <MigrationProgIds>
                <MigrationProgId>"[ProgID]"</MigrationProgId>
            </MigrationProgIds>
        </FileTypeAssociation>
    </Extension>
    • windows.fileTypeAssociation 固定值
    • programmatic identifier (ProgID) 代表 App 的唯一識別碼,例如:MyWPFBridge;
    • MigrationProgId 給 desktop application 當時的 ProgID,它會與設定 Name 整合在一起,例如:oldApp.jpb.a; 範例
    而注冊 File Type 的預設處理程式會被注冊在機碼裏面,如:How to Register a File Type for a New Application
  • 讓用戶對檔案按下右鍵時,可以在 open with 裏面找到 packaged application ,並取代 desktop application
    重點是注冊必要的 file extension,設定方式同 UWP 的 FileTypeAssociation
    <Extension Category="windows.fileTypeAssociation">
        <FileTypeAssociation Name="[AppID]">
            <SupportedFileTypes>
                <FileType>"[file extension]"</FileType>
            </SupportedFileTypes>
        </FileTypeAssociation>
    </Extension>
    • windows.fileTypeAssociation 固定值
    • FileType 例如: .jpb, .avi 等副檔名
  • 為特定的檔案類型增加按下右鍵後,出現多個選項,例如:開啓或列印的範例
    一樣在 FileTypeAssocation tag 下加入 SupportedVerb, 由於是用 http://schemas.microsoft.com/appx/manifest/uap/windows10/3 的命名空間,因此這個設定一樣支援 UWP application。如下:
    <Extension Category="windows.fileTypeAssociation">
        <FileTypeAssociation Name="[AppID]">
            <SupportedVerbs>
               <Verb Id="[ID]" Extended="[Extended]" Parameters="[parameters]">"[verb label]"</Verb>
            </SupportedVerbs>
        </FileTypeAssociation>
    </Extension>
    • Ver 代表在 File Explorer 的右鍵内容要顯示的項目名稱,可搭配 ms-resources 支持多語系。
    • Id 代表 Ver 的唯一值。
      如果是 UWP application,該參數會被帶入 activation event args 裏面;如果是 full-trust packaged app,則會收到下面介紹的 parameters。
    • Parameters 代表啓動該 Verb 要帶入的參數;如果是 full-trust packaged application 該參數將會被傳入在程式啓動時。Parameters 可搭配 Verb 客制為自己需的内容,甚至帶入 file path,請利用引號(")做為包裝避免有空白造成錯誤,例如: Parameters="/e "%1""。Parameters 不支援 UWP application。
    • Extended 該特性只支援用戶先按住 shift 按鈕,再到檔案上按下滑鼠右鍵時,該 Verb 才會顯示。預設是 false 代表用戶在檔案按右鍵就會顯示。
  • 支持檔案利用 URL 開啓 packaged application
    <Package
      xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
      xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
      IgnorableNamespaces="uap, uap3">
      <Applications>
          <Application>
            <Extensions>
              <uap:Extension Category="windows.fileTypeAssociation">
                <uap3:FileTypeAssociation Name="documenttypes" UseUrl="true" Parameters="%1">
                  <uap:SupportedFileTypes>
                    <uap:FileType>.txt</uap:FileType>
                    <uap:FileType>.doc</uap:FileType>
                  </uap:SupportedFileTypes>
                </uap3:FileTypeAssociation>
              </uap:Extension>
            </Extensions>
          </Application>
        </Applications>
    </Package>
    其中 UseUrl 如果有設定可以在用戶輸入 URL 時先用 packaged application 打開,而不會先下載。
  • 在防火墻加入例外你的 App
    如果您的 application 用到特殊的網路 Port 需要向防火墻宣告,才能正常使用。使用 http://schemas.microsoft.com/appx/manifest/desktop/windows10/2 命名空間,只支援 Desktop 環境使用。
    <Package
      xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2"
      IgnorableNamespaces="desktop2">
      <Extensions>
        <desktop2:Extension Category="windows.firewallRules">
          <desktop2:FirewallRules Executable="Contoso.exe">
              <desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
              <desktop2:Rule Direction="in" IPProtocol="UDP" LocalPortMin="1337" LocalPortMax="1338" Profile="domain"/>
              <desktop2:Rule Direction="in" IPProtocol="UDP" LocalPortMin="1337" LocalPortMax="1338" Profile="public"/>
              <desktop2:Rule Direction="out" IPProtocol="UDP" LocalPortMin="1339" LocalPortMax="1340" RemotePortMin="15"
                             RemotePortMax="19" Profile="domainAndPrivate"/>
              <desktop2:Rule Direction="out" IPProtocol="GRE" Profile="private"/>
          </desktop2:FirewallRules>
      </desktop2:Extension>
    </Extensions>
    </Package>
    • windows.firewallRules 固定值,搭配 FirewallRules 使用
    • Executable 代表這條規則的名稱,它會被加入到 firewall 的例外清單裏
    • Direction 設定為 inbound 或 outbound
    • IPProtocol 設定為 TCP, UDP 或其他 communication protocol
    • LocalPortMin 代表 local port numbers 的最小值
    • LocalPortMax 代表 local port numbers 的最大值
    • RemotePortMin 代表 remote port numbers 的最小值
    • RemotePortMax 代表 remote port numbers 的最大值
    • Profile 網路類型:private, public, ... 等

還有更多整合 File Explorer 與設定 Environment 的部分可以參考:Integrate with File Explorer 介紹。 接下來介紹 packaged application 如何與其他 applications 整合。

  • 讓 packaged application 支援其他 applications 想要列印時,可以找到您的 application
    例如用戶從 notepad 按下列印,列印程式選單裏面會出現您的 application。
    <Extension Category="windows.appPrinter">
        <AppPrinter
            DisplayName="[DisplayName]"
            Parameters="[Parameters]" />
    </Extension>
    • DisplayName 代表要顯示的名稱
    • Parameters 給任何您程式需要的參數,例如:Parameters="/insertdoc %1"
  • 分享 packaged application 中的自定義 font 給其他 applications
    <Extension Category="windows.sharedFonts">
        <SharedFonts>
          <Font File="[FontFile]" />
        </SharedFonts>
    </Extension>
    File 指定您 application 放的 font 位置
  • 從 UWP application 啓動 win32 process
    <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
             xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
             xmlns:rescap=
    "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
             xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
      ...
      <Capabilities>
          <rescap:Capability Name="runFullTrust"/>
      </Capabilities>
      <Applications>
        <Application>
          <Extensions>
              <desktop:Extension Category="windows.fullTrustProcess" Executable="fulltrustprocess.exe">
                  <desktop:FullTrustProcess>
                      <desktop:ParameterGroup GroupId="SyncGroup" Parameters="/Sync"/>
                      <desktop:ParameterGroup GroupId="OtherGroup" Parameters="/Other"/>
                  </desktop:FullTrustProcess>
               </desktop:Extension>
          </Extensions>
        </Application>
      </Applications>
    </Package>
    • GroupId 代表要傳送 parameter 的識別值
    • Parameters 代表要使用的内容
    如果您想要建立一個 UWP 可以呼叫的 win32 元件,可以使用這個方法來宣告使用 full-trust process 的機制,並設定 packaged application 的形執行位置,例如:fulltrustprocess.exe,這樣一來 UWP app 就能直接呼叫使用。 如果您想要讓 UWP application 與 Win32 application 互相溝通,可以建立 app service 來完成,可以參考 WPF 使用 Windows 10 APIs - 1

上面介紹的内容比較詳細的範例程式,可以參考 WPF picture viewer with transition/migration/uninstallation。 在 WPF 使用 Windows 10 APIs - 1 介紹幾個常用的 Windows 10 APIs 以及 UWP app 與 AppService 的互動 (需要注意 windows.fullTrustProcess 只能在 Desktop 使用),這一篇再補充整合 UWP app 與 Background Task 的使用。

  • 建立一個 windows runtime component 的 Background Task:
    BackgroundTaskDeferral _deferral; 
    
    public async void Run(IBackgroundTaskInstance taskInstance)
    {
        _deferral = taskInstance.GetDeferral();
    
        // 利用 send toast 的方式表示有處理            
        string msg = $"收到 TimeZoneChanged 的事件,{TimeZoneInfo.Local.DisplayName}";
    
        ToastTemplateType toastTemplate = ToastTemplateType.ToastText02;
        XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);
    
        XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
        toastTextElements[0].AppendChild(toastXml.CreateTextNode(msg));
        toastTextElements[1].AppendChild(toastXml.CreateTextNode(DateTime.Now.ToString()));
    
        ToastNotification toast = new ToastNotification(toastXml);
        ToastNotificationManager.CreateToastNotifier().Show(toast);
    
        _deferral.Complete();
    }
  • 在 WPF 加入 C:\Program Files (x86)\Windows Kits\10\UnionMetadata\Windows.winmdC:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\*WindowsRuntime*.dll 的參考,並注冊 Background Task 與 entry point。
    private void RegistBackgroundTask()
    {
        var taskRegistered = false;
        var exampleTaskName = "ExampleBackgroundTask";
    
        foreach (var task in BackgroundTaskRegistration.AllTasks)
        {
            if (task.Value.Name == exampleTaskName)
            {
                taskRegistered = true;
                break;
            }
        }
    
        if (taskRegistered)
        {
            return;
        }
    
        var builder = new BackgroundTaskBuilder();
    
        builder.Name = exampleTaskName;
        builder.TaskEntryPoint = "MyBackgroundTask.ExampleBackgroundTask";
        builder.SetTrigger(new SystemTrigger(SystemTriggerType.TimeZoneChange, false));
        builder.AddCondition(new SystemCondition(SystemConditionType.UserPresent));
        BackgroundTaskRegistration registResult = builder.Register();
    }
     
  • 在 Windows Application Packaging Project 分別把 WPF 專案與 Windows runtime component 專案加入,並宣告必要的内容:
    <Proejct>  
      <PropertyGroup>
        <ProjectGuid>fd2d401e-0cd1-48df-9812-cd56909d97cf</ProjectGuid>
        <TargetPlatformVersion>10.0.17763.0</TargetPlatformVersion>
        <TargetPlatformMinVersion>10.0.15063.0</TargetPlatformMinVersion>
        <DefaultLanguage>en-US</DefaultLanguage>
        <PackageCertificateKeyFile>WapProjTemplate1_TemporaryKey.pfx</PackageCertificateKeyFile>
        <EntryPointProjectUniqueName>..\WPFWithBackgroundTask\WPFWithBackgroundTask.csproj</EntryPointProjectUniqueName>
      </PropertyGroup>
      <ItemGroup>
        <ProjectReference Include="..\WPFWithBackgroundTask\WPFWithBackgroundTask.csproj" />
      </ItemGroup>
      <ItemGroup>
        <ProjectReference Include="..\MyBackgroundTask\MyBackgroundTask.csproj" />
      </ItemGroup>
    </Project>
    <Package>
      <Applications>
        <Application>
         <Extensions>
            <Extension Category="windows.backgroundTasks" EntryPoint="MyBackgroundTask.ExampleBackgroundTask">
              <BackgroundTasks>
                <Task Type="systemEvent"/>
              </BackgroundTasks>
            </Extension>
          </Extensions>
        </Application>
      </Applications>
    
      <Capabilities>
        <Capability Name="internetClient" />
        <rescap:Capability Name="runFullTrust" />
      </Capabilities>
    </Package>

上面步驟完成就可以讓 WPF 程式呼叫 Background Task 與觸發 Windows runtime component 了。 另外,可以參考 UWP APIs available to a packaged desktop app 瞭解那些 APIs 是 packaged application 可以使用的。

[範例程式]

======

如果您的 WPF 程式不想上 Store 只是想要用到 Windows 10 APIs,可以參考 Calling Windows 10 APIs From a Desktop Application 介紹的 UWPDesktop package 方便您使用 APIs。
另外需要注意的是 2019 開始 Windows 10 支援 ARM64,目前不確定 Desktop Bridge 上去的 App 是否還有其他需要調整的地方,我也會研究再做補充。

References