.NET MAUI Advent Calendar 2024 1日目の記事です。
TL;DR;
やりたいこと
アプリケーションにファイルを投げ込んで、そのファイルを表示させたいと思ったことはありますか?
MAUI Drag and Drop
とかで調べてみると、アプリケーション内の画像をドラッグアンドドロップで移動するチュートリアルが出てきますが、それではなく**アプリケーションの外からファイルを読み込みたい!!**という内容です。
この資料では、使い方のみ解説しています。詳細は公式ドキュメントをご覧ください。
(.NET 8 以上で動きます)
動かす
テンプレートの MAUI アプリケーションを作成します。
UI 部分
画像とラベル部分を以下のように変更します。
<Image
+ x:Name="image"
Source="dotnet_bot.png"
HeightRequest="185"
Aspect="AspectFit"
- SemanticPropeties.Description="dot net bot in a race car number eight" />
+ SemanticPropeties.Description="dot net bot in a race car number eight">
+ <Image.GestureRecognizers>
+ <DropGestureRecognizer Drop="OnDropGestureRecognizerDrop" />
+ </Image.GestureRecognizers>
+ </Image>
...
<Label
Text="Hello, World!"
+ x:Name="path"
... />
ドラッグアンドドロップは GestureRecognizers
を利用します。
今回は、画像部分にはドロップされた画像を、テキスト部分にはドロップされたファイルのパスを表示するため、x:Name
で識別子をつけておきます。
処理部分
コード (C#) 側は、以下のようにします。
#if WINDOWS
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
#elif IOS || MACCATALYST
using UIKit;
using Foundation;
using System.Diagnostics;
#endif
async void OnDropGestureRecognizerDrop(object? sender, DropEventArgs e)
{
var filePaths = new List<string>();
#if WINDOWS
if (e.PlatformArgs is not null && e.PlatformArgs.DragEventArgs.DataView.Contains(StandardDataFormats.StorageItems))
{
var items = await e.PlatformArgs.DragEventArgs.DataView.GetStorageItemsAsync();
if (items.Any())
{
foreach (var item in items)
{
if (item is StorageFile file)
filePaths.Add(item.Path);
}
}
}
#elif IOS || MACCATALYST
var session = e.PlatformArgs?.DropSession;
if (session == null)
return;
foreach (UIDragItem item in session.Items)
{
var result = await LoadItemAsync(item.ItemProvider, item.ItemProvider.RegisteredTypeIdentifiers.ToList());
if (result is not null)
filePaths.Add(result.FileUrl?.Path!);
}
foreach (var item in filePaths)
{
Debug.WriteLine($"Path: {item}");
}
static async Task<LoadInPlaceResult?> LoadItemAsync(NSItemProvider itemProvider, List<string> typeIdentifiers)
{
if (typeIdentifiers is null || typeIdentifiers.Count == 0)
return null;
var typeIdent = typeIdentifiers.First();
if (itemProvider.HasItemConformingTo(typeIdent))
return await itemProvider.LoadInPlaceFileRepresentationAsync(typeIdent);
typeIdentifiers.Remove(typeIdent);
return await LoadItemAsync(itemProvider, typeIdentifiers);
}
#endif
// string filePath = filePaths.FirstOrDefault();
// Process the dropped file
path.Text = filePaths.FirstOrDefault();
image.Source = filePaths.FirstOrDefault();
}
(公式ドキュメントのものを改変)
コードを見てわかるとおり、Windows と iOS + macOS で異なる処理をしてファイルを読み込んでいます。(Android は非対応)
複数のファイルがドロップされることがあるため、List として管理 (var filePaths = new List<string>();
) し、表示の際には1番目のファイル (filePaths.FirstOrDefault()
) を利用しています。
結果
このサンプルを動かすと以下のようになります。
(画像ファイルをドロップする)
(画像が変更され、画像のパスが表示される)
以上です。
先程の
path.Text = filePaths.FirstOrDefault();
image.Source = filePaths.FirstOrDefault();
だった部分を別の処理に変えることで、ドロップされたファイルを簡単に操作できますよ!
その他
何故かよくわからないけど、Intellisence があんまうまく効いていないみたいです。
(XAML のほうで <DropGestureRecognizer Drop="OnDropGestureRecognizerDrop" />
と設定しているのに、OnDropGestureRecognizerDrop
が使われていないという警告が出ています。現在の .NET 9 でも直っていないです)
最後に
この方法を見つけるのに半年以上かかりました…
もともと .NET 9 での新機能 (タイトルバーのカスタマイズとか) で書こうと思っていたのですが、試す時間がなくてやめました。結構便利そうなので今度試してみたいですね。