こんにちは!今回ブログを担当することになりましたエンジニアの山元です。
デザイナー業務では、画像のリサイズ処理を必要とする状況がしばしば見受けられます。
そのため迅速に対応できる支援ツールがあれば負担軽減に繋がると考え、現在Xamarin.Macを利用して実装しています。
画像入力が簡単になるよう、ドラッグ&ドロップ機能を入れたいと調査していたのですが、
あまり情報を見つけられなかったこともあり、一連の実装作業をメモとして残そうと思います。
同じようなことをしたいと考えている方がいれば、参考にして頂ければ幸いです。
準備
実装環境は以下の通りです。
・macOS Catalina 10.15.4
・Visual Studio for Mac 8.5.4
・Xcode 11.5
初めに、Visual Studioで新しいプロジェクトを作成しておきます。
Cocoaアプリを選択し、任意のプロジェクト名と作成場所を設定する手順を踏みます。

↓ 作成されたプロジェクト

実装手順
以下の手順で進めます。
- Storyboard上のViewControllerScene内にCustomViewを配置し、操作用クラス”DropImageView”を設定
- 設定した”DropImageView”クラスに、ドラッグ&ドロップ用のコードを記述
- プロジェクトを実行し、任意の画像をウィンドウ内にドラッグ&ドロップして、画像が表示されたら完成
手順1
Visual Studioのソリューションエクスプローラーに"Main.storyboard"の項目があるので、右クリックして「プログラムから開く → Xcode Interface Builder」を選択し、Xcodeを開きます。

Xcodeの右上にある"+アイコン"(Library)を押し、CustomViewを検索してViewControllerScene内に配置します。

サイズや位置を変更して自由に配置した後、右側のユーティリティ・エリアに"Custom Class"の項目があるので、ここに任意のクラス名を記述します。

手順2
Visual Studioに戻ると自動的にXcodeと同期され、手順1で設定したクラスがソリューションエクスプローラーに追加されます。

ドラッグ&ドロップ用のコードを、追加されたクラスに記述していきます。
コードの全容は以下の通りです。
// This file has been autogenerated from a class added in the UI designer.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Foundation;
using AppKit;
using CoreGraphics;
namespace Drag_and_Drop
{
    public partial class DropImageView : NSView
    {
        public DropImageView (IntPtr handle) : base (handle)
        {
            //初期化処理を呼ぶ
            this.Initialize();
        }
        //初期化
        private void Initialize()
        {
            //ファイルのドラッグ&ドロップを受付可能に
            this.RegisterForDraggedTypes(new string[] { NSPasteboard.NSFilenamesType });
        }
        //ドラッグされたファイルのドロップ判定
        public override NSDragOperation DraggingEntered(NSDraggingInfo sender)
        {
            //受け入れるファイルリスト
            var files = this.GetFiles(sender);
            //拡張子がpngで、1つのみドラッグしていた場合にドロップする
            if (files.Length == 1)
            {
                //ドラッグしたファイルをコピーしてドロップ
                return NSDragOperation.Copy;
            }
            return NSDragOperation.Generic;
        }
        //受け入れるファイルリストを取得
        private string[] GetFiles(NSDraggingInfo info)
        {
            //受け入れるファイルリスト
            var files = new List<string>();
            var availableType = info.DraggingPasteboard.GetAvailableTypeFromArray(RegisteredDragTypes());
            if (availableType == NSPasteboard.NSFilenamesType)
            {
                //ドラッグされてきたファイルリスト
                var lists = info.DraggingPasteboard.GetPropertyListForType(NSPasteboard.NSFilenamesType);
                if (lists is NSArray)
                {
                    //対象の拡張子だったら受け入れる (今回は"png")
                    var file = lists as NSArray;
                    for (nuint i = 0; i < file.Count; i++)
                    {
                        var item = file.GetItem<NSString>(i);
                        var ext = Path.GetExtension(item).ToLower();
                        if (ext == ".png")
                        {
                            files.Add(item);
                        }
                    }
                }
            }
            return files.ToArray();
        }
        //ドロップされたファイルの処理
        public override bool PerformDragOperation(NSDraggingInfo sender)
        {
            //受け入れるファイルリスト
            var files = this.GetFiles(sender);
            //ファイルを1つだけドラッグしていた場合のみ受け入れる
            if (files.Length != 1)
            {
                return false;
            }
            //ファイル情報を取得
            var file = files[0];
            var img = new NSImage(file);
            //ウィンドウ内に画像を表示するため、画像サイズを調整
            var imgResize = this.AspectFitSizeForMaxDimension(img);
            //既に画像が表示されていた場合は、その画像を削除する
            var drawImageViewChildren = this.Subviews;
            foreach (var child in drawImageViewChildren)
            {
                if (child is NSImageView)
                {
                    child.RemoveFromSuperview();
                    break;
                }
            }
            //画像をウィンドウ内に貼り付ける (中央に表示されるように位置を設定)
            var imgView = new NSImageView(new RectangleF(
                (float)this.Frame.Size.Width / 2 - (float)imgResize.Width / 2, (float)this.Frame.Size.Height / 2 - (float)imgResize.Height / 2, (float)imgResize.Width, (float)imgResize.Height
                ))
            {
                Image = img
            };
            this.AddSubview(imgView);
            return true;
        }
        private CGSize AspectFitSizeForMaxDimension(NSImage img)
        {
            //ウィンドウサイズ
            var minSize = Math.Min(this.Frame.Size.Width, this.Frame.Size.Height);
            float maxDimension = (float)minSize - 10.0f;
            //画像サイズ
            var imgWidth = (float)img.Size.Width;
            var imgHeight = (float)img.Size.Height;
            //ウィンドウサイズより画像が大きければ、比率を保ったまま画像を縮小させる
            if (imgWidth > maxDimension || imgHeight > maxDimension)
            {
                if (imgWidth == imgHeight)
                {
                    imgWidth = maxDimension;
                    imgHeight = maxDimension;
                }
                else if (imgWidth > imgHeight)
                {
                    var aspectRatio = imgWidth / imgHeight;
                    imgWidth = maxDimension;
                    imgHeight = maxDimension / aspectRatio;
                }
                else if (imgWidth < imgHeight)
                {
                    var aspectRatio = imgHeight / imgWidth;
                    imgWidth = maxDimension / aspectRatio;
                    imgHeight = maxDimension;
                }
            }
            CGSize resize = new CGSize(imgWidth, imgHeight);
            return resize;
        }
    }
}
手順3
プロジェクトを実行し、任意のpngファイルをドラッグしてウィンドウ上にドロップすると、画像が表示されます。
続けて他pngファイルをドラッグ&ドロップすると、新しい画像に更新されます。



まとめ
ドラッグ&ドロップ処理について、基本的な実装を紹介しました。
他にも複数画像をドラッグ&ドロップしてウィンドウ内にランダムに配置したり、ウィンドウ内の画像をドラッグして自由に移動させたりなど、色々応用が効きます。
今回の内容から発展させて試してみてくださいね。
