VMT TECH BLOG

ひらけファイル! Xamarin.Macでウィンドウにドラッグアンドドロップされたファイルを開く方法!

この記事を書いた人:@nao-a(nao-aのマイページ – Qiita)

Xamarin.Mac とは

  • Mac上で動くGUIアプリをC#で作成する際のフレームワークのひとつです
  • .NetCoreで動く……と思いきや、.NetFrameworkで動く模様。。。
    • ので 別プロジェクトを参照する際は要注意(1敗)
  • macOSが搭載されたPCと、そのPCへMac用アプリである VisualStudio for Mac のインストールが必須ですので用意しておきましょう (MacMiniはまだ安いですよー)

Xamarin.Macは何が嬉しいの?

例えばWindowsとMacのアプリをそれぞれ作成しようとすると、それぞれ別々のUIとビジネスロジックを組む必要があると思いますが、以下の弊害があります

  • 製造や機能改修でWindowsとMacの両方の人員をアサインしなくてはならない
    • => 工数の倍化
  • WindowsならVBやC#、macOSならSwiftまたはObjective-Cの人員をアサインしなくてはならない
    • => それぞれ専門性が必要

macOS側をXamarin.Mac、Windows側をUWPまたはWPFを採用することで、少なくともビジネスロジックをmacOSとWindowsとで共通化することが可能となります

ビジネスロジックの共通化が可能ということは、その分製造や機能改修にかかるコストが下がることになりますし、作り方次第では、UIを触らない場合はUIのことを気にせずコーディングできるようになります

Xamarin.Macのちょっとバッドポイント

全部Windowsみたいに書けるわけではなく、UIを書く際は XcodeCocoaAPI の知識が多少必要となります。。。(でも、きっとそんなに難しくないですよ)

本記事の概要

Xamarin.Mac初心者がXamarin.Macを使ってファイルのパスを2つの方法で取得するのを試してみました!

Xamarin.Mac初心者向けになるべくハンズオン形式で記載しています(といいながら私も初心者ですが)

この記事ではタイトルを「ドラッグアンドドロップされたファイルのパスを取得する方法!」としていますが、以下の3部構成として記載しています

  • 事前準備
    • プロジェクトのセットアップについて解説します
  • 基本編
    • ボタンクリックでファイルを開くダイアログを表示してファイルを開く方法について解説します
  • 応用編
    • ウィンドウへファイルをドラッグアンドドロップしてファイルを開く方法について解説します

この記事だけで理解するのが不安でしたら、Microsoft公式の Hello, Mac – チュートリアル を読んでから本記事を読むことをおすすめします。

事前準備

ソリューション/プロジェクトの作成

VS2019forMac-スタート画面

  • 右下の 新規 をクリックします

VS2019forMac-テンプレート選択画面

  • 左側のペインから Mac > アプリ を選択します
  • Cocoaアプリ を選択します
  • 次へ を選択します

VS2019forMac-アプリの構成選択画面

  • アプリ名 を適当に入力します
    • 今回の場合は GetFilePathTest にしています
  • 必要であれば 組織の識別子 や Dock項目 を適宜変更しても構いません
  • 次へ をクリックします

VS2019forMac-Cocoaアプリの構成画面

  • プロジェクト名ソリューション名 はデフォルトの文字列で構いません
  • 必要であれば、 バージョンコントロールにGitを使用する をEnabledにしても良いでしょう
  • 作成 をクリックします

VS2019forMac-プロジェクト作成直後

  • このようなウィンドウが表示されれば、ひとまず作成は完了です

素のプロジェクト作成直後にビルド/実行した時のウィンドウ

  • 先ほどのウィンドウの左上の ボタンを押下するとビルドと実行がはじまります
  • 上記のようなプレーンなウィンドウが表示されればビルドと実行に成功しています

基本編 – ボタンを押してファイルをオープンする方法

UIを作成する

VS2019forMac-Main.storyboardをXcodeInterfaceBuilderで開く

  • まずはUIを作成していきます
  • Main.storyboard を右クリックし、 プログラムから開く > XcodeInterfaceBuilder と選択していきます

Xcode-InterfaceBuilder初期画面

  • すると、このような形でXcodeが起動してきます
  • 同じく Main.storyboard をクリックして、storyboardを開きます

Xcode-InterfaceBuilder初期画面-ViewControllerにフォーカス

  • ボタンとラベルのオブジェクトを適当に画面に配置します
  • この時、必ず ViewController の方に配置するようにしてください
    • ボタンとラベルの配置の仕方は以下の通りです
      1. Cmd + Shift + L でオブジェクトライブラリが表示されます
      2. そこから使用したいコンボーネントをstoryboard上にドラッグアンドドロップで配置します
      3. 適当にテキストを変更します

Xcode-InterfaceBuilder-ButtonとViewController.hにOutletのSegueを張る

  • 次にSegueをstoryboardからコードに設置します
  • 設置の仕方は次の通りです
    1. Xcodeでstoryboardを開いている状態で、ウィンドウ右上にある 横線がたくさんあるボタン をクリックします

Xcode-InterfaceBuilder-AssistantViewを表示する1

  • Assistant を選択します
  • 以下の画面で ViewController.h となっている箇所がなっていない場合は、 ViewController.h に変更します

Xcode-InterfaceBuilder-AssistantViewを表示する2

  • Segueを設置したいオブジェクトコントロールを右クリックした状態でドラッグし、先ほどの右ペインの @interface … 以下の箇所にドロップします
  • すぐに先ほどの画像が表示されるので、 name 欄を記入し、 connect ボタンをクリックします
    • この例では buttonOpenFile とします

Xcode-InterfaceBuilder-LabelとViewController.hにOutletのSegueを張る

  • ラベルについても同様にSegueを張ります
    • この例では labelFilePath とします

Xcode-InterfaceBuilder-ButtonとViewController.hにActionのSegueを張る

  • 再度、ボタンに対してSegueを張ります
  • この時、 ConnectionAction に変更します
    • この例では nameOnClickButtonOpenFile とします

Xcode-InterfaceBuilder-AssistantView-状態確認

  • Assistantビューがこのような状態になっていれば完了です
  • 保存しましょう

VS2019forMac-ViewController.designer.csのイメージ

  • ここでVisualStudioに戻ってみます
  • ViewController.cs の中に ViewController.designer.cs が含まれています
  • それを開くと上の画像のような表示になると思います
  • Xamarin.MacではUI部分の対応をXcodeに委ねておりますが、それとC#をこのような designer.cs でクッションすることで対応していることがわかります
作成したUIから、ファイルを開くダイアログを表示させる
  • 次のコードを ViewController.cs のクラス内に追加します
    • この時のメソッド名は先ほど Button のSegueを指定する時に指定した name と同一の物である必要があります
partial void OnClickButtonFileOpen(Foundation.NSObject sender)
{
    var dlg = NSOpenPanel.OpenPanel;
    dlg.CanChooseFiles = true;
    dlg.CanChooseDirectories = false;
    dlg.AllowedFileTypes = new string[] { "txt", "html", "md", "css" };

    if (dlg.RunModal() == 1)
    {
        // Nab the first file
        var url = dlg.Urls[0];

        if (url != null)
        {
            var path = url.Path;
            labelFilePath.StringValue = path;
        }
    }
}

VS2019forMac-ファイルオープンモーダル実装例

  • こんな感じになります

ボタンやラベル、Segueなどを実装した状態の実行したウィンドウ

  • ビルドして実行を行うと、きちんと作成したウィンドウが反映されて表示されると思います
  • ファイルを開く をクリックしましょう

ファイルを開くボタンをクリックした時に表示されるダイアログ

  • すると、このようなダイアログが表示されます
  • 任意のファイルを選択して、右下の Open をクリックします

ファイルを開くダイアログからファイルを選択した後の実行したウィンドウ

  • 先ほどまで「ここに選択したファイルのパスが表示されます」となっていた箇所がパスに変わっていると思います
  • このようになっていれば成功です

応用編 – ウィンドウにドラッグアンドドロップしたファイルのパスを取得する

カスタムビューを作成する

カスタムビュー作成 プロジェクト側設定

Xcode-ファイルを追加

  • Xcode上でソースの存在するディレクトリを右クリックします
  • New File… を選択します

Xcode-CocoaClassを追加する1

  • Cocoa Class を選択します
  • Next をクリックします

Xcode-CocoaClassを追加する2

  • Class欄は任意の名前を設定します
    • この例では DragAndDropView を指定しています
  • Subclass ofNSView を指定します
  • LanguageObjective-C で良いです
  • Next をクリックします

Xcode-CocoaClassを追加する3

  • おもむろに Create をクリックします

Xcode-CocoaClassを追加する4

  • このようなかたちでクラスが作成されます

VS2019forMac-C#でViewを追加する1

  • VisualStudioに戻り
  • プロジェクト を右クリックし、 追加 > 新しいファイル を選択します

VS2019forMac-C#でViewを追加する2

  • 左ペインから Mac を選択し
  • 中央ペインから ビュー を選択します
  • ファイル名は任意のビューとわかる、任意の名前を設定します
    • ここでは先ほど設定した DragAndDropView とします

VS2019forMac-C#でViewを追加する3

  • このようなコードが表示されればOKです

実際のコーディング

  • ここからこのコードをこのqiita記事を元に肉付けしていきます
    • 元記事はフルObjective-Cですので、C#コードに置き換えていきましょう
public interface DragAndDropViewDelegate { /// <summary> /// ビュー上にファイルがD&Dされた場合に呼ばれる /// </summary> /// <param name="files">ファイルパス一覧</param> void DragAndDropViewGetDraggingFiles(NSArray files); }
  • まず、ViewControllerとViewの間でデータを伝送する取り決めを作らないとなりません
  • 上記のコードがそれを担ってくれます
  • ここからは一気に記載します
public partial class DragAndDropView : AppKit.NSView { /// <summary> /// マウスドラッグアンドドロップ時のフォーカスフラグ /// </summary> private bool _isHighlight = false; /// <summary> /// ドラッグアンドドロップされた後の処理を移譲するためのインターフェースオブジェクト /// </summary> public DragAndDropViewDelegate Delegate; #region Constructors // Called when created from unmanaged code public DragAndDropView(IntPtr handle) : base(handle) { Initialize(); } // Called when created directly from a XIB file [Export("initWithCoder:")] public DragAndDropView(NSCoder coder) : base(coder) { Initialize(); } // Shared initialization code void Initialize() { _isHighlight = false; RegisterForDraggedTypes(new string[] { NSPasteboard.NSFilenamesType }); } /// <summary> /// Viewの描画処理 /// View上にファイルがドラッグされているならば、ハイライトをつける /// </summary> /// <param name="dirtyRect"></param> public override void DrawRect(CGRect dirtyRect) { base.DrawRect(dirtyRect); if (_isHighlight) { NSColor.SystemBlueColor.Set(); NSBezierPath.DefaultLineWidth = 5; } else { NSColor.SystemGrayColor.Set(); NSBezierPath.DefaultLineWidth = 1; } NSBezierPath.StrokeRect(Bounds); } /// <summary> /// Viewの境界にファイルがドラッグされるときに呼ばれる /// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。 /// </summary> /// <param name="sender"></param> /// <returns></returns> public override NSDragOperation DraggingEntered(NSDraggingInfo sender) { _isHighlight = true; NeedsDisplay = true; return NSDragOperation.Generic; } /// <summary> /// View上にファイルがドラッグで保持されている間、短い間隔毎に呼ばれるメソッド /// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。 /// </summary> /// <param name="sender"></param> /// <returns></returns> public override NSDragOperation DraggingUpdated(NSDraggingInfo sender) { _isHighlight = true; NeedsDisplay = true; return NSDragOperation.Generic; } /// <summary> /// View上にファイルがドラッグされなくなった際に呼ばれる /// </summary> /// <param name="sender"></param> public override void DraggingExited(NSDraggingInfo sender) { _isHighlight = false; NeedsDisplay = true; } /// <summary> /// View上でファイルがドロップされた際に呼ばれる /// メッセージが返された場合はtrue、performDragOperation:メッセージが送信されます。 /// </summary> /// <param name="sender"></param> /// <returns></returns> public override bool PrepareForDragOperation(NSDraggingInfo sender) { _isHighlight = false; NeedsDisplay = true; return true; } /// <summary> /// View上でファイルがドロップされた後の処理 /// </summary> /// <param name="sender"></param> /// <returns></returns> public override bool PerformDragOperation(NSDraggingInfo sender) { NSArray draggedFilenames = (NSArray)sender.DraggingPasteboard.GetPropertyListForType(NSPasteboard.NSFilenamesType); for (uint i=0; i<draggedFilenames.Count; i++) { bool isDir = false; if (!NSFileManager.DefaultManager.FileExists(draggedFilenames.GetItem<NSString>(i), ref isDir)) { return false; } if (isDir == true) { return false; } } return true; } /// <summary> /// 一連のドラッグ操作が完了したときに呼ばれる /// </summary> /// <param name="sender"></param> public override void ConcludeDragOperation(NSDraggingInfo sender) { NSArray filePathes = (NSArray)sender.DraggingPasteboard.GetPropertyListForType(NSPasteboard.NSFilenamesType); Delegate.DragAndDropViewGetDraggingFiles(filePathes); } #endregion }

作ったViewのマッピング

Xcode-InterfaceBuilder-CustomView(DragAndDropView)を追加1

  • Xcodeのstoryboardに戻り、オブジェクトリストを表示します
    • Cmd + Shift + L で表示できます
  • CustomView を選択し、ViewController上に貼り付けます

Xcode-InterfaceBuilder-CustomView(DragAndDropView)を追加2

  • こんな状態になればOKです (例ではWindowいっぱいにCustomViewを広げています)

Xcode-InterfaceBuilder-CustomView(DragAndDropView)を追加3

  • 先ほど追加した CustomView にフォーカスを合わせた状態にします
  • 右ペインのなんかごちゃごちゃしているところから新聞紙みたいなアイコンを選択し
  • CustomClass を、先ほど作成した DragAndDropView に変更します

Xcode-InterfaceBuilder-CustomView(DragAndDropView)を追加4

  • 先ほど行った方法で、 ViewController.h にカスタムビューのSegueを張ります
  • 最後にVisualStudioに戻って、ViewController.csに修正を加えましょう
public partial class ViewController : NSViewController, DragAndDropViewDelegate
  • まずクラス定義から
  • 先ほど作成した DragAndDropViewDelegate を implement しましょう
public override void ViewDidLoad() { base.ViewDidLoad(); // Do any additional setup after loading the view. dragAndDropView.Delegate = this; }
  • dragAndDropView.Delegate = this; が追加部分です
  • 先ほどのSegue定義で ViewController.csdragAndDropView にアクセスできますので、 Delegate にそれが実装される this を代入しましょう
public void DragAndDropViewGetDraggingFiles(NSArray files) { string pathes = ""; for (uint i=0; i<files.Count; i++) { pathes += files.GetItem<NSString>(i) + ", "; } labelFilePath.StringValue = pathes; }
  • 最後に Delegate の実装です
  • ここでは受け取った NSArray から NSString を一つずつ取得し、最終的に画面に表示するようにしています

最終的にこうなります

応用編完成映像

今回のファイルはgithubに公開しています。
こちら

参考文献

more

新着記事一覧

バルテス株式会社
VALTES Advanced Technology, Inc.