Средство выбора файлов в WinUI3

WinUI 3 - Windows User Interface

Введение

Постановка задачи: Осуществить выбор файла для открытия из настольного приложения WinUI 3.

Оказалось, что найти класс Windows.Storage.Pickers.FileOpenPicker, настроить опции и вызвать метод PickSingleFileAsync() недостаточно.

Следующий код

            // Create a file picker
            var openPicker = new Windows.Storage.Pickers.FileOpenPicker();

           
// Set options for your file picker
            openPicker.ViewMode = PickerViewMode.Thumbnail;
            openPicker.FileTypeFilter.Add(
"*");

           
// Open the picker for the user to pick a file
            var file = await openPicker.PickSingleFileAsync();
           
if (file != null)
            {
               
// File picked up
            }
           
else
            {
               
// Something went wrong
            }
        }

Вызывает ошибку. Что-то вроде вот такого:
 

Сообщение об ошибке
Сообщение об ошибки

После этого пришлось начать разбираться в вопросе серьёзно.

 

Общие сведения

В Windows User Interface 3 (WinUI 3) кроме Страницы (Page), Фрейма (Frame) добавилось ещё понятие Окна (Window). И можно работать с окнами (при этом из можно создавать несколько) и можно продолжать по привычки от UWP работать со страницами. Панели (Panel) можно размещать как внутри окна, так и внутри страницы (постаринке).

WinUI полностью написан на C++ и может быть использовано в неуправляемых приложениях Windows. (У меня попутно возник вопрос: зачем тогда нужно было наворачивать C#, если возвращаемся в C++). WinUI 3 является частью Windows App SDK.

Если мы хотим работать со страницами, то класс Window содержит в атрибуте Content корневую страницу, например Shell для работы с приложением (ну или ка написано в документации – визуальный корень окна приложения).

Пространство имён Windows.Storage.Pickers предоставляет классы для создания элементов пользовательского интерфейса и управления ими, которые позволяют пользователю просматривать файлы, выбирать их для открытия и сохранения, а также выбирать имя, расширение и расположение файлов.

В UWP, код приведённый выше отлично работает. Для работы кода в WinUI 3 необходимо произвести связку экземпляра объекта FileOpenPicker с дескриптором окна (handler – HWND) соответствующего окна в операционной системе. Для этого нам необходимо выполнить два действия:

1. Получить дескриптор текущего окна.

2. Связать экземпляр объекта выбора файла с полученным дискриптором.

Microsoft рекомендует для этого следующий код:

    // 1. Retrieve the window handle (HWND) of the current WinUI 3 window.
    var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);

где window – это экземпляр окна в котором мы хотим выполнить диалог выбора файла.

    // 2. Initialize the folder picker with the window handle (HWND).
    WinRT.Interop.InitializeWithWindow.Initialize(openPicker, hWnd);

Вроде бы во всём разобрались, но остался ещё один интересный вопрос: а где взять это самое текущее окно, особенно в многооконных приложениях?

Для ответа на этот вопрос рассмотрим два варианта: один из комплексного примера от Microsoft с github, второй из шаблона Template Studio for WinUI (C#).

Разбор примера от Microsoft

Пример от Microsoft, показывающий возможности WinUI3 находится в github: microsoft/WinUI-Gallery (https://github.com/microsoft/WinUI-Gallery/tree/main).

Пример подразумевает работу с несколькими окнами, поэтому подготовка к этому выглядит несколько сложнее. Для решения задачи определения текущего окна используется специальный помощник.

//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************


using Microsoft.UI.Xaml;
using System.Collections.Generic;

namespace AppUIBasics.Helper
{
   
// Helper class to allow the app to find the Window that contains an
    // arbitrary UIElement (GetWindowForElement).  To do this, we keep track
    // of all active Windows.  The app code must call WindowHelper.CreateWindow
    // rather than "new Window" so we can keep track of all the relevant
    // windows.  In the future, we would like to support this in platform APIs.
    public class WindowHelper
    {
       
static public Window CreateWindow()
        {
            Window newWindow =
new Window();
            TrackWindow(newWindow);
           
return newWindow;
        }

       
static public void TrackWindow(Window window)
        {
            window.Closed += (sender,args) => {
                _activeWindows.Remove(window);
            };
            _activeWindows.Add(window);
        }

       
static public Window GetWindowForElement(UIElement element)
        {
           
if (element.XamlRoot != null)
            {
               
foreach (Window window in _activeWindows)
                {
                   
if (element.XamlRoot == window.Content.XamlRoot)
                    {
                       
return window;
                    }
                }
            }
           
return null;
        }

       
static public UIElement FindElementByName(UIElement element, string name)
        {
           
if (element.XamlRoot != null && element.XamlRoot.Content != null)
            {
               
var ele = (element.XamlRoot.Content as FrameworkElement).FindName(name);
               
if (ele != null)
                {
                   
return ele as UIElement;
                }
            }
           
return null;
        }

        
static public List<Window> ActiveWindows { get { return _activeWindows; }}

       
static private List<Window> _activeWindows = new List<Window>();
    }
}

Класс WindowHelper содержит полное свойство ActiveWindows, которое содержит список активных в данный момент окон и следующие методы:

CreateWindow() – создаёт новое окно и заносит его в список активных окон.

TrackWindow(Window) – добавляет окно в список активных и подключает к событию закрытия окна, удаление окна из списка активных.

GetWindowForElement(UIElement) – позволяет получить окно для текущего элемента управления.

FindElementByName(UIElement, string) – позволяет найти элемент управления по имени.

В файле App.xaml.cs создаётся окно:

/// <summary>
///
Invoked when the application is launched normally by the end user.
/// Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
///
<param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    IdleSynchronizer.Init();

    startupWindow = WindowHelper.CreateWindow();
    startupWindow.ExtendsContentIntoTitleBar =
true;
   
···

Далее в файле страны FilePickerPage.xaml.cs мы получаем текущее окно:

            // Create a file picker
            var openPicker = new Windows.Storage.Pickers.FileOpenPicker();

     
// Retrieve the window handle (HWND) of the current WinUI 3 window.
            var window = WindowHelper.GetWindowForElement(this);
            var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);

     
// Initialize the file picker with the window handle (HWND).
     WinRT.Interop.InitializeWithWindow.Initialize(openPicker, hWnd);

     
// Set options for your file picker
     openPicker.ViewMode = PickerViewMode.Thumbnail;
     openPicker.FileTypeFilter.Add(
"*");

     
// Open the picker for the user to pick a file
            var file = await openPicker.PickSingleFileAsync();
     
if (file != null)
     {
         
// Creating the text for the Texblock
         Span span = new Span();
         Run run1 =
new Run();
         run1.Text =
"Picked file: ";

         
// Adding the name of the picked file in bold
         Run run2 = new Run();
         run2.FontWeight = Microsoft.UI.Text.FontWeights.Bold;
         run2.Text = file.Name;
               
         span.Inlines.Add(run1);
         span.Inlines.Add(run2);
         PickAFileOutputTextBlock.Inlines.Add(span);    
     }
     
else
     {
          PickAFileOutputTextBlock.Text =
"Operation cancelled.";
     }

Использование в TS WinUI

В студии шаблонов используется только одно окно, оно хранится в статическом свойстве MainWindow в классе App:Application в файле App.xaml.cs.


В любом месте приложения мы можем обратиться к нему App.MainWindow. И тогда наш код будет выглядеть

            // Create a file picker
            var openPicker = new Windows.Storage.Pickers.FileOpenPicker();

     
// Retrieve the window handle (HWND) of the current WinUI 3 window.
      var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);

            // Initialize the file picker with the window handle (HWND).
     WinRT.Interop.InitializeWithWindow.Initialize(openPicker, hWnd);

     
// Set options for your file picker
     openPicker.ViewMode = PickerViewMode.Thumbnail;
     openPicker.FileTypeFilter.Add(
"*");

     
// Open the picker for the user to pick a file
            var file = await openPicker.PickSingleFileAsync();

 

Заключение

Вот такие изменения мне пришлось произвести в своём приложении и узнать что-то новое.

Надеюсь, эта информация будет вам полезна.

17.02.2023

P.S.
 Для отображения диалогового окна используем точно такой же подход:

    catch (Exception ex)
  {
      
var msgDialog = new MessageDialog(
          
$"ERROR when selecting a file: {ex.Message}.",
     
           " Selecting a file to open ");
      
// Retrieve the window handle (HWND) of the current WinUI 3 window.
             var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
     
// Initialize the file picker with the window handle (HWND).
      WinRT.Interop.InitializeWithWindow.Initialize(msgDialog, hWnd);
     
var result = await msgDialog.ShowAsync();
  }

 

Дополнения:

Дополнение 1:
Для выбора файла необходимо задать хотя бы одни фильтр типа файла .FileTypeFilter, допустимый формат только ".txt" или "*", любой другой будет вызывать ошибку.

Ключевые слова: WinnUI 3, Windows User Interface 3, Windows.Storage.Picker, PickSinglFileAsync, Выбор файла в WinUI 3.

Comments

Добавить комментарий

Код языка комментария.

Ограниченный HTML

  • Допустимые HTML-теги: <i> <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Строки и абзацы переносятся автоматически.
  • Адреса веб-страниц и email-адреса преобразовываются в ссылки автоматически.