Введение
Постановка задачи: Осуществить выбор файла для открытия из настольного приложения 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
Добавить комментарий