Xamarin.MacのウインドウでDrag&Dropを受け入れる

## Xamarin.MacにDrag&Drop

忘備録です。

Window（正確にはView）でDrag&Dropを受け入れる方法です。
と言っても、Objective-Cと同じことするだけなのですが。

なお、[アドベントカレンダーで書いた方法](https://qiita.com/yakumomo/items/05ca19562b11de123282)でXamarin.FormsのページをViewの上に被せる方法を使った場合でも親ViewでDrag&Dropを捕まえてくれます。

## ファイルをDropできるようにする

### NSViewのサブクラスを作成する

```c-sharp
namespace Views
{
    public class MainView : AppKit.NSView
    {
        public MainView(IntPtr handle) : base(handle)
        {
            Initialize();
        }

        [Export("initWithCoder:")]
        public MainView(NSCoder coder) : base(coder)
        {
            Initialize();
        }

        void Initialize()
        {
        }
    }
}
```

ソリューションビュー内で *右クリック→追加→新しいファイル* として、サブクラスを作り、xibでもstoryboardでも割り付けて使えるようにします。

### Dropできる形式を登録する

`Initialize`関数内でDropできる形式を登録します。

```c-sharp
        void Initialize()
        {
            RegisterForDraggedTypes(new string[] { NSPasteboard.NSFilenamesType });
        }
```

これで、View内でファイルをDropできると認識させておきます。

### DragされてきたときにDropで受け入れるかチェックする

```c-sharp
        string[] GetFilesFromDraggingInfo(NSDraggingInfo info)
        {
            var pb = info.DraggingPasteboard;

            var files = new List<string>();
            var t = pb.GetAvailableTypeFromArray(RegisteredDragTypes());
            if (t == NSPasteboard.NSFilenamesType)
            {
                var lists = pb.GetPropertyListForType(NSPasteboard.NSFilenamesType);
                if (lists is NSArray)
                {
                    var arr = lists as NSArray;
                    for (nuint i = 0; i < arr.Count; i++)
                    {
                        var item = arr.GetItem<NSString>(i);
                        var ext = Path.GetExtension(item).ToLower();
                        if (ext == ".epub")
                        {
                            files.Add(item);
                        }
                    }
                }
            }

            return files.ToArray();
        }

        public override NSDragOperation DraggingEntered(NSDraggingInfo sender)
        {
            var files = GetFilesFromDraggingInfo(sender);

            if(files.Length > 0)
            {
                return NSDragOperation.Copy;
            }

            return NSDragOperation.Generic;
        }
```

`GetFilesFromDraggingInfo`関数で行なっているのは、Dragされてきたものがファイルで拡張子がepubか確認し、そのファイル名のみを配列で抜き出す、ということを行なっています。

`DraggingEntered`関数は、DragされてView内に入ってきたときに呼び出される関数で、受け入れるなら受け入れる方法を返答します。
以下の形式を返すようにします。

<dl>
<dt>NSDragOperation.None</dt>
<dd>何もしない</dd>
<dt>NSDragOperation.Copy</dt>
<dd>コピーとして受け入れる</dd>
<dt>NSDragOperation.Link</dt>
<dd>リンクとして受け入れる</dd>
<dt>NSDragOperation.Move</dt>
<dd>移動として受け入れる</dd>
<dt>NSDragOperation.Generic</dt>
<dd>何もしない（Noneとは意味合いが違う）</dd>
</dl>

返答により、マウスカーソル周りの表示が色々変わります。

### Dropを受け入れる

```c-sharp
        public override bool PerformDragOperation(NSDraggingInfo sender)
        {
            var files = GetFilesFromDraggingInfo(sender);

            foreach (var file in files)
            {
                Debug.WriteLine($"drop file:{file}");
            }

            return files.Length > 0;
        }
```

`PerformDragOperation`はDropを受け入れるかどうかを最終的に判断する部分です。
マウスボタンを離した瞬間に呼ばれます。
trueを返すと受け入れ、falseを返すと受け入れない、となっており、これによって戻っていくアニメーションなどが表示されます。

## Object-Cそのまま

実は、[ここ（ドラッグ＆ドロップされた複数のファイルのパスを得るカスタムView:Qiita）](https://qiita.com/7375u@github/items/dda64c846b2b40b2e0aa)とほとんど同じです。
見比べればXamarin.Macの作法的なものがわかります？