【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

Gtk#でファイルをドラッグ&ドロップ

f:id:n7shi:20121025213121p:plain:right

Gtk#のウィジェットにファイルをドラッグ&ドロップで渡します。

受け取る種類を指定しておくと、ドロップしたときにDragDataReceivedシグナルが発生します。ファイルは "text/uri-list" です。URIで渡されるため、ローカル名に変換が必要です。

using System;
using System.Text;
using Gtk;

class MainClass
{
    public static void Main ()
    {
        Application.Init ();
        var win = new Window ("Test DnD") {
            DefaultSize = new Gdk.Size (100, 100)
        };
        win.DeleteEvent += (o, args) => {
            Application.Quit ();
            args.RetVal = true;
        };
        var vbox = new VBox (false, 5);
        var label = new Label ();
        var text = new TextView ();
        vbox.Add (label);
        vbox.Add (text);
        win.Add (vbox);

        var targets = new [] {
            new TargetEntry ("text/uri-list", TargetFlags.OtherApp, 0)
        };
        Drag.DestSet (label, DestDefaults.All, targets, Gdk.DragAction.Copy);
        Drag.DestSet (text, 0, targets, Gdk.DragAction.Copy);
        label.DragDataReceived += HandleDragDataReceived;
        text.DragDataReceived += HandleDragDataReceived;

        win.ShowAll ();
        Application.Run ();
    }

    static void HandleDragDataReceived (object o, DragDataReceivedArgs args)
    {
        Console.WriteLine (args.Time);
        var uris = Encoding.ASCII.GetString (args.SelectionData.Data)
            .Replace ("\r", "").Replace ("\0", "").Trim ().Split ('\n');
        foreach (var s in uris) {
            var uri = new Uri (s);
            if (uri.IsFile)
                Console.WriteLine (uri.LocalPath);
        }
    }
}

注意点

Drag.DestSet() はLabelとTextViewで挙動が異なります。Labelでは2番目の引数にDestDefaults.Allを指定する必要があります。しかしTextViewでDestDefaults.Allを指定するとなぜか2度イベントが通知され、Windowsでは2回目の通知で妙なファイルが渡されます(Xでは1回目と同じ)。そのためゼロを指定しておく必要があります。

【追記】WindowsではLabelにDestDefaults.Dropを指定してもドロップを受け付けますが、Xでははじかれました。

これはC言語からGTK+を直接使用しても同じ結果となりました。GTK+本来の挙動だと思われます。

C言語で同じコードを書いてみると、改めてGtk#が薄いラッパーだと思いました。