Xamarin.Forms - UWP でファイルドロップ

Xamarin.Forms - UWP でファイルドロップ

  • Xamarin.Forms - UWPでファイルをドロップしたい
  • カスタム レンダラー 利用

環境

準備

UWPのドラッグドロップ関連の処理は、下記から拝借。

https://docs.microsoft.com/ja-jp/windows/uwp/design/windows-template-studio/

  • 1:Windows Template Studio で、UWPプロジェクト作成

f:id:kawaishi2:20210608153837p:plain

  • 2:機能で Drag & Drop を追加。(それ以外は不要)

f:id:kawaishi2:20210608153927p:plain

  • 3:出来たプロジェクトから、D&D関連のファイルだけコピーして、 XamarinのUWPプロジェクトに追加。(プラットフォームのプロジェクト)

f:id:kawaishi2:20210608153938p:plain

↑ のファイルをコピペ ※ ネームスペースを適当に修正


Xamarin-UWP プロジェクトでドロップ

1. プロジェクト作成

f:id:kawaishi2:20210608153950p:plain

f:id:kawaishi2:20210608154002p:plain


2. 共通プロジェクトのViewにFrame 作成

  • 今回はFrame にドロップ(したい)
  • Frameの子要素の子要素に Entry を配置(ドロップしたファイルパスを表示)
  • ViewModel とバインド。ViewModelの String に EntryのTextをバインド
  • FrameやEntry コントロールに x:Name で名前付けとく(後で使う)
共通プロジェクトのView

<Frame  x:Name="TopFrame">

    <xct:DockLayout>

        <Entry
            x:Name="EntryTop"
            Text="{Binding Path=SourcePath, Mode=TwoWay}"  >
        </Entry>

    </xct:DockLayout>
</Frame>

・・・略
共通プロジェクトのViewModel

public class MainViewModel : BaseViewModel
{
    private string sourcePath = string.Empty;
    public string SourcePath
    {
        get { return this.sourcePath; }
        set { SetProperty(ref sourcePath, value); }
    }

    private string targetPath = string.Empty;
    public string TargetPath
    {
        get { return this.targetPath; }
        set { SetProperty(ref targetPath, value); }
    }
・・・略

ここまでは特に特別な箇所はない。


3. UWP プロジェクト側にカスタムレンダラー

通常だと、共通プロジェクトでFrameをサブクラス化したカスタムコントロールを作成する・・・んだけど、今回は横着して省略。

下準備

UWPドロップ用のファイルを追加しとく

f:id:kawaishi2:20210608154029p:plain

カスタムレンダラー作成

  • FrameだからFrameRenderer
  • (横着して)標準のFrameを対象
    • Xamarin.Forms.Frame を直で指定。普通は MyFrameとか作る
カスタムレンダラー クラス:

[assembly: ExportRenderer(typeof(Xamarin.Forms.Frame), typeof(CustomFrameRenderer))]

namespace MyFormPrj.UWP.CustomViews
{
    public class CustomFrameRenderer : FrameRenderer

・・・略

カスタムレンダラー:OnElementChanged

  • OnElementChanged で色々やっているけど、この辺はイマイチわかってない。
  • OnLoaded とかあれば安心なんだけが…。
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
    base.OnElementChanged(e);

    // ドロップ可
    this.Control.AllowDrop = true;

    // ドロップ処理を作成
    var fileDropCmd = new ReactiveCommand<IReadOnlyList<IStorageItem>>().WithSubscribe(x => this.OnGetStorageItem(x));

    // D&D を登録
    DragDropService.SetConfiguration(this.Control, new DropConfiguration() { DropStorageItemsCommand = fileDropCmd });

    return;
}
  • ドラックドドロップ処理をFrameに登録する
  • XAMLで書くようなことをコードで書いているだけ

カスタムレンダラーからフォームへ

public void OnGetStorageItem(IReadOnlyList<IStorageItem> items)
{
    foreach (var item in items)
    {

        switch (this.Element.StyleId)
        {
            case "TopFrame":
                {
                    Xamarin.Forms.Entry entryElement = this.Element.Content.FindByName("EntryTop") as Xamarin.Forms.Entry;

                    // コントロールに直接格納
                    entryElement.Text = item.Path;
                    break;
                }
            case "BottomFrame":
                {
                    Xamarin.Forms.Entry entryElement = this.Element.Content.FindByName("EntryBottom") as Xamarin.Forms.Entry;

                    // (フォーム側で)バインドしているモデルに格納
                    var model1 = entryElement.BindingContext as MyFormPrj.ViewModels.MainViewModel;
                    model1.TargetPath = item.Path;

                    break;
                }

            default:
                break;
        }

        // for文に意味はない
        break;
    }
}
  • items はドロップされたファイル/フォルダ
  • Frame全部にドロップ処理を入れているので、どのフレームにドロップされたかわからない。
  • ので、x:Name に付けた名前で判別(イケてない)

共通プロジェクトに値を渡す

  • 渡し方1:entryElement.Text(←共通プロジェクトのEntryのテキスト) = item.Path(UWPでドロップされたファイルパス)

  • バインドしているモデルに(無理くり?)アクセスしてもいい(のか?)

  • 渡し方2:var model1 = entryElement.BindingContext as MyFormPrj.ViewModels.MainViewModel;

f:id:kawaishi2:20210608154050g:plain


4. 感想

  • 以前やった時よりも、シンプルにかけた(気がする)👍
  • View(レンダラー)を通してデータをやりとりするのにモヤッとする
  • x:Name の名前(文字列)で判定してる箇所にモヤッとする