none
ドラッグしているデータのパスと、ドラッグ先のデータのパスを取得したいです。 RRS feed

  • 質問

  • こんにちは。
     
    Windows標準のエクスプローラでフォルダやアイコンをドラッグした時にイベントを拾い、ドラッグしているデータのパスと、ドラッグ先のデータのパスを取得したいです。

    環境は、
     OS:Windows7
     開発環境:Visual Studio 2010(.Net Framework4.0)
     開発言語:C#
     です。

    先日、自分から起動したエクスプローラで選択した状態を取得するという質問があり、試してみました。

    あたりをつけたのは、

    Type t = Type.GetTypeFromCLSID(new Guid("C08AFD90-F2A1-11D1-8455-00A0C91F3880"));
    object o = Activator.CreateInstance(t);
    explorer = (ShellBrowserWindow)o;
    explorer.NavigateComplete2 += explorer_NavigateComplete2;

    のところです。
    ここで、explorer.まで打ってインテリセンスを見てみました。
    NewWindow2とかはあるのですが、DragDropとかは見当たりませんでした。
     そもそもここではないのでしょうか?

    エクスプローラで、ドラッグ&ドロップするとき、ドロップ先が.txtとかだと、そもそもドロップ可能になりませんが、その場合でもドロップ先のフルパスを取得したいです。

    ご存知の方おられましたらご教示願います。
     よろしくお願い致します。

    2013年6月13日 3:10

回答

  • ドロップされる側の処理を記述したいです。

    「ドラッグ開始時に、ドラッグされているファイル名を得る」のではなく、
    「ドラッグ完了時に、ドロップ元のファイル名を得る」のが目的なのでしょうか?

    何のためにそれが必要なのかにもよりますが、その場合はドロップされる側を個別に実装していく必要があるかと思います。つまり、editor.exe にドロップする場合なら、editor.exe のソースコードに手を加える必要があるということです。

    (1)このeditor.exeに01.txtをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。

    editor.exe 側で、コマンドライン引数を取得するようにしてください。
     DOBON.NET - 『 起動時のコマンドライン引数を取得する 』

    (2)この01.txtに02.docをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。

    文書ファイルの場合は……Drop Handlers を用意しないといけないかも。

    .txt に対するシェルエクステンションを作成する必要があるので、仕掛けとしては相当大掛かりになってしまい、あまり現実的ではないかもしれませんが。
     デベロッパーセンター - 『 How to Create Drop Handlers 』

    大雑把にいえば、IDropTarget の DragEnter メソッドで DROPEFFECT_COPY を返却してドロップを許可し、 IDropTarget の Drop メソッドが呼ばれたら、DragQueryFile API でパスを受け取るという感じです。 ただ、そもそも .NET でシェルエクステンションを作成することは推奨されていませんので、止めておいた方が良いでしょう。
     The Old New Thing - 『 Now that version 4 of the .NET Framework supports in-process side-by-side runtimes, is it now okay to write shell extensions in managed code?

    教えて頂きました「folderView_BeginDrag」で、見よう見まねで次のようにメソッドを実装してみました。

    FolderItem の受け取り方に問題がありますね。

    FolderItems.Items の引数 が Missing の場合、親フォルダが返されます。
    アイテム自身を得たい場合には、「0」~「.Count - 1」の範囲の Int32 を渡してください。

    FolderItem の具体的な受け取り方は、先の SelectionChanged イベントのサンプルに掲載しています。

    • 回答の候補に設定 星 睦美 2013年6月17日 0:50
    • 回答としてマーク 星 睦美 2013年6月25日 1:32
    2013年6月13日 18:46
  • 本筋ではないところですが…。

     なぜ.txtに.docをドロップしたくならないのですか?

    ドロップ先になるもの、ならないもののルールをおおよそ理解しているからこそ、ドロップしようという考えを持たないというところでしょうか。
    ドロップ先になり得るのでは、ファイルを格納するフォルダーと、ファイルを引数にとって実行できるショートカット・アプリケーションなどになります。
    このルールに照らせば、単なるファイルを単なるファイルにドロップできないのは当然のことなので、やろうと思わないわけです。
    (概念として、txt を doc にドロップして期待する動きの定義、その実現もまた難しいことですが)


    なにかしていることの奥でなにが起きているのか(あるいはなぜ禁止されているのか)知りたい

    ドラッグ&ドロップにおいては、ドロップ先が受け入れる・受け入れないを自由に決めることができる設計になっています。
    エクスプローラーはおそらく、前述のようなルールに基づいているので、ドロップ先がフォルダーやアプリケーション類でなければドロップしないというコーディングをしているはずです。

    さて、エクスプローラーが設計された意図から外れることをしようとした場合、実現できない可能性が出てきます。
    インターフェースで公開されていない、備えられていない事柄は外部から実現することはほぼ困難になることがあるのでご留意ください。

    • 回答の候補に設定 星 睦美 2013年6月17日 0:50
    • 回答としてマーク 星 睦美 2013年6月25日 1:32
    2013年6月14日 13:41
    モデレータ

すべての返信

  • 先日、自分から起動したエクスプローラで選択した状態を取得するという質問があり、試してみました。

    C#フォーラムの“エクスプローラでフォルダ選択時のイベント取得方法” のことでしょうか?

    DragDropとかは見当たりませんでした。

    ドロップする側の処理を記述したいのでしょうか? それともドロップされる側の処理を記述したいのでしょうか?

    そもそもここではないのでしょうか?

    ドラッグの開始を知りたいのであれば、BeginDrag イベントが使えます。

    private void explorer_NavigateComplete2(object pDisp, ref object URL)
    {
        if (folderView != null)
        {
            folderView.BeginDrag -= folderView_BeginDrag;
            Marshal.ReleaseComObject(folderView);
        }
        object document = pDisp.GetType().InvokeMember("Document", BindingFlags.GetProperty, null, pDisp, null);
        folderView = (ShellFolderView)document;
        folderView.BeginDrag += folderView_BeginDrag;
    }
    
    bool folderView_BeginDrag()
    {
        try
        {
            // 選択されたアイテムを取得
            // FolderItems folderItems = folderView.SelectedItems();
            // あとはここから、FolderItem を調べて Path 等を得る
    
            // 戻り値のあるイベントなので、true/false をセットすること
            if (ドラッグを開始させたくない場合)
            {
               return false;
            }
            return true;
        }
        catch(Exception ex)
        {
            return true;
        }
    }

    ドロップ先が.txtとかだと、

    ファイルがドロップされた時の処理を実装したいのでしょうか?

    ドラッグアイテムの内容を判定するのは、個々の「ドロップされるアプリ側」の仕事です。ドラッグ元がExplorerか他アプリなのかは関係ありません。

    自アプリ上にドラッグされてきたときには、DragEnter 等のイベントで渡される DragEventArgs を元に、そのデータを受け入れられるかどうかを判断します。ここで DragDropEffects.None を指定した場合、受入不能ということで、ドラッグ元のアイコンは禁止マークになります。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.AllowDrop = true;
            this.DragLeave += delegate(object o, EventArgs arg) { this.Text = ""; };
            this.DragEnter += delegate(object o, DragEventArgs arg)
            {
                string[] files = arg.Data.GetData(DataFormats.FileDrop) as string[];
                if (files == null)
                {
                    arg.Effect = DragDropEffects.None;
                    this.Text = "";
                }
                else
                {
                    arg.Effect = DragDropEffects.Copy;
                    this.Text = files.First();
                    if (files.Length >= 2)
                    {
                        this.Text += ",他" + (files.Length - 1) + "個";
                    }
                }
            };
        }
    }
    2013年6月13日 7:15
  • 魔界の仮面弁士様
     
    早速の回答ありがとうございます。
     大変感謝します。
     おっしゃるとおり、このC#フォーラムの"エクスプローラでフォルダ選択時のイベント取得方法"をたいへん参考にさせていただき、勉強しました。
     
    回答内容についての確認と質問です。
     ドロップされる側の処理を記述したいです。
     より具体的に申し上げれば、エクスプローラ内部でのドロップについて記述したいです。

    たとえば、エクスプローラで開いたあるフォルダに、editor.exeと01.txtと02.docがあるとします。
    (1)このeditor.exeに01.txtをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。
    (2)この01.txtに02.docをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。

    (2)はエクスプローラで操作すると、02.docを01.txtにドロップした瞬間にキャンセルされて、02.docのアイコンはもともと02.docのあった場所に戻ってしまうのですが、それでも可能であれば02.docと01.txtのフルパスを取得したいと思っています。

     教えて頂きました「folderView_BeginDrag」で、見よう見まねで次のようにメソッドを実装してみました。
     これでは、値はとれていないようです。

    bool folderView_BeginDrag() {
     try {
      Folder folder = folderView.Folder;
      // 選択したアイテムを取得
      FolderItems folderItems = folderView.SelectedItems();
      // あとはここから、FolderItem を調べて Path 等を得る
      FolderItem fItem = folderItems.Item(Type.Missing);
      string folderPath = fItem.Path;//この値は、開いたエクスプローラの初期ウィンドウのフルパス。
      Marshal.ReleaseComObject(fItem);
      Marshal.ReleaseComObject(folderItems);
      Marshal.ReleaseComObject(folder);
      string title = "DragDropTarget: " + folderPath;
      Invoke(new Action<string>(delegate(string s) {
       listBox1.Items.Clear();
       Text = s;//この値は、開いたエクスプローラの初期ウィンドウのフルパス。
      }), title);

      // 戻り値のあるイベントなので、true/false をセットする
      //if (ドラッグを開始しない場合) {
      // return false;
      //}
      return true;
      }
     catch (Exception ex) {
      return true;
     }
    }

    ご教示お願い致します。

    2013年6月13日 13:57
  • ドロップされる側の処理を記述したいです。

    「ドラッグ開始時に、ドラッグされているファイル名を得る」のではなく、
    「ドラッグ完了時に、ドロップ元のファイル名を得る」のが目的なのでしょうか?

    何のためにそれが必要なのかにもよりますが、その場合はドロップされる側を個別に実装していく必要があるかと思います。つまり、editor.exe にドロップする場合なら、editor.exe のソースコードに手を加える必要があるということです。

    (1)このeditor.exeに01.txtをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。

    editor.exe 側で、コマンドライン引数を取得するようにしてください。
     DOBON.NET - 『 起動時のコマンドライン引数を取得する 』

    (2)この01.txtに02.docをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。

    文書ファイルの場合は……Drop Handlers を用意しないといけないかも。

    .txt に対するシェルエクステンションを作成する必要があるので、仕掛けとしては相当大掛かりになってしまい、あまり現実的ではないかもしれませんが。
     デベロッパーセンター - 『 How to Create Drop Handlers 』

    大雑把にいえば、IDropTarget の DragEnter メソッドで DROPEFFECT_COPY を返却してドロップを許可し、 IDropTarget の Drop メソッドが呼ばれたら、DragQueryFile API でパスを受け取るという感じです。 ただ、そもそも .NET でシェルエクステンションを作成することは推奨されていませんので、止めておいた方が良いでしょう。
     The Old New Thing - 『 Now that version 4 of the .NET Framework supports in-process side-by-side runtimes, is it now okay to write shell extensions in managed code?

    教えて頂きました「folderView_BeginDrag」で、見よう見まねで次のようにメソッドを実装してみました。

    FolderItem の受け取り方に問題がありますね。

    FolderItems.Items の引数 が Missing の場合、親フォルダが返されます。
    アイテム自身を得たい場合には、「0」~「.Count - 1」の範囲の Int32 を渡してください。

    FolderItem の具体的な受け取り方は、先の SelectionChanged イベントのサンプルに掲載しています。

    • 回答の候補に設定 星 睦美 2013年6月17日 0:50
    • 回答としてマーク 星 睦美 2013年6月25日 1:32
    2013年6月13日 18:46

  • 魔界の仮面弁士様
     
    早速の回答ありがとうございます。
     ご整理いただきありがとうございます。

    【状況補足】
    エクスプローラで開いたあるフォルダに、editor.exeと01.txtと02.docがあるとします。現在作成しようとしている、主体となるpathを取得したいプログラムをgetpath.exeとし、それは起動ずみであるとします。

    (1)このeditor.exeに01.txtをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。

    editor.exeが自作の場合は、コマンドライン引数を取得すれば可能ですね。ただ、editor.exeが取得した引数を、getpath.exeが受けとるためには、Messageとかを実装すれば可能でしょうか?
    editor.exeが自作でない場合、たとえば、*.txtをnotepad.exe(Windown標準の)で起動しているような場合には、起動情報は受けとれないでしょうか? むしろ起動後に、取得することになりますか。

    (2)この01.txtに02.docをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。
    じつはこれがいちばん行いたいことです。
    お話を伺うと、そもそも.NETでシェルエクステンションは推奨されないとのことなので、ちょっと無理ですかね。

    (3)次のように考えました。
    (3-1)常時、マウスのボタンの状態と位置を取得しておく。たとえばmousehook。
    (3-2)ドラッグ開始時にドラッグしているファイルを取得する。
    (3-3)マウスをリリースしたときに、マウスの位置にあるアプリケーションを取得する。
    (3-4)それがエクスプローラーだった場合、その位置にあるアイコンを取得する。ひょっとして、folderViewのなかに、onmouseなどのイベントフラグがあれば可能?
     エクスプローラでマウスを動かすと、マウスが乗っているファイルを薄く反転表示するので、そこにはイベントがあるようです。
    hongliang.up.seesaa.net/sources/MouseHook2.cs
    を参考に3-1~3-3を実現する処理を書いてみているところです。

    public MouseHook() {
     if (Environment.OSVersion.Platform != PlatformID.Win32NT)
      throw new PlatformNotSupportedException("Windows 98/Meではサポートされていません。");
     MouseHookDelegate handler = new MouseHookDelegate(CallNextHook);
     this.hookDelegate = GCHandle.Alloc(handler);
     IntPtr module = Marshal.GetHINSTANCE(typeof(MouseHook).Assembly.GetModules()[0]);
     this.hook = SetWindowsHookEx(MouseLowLevelHook, handler, module, 0);
     if (hook == IntPtr.Zero)
      throw new Win32Exception(Marshal.GetLastWin32Error());

    }

    private void Form1_Load(object sender, EventArgs e) {
    //サンプルではForm1_Shownというメソッド名だったが、Shownというイベントが見当たらないので、Loadに割り当てる。これでとりあえずは問題なし。
     globalHook.MouseHook mousehook = new globalHook.MouseHook();
     mousehook.MouseHooked += new globalHook.MouseHookedEventHandler(mousehook_MouseHooked);
     // 操作用の Explorer を起動
     Type t = Type.GetTypeFromCLSID(new Guid("C08AFD90-F2A1-11D1-8455-00A0C91F3880"));
     object o = Activator.CreateInstance(t);
     explorer = (ShellBrowserWindow)o;
     explorer.NavigateComplete2 += explorer_NavigateComplete2;
     // explorer.OnQuit += explorer_OnQuit;
     explorer.Left = this.Left + 50;
     explorer.Top = this.Top + 50;
     object url = @"C:\";    // 初期表示フォルダ
     object flags = Type.Missing;
     object targetFrameName = Type.Missing;
     object postData = Type.Missing;
     object headers = Type.Missing;
     explorer.Navigate2(ref url, ref flags, ref targetFrameName, ref postData, ref headers);
     explorer.Visible = true;
    }

    void mousehook_MouseHooked(object sender, globalHook.MouseHookedEventArgs e) {
     var mousestatus = e.XButtonData;
    }

    のようにしてみました。
    ただ、このmousehookのサンプルの、コメント欄での補足で、
    hongliang.seesaa.net/article/7651626.html

    // ファイル先頭部分にて
    using System.Diagnostics;

    // KeyboardHook クラスのメソッドとして
    [DllImport("kernel32.dll", CharSet=CharSet.Auto)]
    private static extern IntPtr GetModuleHandle(string moduleName);

    // コンストラクタの module 取得部分~ this.hook 設定部分
    using (Process current = Process.GetCurrentProcess()) {
    IntPtr module = GetModuleHandle(current.MainModule.ModuleName);
    this.hook = SetWindowsHookEx(KeyboardHookType, callback, module, 0);
    }
    と改良しないと、エラーとなる旨のコメントがあり、それの改変をしようとしています。
    usingはできるとして、
    // KeyboardHook クラスのメソッドとして
    というのが理解困難なところです。
    このmousehookのサンプルには、一連の記事として、keyboardhookのサンプルもあり、それもコードとしてつけ加えるのでしょうか。
    そう理解して、KeyboardHook3.csを加えました。
    hongliang.up.seesaa.net/sources/KeyboardHook3.cs

    次に、
    // コンストラクタの module 取得部分~ this.hook 設定部分
    を置き換えました。

    public MouseHook() {
     if (Environment.OSVersion.Platform != PlatformID.Win32NT)
      throw new PlatformNotSupportedException("Windows 98/Meではサポートされていません。");
     MouseHookDelegate handler = new MouseHookDelegate(CallNextHook);
     this.hookDelegate = GCHandle.Alloc(handler);
     using (Process current = Process.GetCurrentProcess()) {
      IntPtr module = GetModuleHandle(current.MainModule.ModuleName);
      this.hook = SetWindowsHookEx(KeyboardHookType, callback, module, 0);
     }
     if (hook == IntPtr.Zero)
      throw new Win32Exception(Marshal.GetLastWin32Error());
    }
    としました。
    すると、当然ですが、
    this.hook = SetWindowsHookEx(KeyboardHookType, callback, module, 0);
    で、KeyboardHookTypeとcallbackが、現在のコンテキストに存在しないとエラーです。
    SetWindowsHookEx(KeyboardHookType, callback, module, 0);
    は、
    keyboardHook3.csの99行目にあるので、
    private const int KeyboardHookType = 13;

    public const int KeyboardHookType = 13;
    としました。
    そして、
    this.hook = SetWindowsHookEx(KeyboardHook.KeyboardHookType, callback, module, 0);
    とします。
    callbackのほうも書き換えました。

    public MouseHook() {
     if (Environment.OSVersion.Platform != PlatformID.Win32NT)
      throw new PlatformNotSupportedException("Windows 98/Meではサポートされていません。");
     MouseHookDelegate handler = new MouseHookDelegate(CallNextHook);
     this.hookDelegate = GCHandle.Alloc(handler);
     using (Process current = Process.GetCurrentProcess()) {
      MouseHookDelegate callback = new MouseHookDelegate(CallNextHook);
      IntPtr module = KeyboardHook.GetModuleHandle(current.MainModule.ModuleName);
      this.hook = SetWindowsHookEx(KeyboardHookType, callback, module, 0);
     }
     if (hook == IntPtr.Zero)
      throw new Win32Exception(Marshal.GetLastWin32Error());
    }
    となりました。
    これで、コンパイルは通り、デバッグしています。
    これで、期待しているのは、マウスが動くたびに、
    void mousehook_MouseHooked(object sender, globalHook.MouseHookedEventArgs e) {
     var mousestatus = e.XButtonData;
    }
    を呼び出して、ボタンの状態を確認できることです。
    ところが、これが呼ばれないのです。

    (4)「folderView_BeginDrag」
    ご助言ありがとうございます。確認できました。

    最初の質問からはすこしずれてきていますが、
    ご教示お願いできれば幸いです。

    なお、msdnの制約で本文中のURLは「アカウントを確認できるまで貼れない」とのことなので、httpを省略しました。あしからずご了承いただければ幸いです。

     

    2013年6月14日 2:32
  • (1)このeditor.exeに01.txtをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。

    それは何のためにですか?

    (2)この01.txtに02.docをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。

    そもそも何故、.txt に .doc をドロップさせるような操作を行わせたいのでしょうか。

    また、管理している Explorer 以外からの操作…たとえばデスクトップなどからのドラッグや、コモンダイアログとの間でのドラッグ & ドロップなどは考慮しなくて良いのでしょうか。

    *.txtをnotepad.exe(Windown標準の)で起動しているような場合には、起動情報は受けとれないでしょうか? むしろ起動後に、取得することになりますか。

    Win32_Process クラス経由で、現在起動済みのプロセスのコマンドラインを引数付きで取得できます。
    起動後、すぐに終了してしまうようなアプリには使いにくいですけれどね。(PBrush.exe(MSPaint.exe を起動して即時終了)とか、Write.exe(WordPad.exe を起動して即時終了)など)

    using System.Management;
    class Program
    {
        static void Main()
        {
            using (var mos = new ManagementObjectSearcher())
            {
                mos.Query.QueryString = "SELECT * FROM Win32_Process WHERE CommandLine IS NOT NULL";
                using (var processes = mos.Get())
                {
                    foreach (var mo in processes)
                    {
                        Console.WriteLine(mo["CommandLine"]);
                    }
                }
            }
            Console.ReadKey();
        }
    }

    editor.exeが取得した引数を、getpath.exeが受けとるためには、Messageとかを実装すれば可能でしょうか?

    プロセス間通信を実装するだけであれば、Message とか .NET Remoting とか WCF とかソケット通信とかファイル更新監視とか、いろいろと方法がありそうです。

    (3-1)常時、マウスのボタンの状態と位置を取得しておく。

    Timer 監視という手抜き案。

    timer1.Interval = 55;
    timer1.Tick += delegate {
        Text = String.Format("{0}/{1}",
            MouseButtons, Cursor.Position);
    };
    timer1.Start();

    あとは DirectInput の SetEventNotification メソッドでイベント通知してもらう手法もありますかね。

    Managed DirectX は過去の物なので、SlimDX あたりでの操作になるのかな。

    その位置にあるアイコンを取得する。

    アイコンからの情報取得は、Shell Objects 経由で取得しないと厳しいかと思います。

    個々のアイテム名を得るだけなら、ListView 関連の API(LVMメッセージ)を使うとか、AccessibleObjectFromPoint API を使うなどすれば可能ですが…そこから具体的なパス情報を得るのは難しいでしょう。
    たとえば、「拡張子を表示しない」設定になっていた場合、calc.txt と calc.exe はいずれも「calc」というアイテム名で表示され、両者を名前で区別することができなくなりますし。

    マウスの位置にあるアプリケーションを取得する。

    GetWindowThreadProcessId API を使って、HWND から PID に変換するとか。

    マウスが乗っているファイルを薄く反転表示するので、

    それは OS の設定次第ですね。古い OS だとホバーには反応しませんし。

    Shownというイベントが見当たらないので、

    普通はあるはず…。

    2013年6月14日 5:24

  • 魔界の仮面弁士様
     
    早速の回答ありがとうございます。
     再度、ご整理いただきありがとうございます。

     タイマーの手抜きは最初に思いついたので、まずはそれで実装してみることにします。

    そもそものやりたい理由ですが、逆に、それをやりたくなりませんか?
    なにかしていることの奥でなにが起きているのか(あるいはなぜ禁止されているのか)知りたい、という素朴な探求心なので、わたしにとっては、そもそもなぜそれをやりたくならないのか、のほうがむしろ不思議です。
     なぜ.txtに.docをドロップしたくならないのですか?

    2013年6月14日 9:02
  • 本筋ではないところですが…。

     なぜ.txtに.docをドロップしたくならないのですか?

    ドロップ先になるもの、ならないもののルールをおおよそ理解しているからこそ、ドロップしようという考えを持たないというところでしょうか。
    ドロップ先になり得るのでは、ファイルを格納するフォルダーと、ファイルを引数にとって実行できるショートカット・アプリケーションなどになります。
    このルールに照らせば、単なるファイルを単なるファイルにドロップできないのは当然のことなので、やろうと思わないわけです。
    (概念として、txt を doc にドロップして期待する動きの定義、その実現もまた難しいことですが)


    なにかしていることの奥でなにが起きているのか(あるいはなぜ禁止されているのか)知りたい

    ドラッグ&ドロップにおいては、ドロップ先が受け入れる・受け入れないを自由に決めることができる設計になっています。
    エクスプローラーはおそらく、前述のようなルールに基づいているので、ドロップ先がフォルダーやアプリケーション類でなければドロップしないというコーディングをしているはずです。

    さて、エクスプローラーが設計された意図から外れることをしようとした場合、実現できない可能性が出てきます。
    インターフェースで公開されていない、備えられていない事柄は外部から実現することはほぼ困難になることがあるのでご留意ください。

    • 回答の候補に設定 星 睦美 2013年6月17日 0:50
    • 回答としてマーク 星 睦美 2013年6月25日 1:32
    2013年6月14日 13:41
    モデレータ
  • 何かやりたい理由があるというわけではなく、やること自体が目的ということでしょうか?

    >> そもそも何故、.txt に .doc をドロップさせるような操作を行わせたいのでしょうか。
    > 逆に、それをやりたくなりませんか?

    「それ」というのが何を指しているのか曖昧で、回答に詰まっているところです。

    上記の部分だけ見ると、ドラッグ & ドロップを行わせたい、という意味にも読めますが、もしもそうであれば、下記のスレッドが参考になるかもしれません。
    http://bbs.wankuma.com/index.cgi?mode=al2&namber=15727&KLOG=32

    > という素朴な探求心なので、

    『素朴な探求心』とのことですが、一連の質問を見ても、ドラッグ操作の何について探求しているのか、向かっている先が見えていないもので、先のような質問を投げかけた次第です。

    > あるいはなぜ禁止されているのか

    ドロップ操作を許可するか禁止するのかは、IDropTarget インターフェイスの実装者(ドロップターゲット)側が決めています。.NET 的には、DragEventArgs.Effect プロパティでの応答ですね。

    ドロップターゲット側は、ドロップ先のウィンドウに対し、自身が実装した IDropTarget を RegisterDragDrop API で登録します(解除は RevokeDragDrop API )。.NET においては、Control.AllowDrop プロパティがこの役目を担っています。

    ドロップソース側は「GetProp(ターゲットウィンドウ, "OleDropTargetInterface")」 を呼びだして IDropTarget を得られます。

    > (2)この01.txtに02.docをドラッグ&ドロップしたときに、それぞれのフルパスを取得したいです。

    スマートな方法は思いつきませんでした。試行錯誤してはみましたが、先述のような理由から、Shell Objects を経由させずにフルパスをスマートに得るのは難しそうです。

    // 「Accessibility」を参照設定
    // ListBox と Timer を貼っておく
    
    private AccessibleObject desktop;
    
    private void Form1_Load(object sender, EventArgs e)
    {
        desktop = this.AccessibilityObject.Parent.Parent;
        listBox1.Dock = DockStyle.Fill;
        timer1.Interval = 60;
        timer1.Start();
    }
    
    private void timer1_Tick(object sender, EventArgs e)
    {
        var pt = Cursor.Position;
        Accessibility.IAccessible acc = null;
        object childId = null;
        var q = GetInfo(desktop.HitTest(pt.X, pt.Y));
        q = q.Concat(GetInfo("--------"));
        try
        {
            AccessibleObjectFromPoint(pt, out acc, out childId);
            q = q.Concat(GetInfo(acc));
            q = q.Concat(GetInfo("childId = " + childId.ToString()));
            if (0 <= (int)childId)
            {
                q = q.Concat(GetInfo("--------"));
                q = q.Concat(GetInfo(acc, childId));
            }
            listBox1.DataSource = q.ToArray();
        }
        catch (Exception) { }
        finally
        {
            if (acc != null) Marshal.ReleaseComObject(acc);
        }
    }
    
    #region ユーザー補助 API
    [DllImport("oleacc.dll")]
    public static extern IntPtr AccessibleObjectFromPoint(
        Point pt,
        [MarshalAs(UnmanagedType.Interface)]
        out Accessibility.IAccessible accObj,
        out object ChildID);
    
    [DllImport("oleacc")]
    public static extern uint AccessibleChildren(
        Accessibility.IAccessible paccContainer,
        int iChildStart,
        int cChildren,
        out object[] rgvarChildren,
        out int pcObtained);
    #endregion
    
    #region 画面表示用のヘルパーメソッド
    public IEnumerable<string> GetInfo(params string[] args)
    {
        return args;
    }
    
    public IEnumerable<string> GetInfo(Accessibility.IAccessible acc, object child = null )
    {
        if (acc == null) yield break;
        if (child == null) child = Type.Missing;
        var methods = new Dictionary<string, Func<string>>();
        methods.Add("Description", () => acc.get_accDescription(child));
        methods.Add("Name", () => acc.get_accName(child));
        methods.Add("Role", () => Enum.ToObject(typeof(AccessibleRole), acc.get_accRole(child)).ToString());
        methods.Add("State", () => Enum.ToObject(typeof(AccessibleStates), acc.get_accState(child)).ToString());
        methods.Add("Value", () => acc.get_accValue(child).ToString());
        methods.Add("ChildCount", () => acc.accChildCount.ToString());
    
        foreach (var method in methods)
        {
            string item = method.Key + " = ";
            try
            {
                item += method.Value();
            }
            catch (Exception ex)
            {
                item += "{" + ex.Message + "}";
            }
            yield return item;
        }
    }
    
    public IEnumerable<string> GetInfo(AccessibleObject ao)
    {
        if (ao == null) yield break;
        var methods = new Dictionary<string, Func<string>>();
        methods.Add("Bounds", () => ao.Bounds.ToString());
        methods.Add("Name", () => ao.Name);
        methods.Add("Role", () => ao.Role.ToString());
        methods.Add("Description", () => ao.Description);
        methods.Add("State", () => ao.State.ToString());
        methods.Add("Value", () => ao.Value);
        foreach (var method in methods)
        {
            string item = method.Key + " = ";
            try
            {
                item += method.Value();
            }
            catch (Exception ex)
            {
                item += "{" + ex.Message + "}";
            }
            yield return item;
        }
    }
    #endregion
    2013年6月14日 14:46

  • 魔界の仮面弁士様
     
    ご回答ありがとうございます。
     ソースまでいただきありがとうございます。

     昨日深夜に、自分でも実装して、動作することを確認しました。

     自分で実装したのは、次のようなコードです。

    // フォルダーの内容を選択したときのイベント
    private void folderView_SelectionChanged()  {
     List<string> items = new List<string>();
     FolderItem folderItem;
     folderItem = folderView.FocusedItem;
     if (folderItem != null) items.Add("フォーカス: " + folderItem.Path);
     Marshal.ReleaseComObject(folderItem);
     FolderItems folderItems = folderView.SelectedItems();
     if (dragging) {
      for (int i = 0; i < folderItems.Count; i++) {
       folderItem = folderItems.Item(i);
       dragitems.Add("[drag2]" + folderItem.Path);
       Marshal.ReleaseComObject(folderItem);
      }
      Marshal.ReleaseComObject(folderItems);
      FillSelectionList(dragitems);
      dragitems.Clear();
      dragging = false;
     }
     else {
      items.Add("選択数: " + folderItems.Count);
      for (int i = 0; i < folderItems.Count; i++) {
       folderItem = folderItems.Item(i);
       items.Add(i + ": " + folderItem.Path);
       Marshal.ReleaseComObject(folderItem);
      }
      Marshal.ReleaseComObject(folderItems);
      FillSelectionList(items);
     }
    }
    bool dragging = false;
    List<string> dragitems = new List<string>();
    private void mousewatchtimer_Tick(object sender, EventArgs e) {
     Text = String.Format("{0}/{1}", MouseButtons, Cursor.Position);
     if (MouseButtons.ToString() == "Left") {
      int x = point.X - Cursor.Position.X;
      int y = point.Y - Cursor.Position.Y;
      if (100 < x * x + y * y && 0 < dragitems.Count) {
       Text = "[moved]" + Text;
       dragging = true;
      }
      else if (0 < dragitems.Count)  Text = "[stay]" + Text;
      point.X = Cursor.Position.X;
      point.Y = Cursor.Position.Y;
      listBox1.BeginUpdate();
      listBox1.Items.Add(Text);
      listBox1.EndUpdate();
     }
     else if (MouseButtons.ToString() == "None" && dragging) {
      Text += "\r\n" + "[drag finished]";
      //終了時に左クリック処理することで、終了先のアイテムを取得する。

    //note.chiebukuro.yahoo.co.jp/detail/n35657

    //を参考に。★2013年06月14日(金)
      mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
      mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
     }
    }

    まあだいぶ手抜きですし、ドラッグ終了時にクリックするなんてのは反則だ、という感じもしますが、これでドラッグ開始ファイルのフルパスと、ドラッグ終了時のフルパスを取得できました。
    ただ、これだとFormから起動したExplorerでしか取得できないので、汎用的な方法にする必要があるなぁと感じていました。

     早速いただいたコードをテストしました。これは、すでに起動した別のウィンドウに対応しているので、先の方法で気になっていた課題をクリアしています。
     これで、ドラッグ元(from)とドラッグ先(to)は取得できました。
     あと、そのフォルダ(アクティブウィンドウ)のパスを取得できれば…。

    雑談(?)です。
    Azulean様にもご回答いただきましたが…。
    向かっている先はわたし自身にもよく見えていないんです。
    まずはできるのかなぁ、できるんならやってみたいなぁ、という感じです。
    山登りみたいなものでしょうか。

    2013年6月14日 23:33
  • なぜ.txtに.docをドロップしたくならないのですか?

    プログラマが作るものがプログラムであり、プログラムはプログラマによって作られます。では.txtや.docはどうでしょう? どちらもデータでありプログラムではありません。

    データをプログラムにドロップ、もしくはその逆、プログラムをデータにドロップであればわかります。データを伴ってプログラムを実行する。しかし、データ同士では実行されるプログラムがないため何も起こりません。

    それはプログラマであればわかることです。

    非プログラマがデータ同士をドロップしたときにどうなるか興味がある、あり得ることは否定しません。しかし、プログラマが、データ同士をドロップしたときにどうなるか興味がある、監視するプログラムを作りたいという感性は理解できません。

    2013年6月15日 3:22
  • ただ、これだとFormから起動したExplorerでしか取得できないので、汎用的な方法にする必要があるなぁと感じていました。

    SHDocVw.ShellWindows コレクションを使うことで、起動済みの Explorer ならびに InternetExplorer のオブジェクトを列挙できます。この方法であれば、最初のコードで使っていた Shell Objects 群にアクセスできますので、具体的なパスを調べることもできるでしょう。

    なお、アプリ起動後に追加起動された Explorer も監視対象としたいのであれば、そのたびに再列挙が必要となりますので、Win32_Process の変更通知イベントを併用するなどの操作が必要かもしれません。

    とはいえ、世の中のファイラーは Windows Explorer だけでは無いわけで……。

    2013年6月15日 6:23
  • フォーラム オペレーターの星 睦美です。

    回答者のみなさん、いつもありがとうございます。

    skyrabbit さん、こんにちは

    ほかの方にも参考になりそうな返信に今回は私から[回答としてマーク] させていただきました。
    次回もフォーラムで役立つ回答がありましたら、ぜひ投稿者から[回答としてマーク] いただけると回答者へ良いフィードバックになります。

    では、これからもMSDN フォーラムをよろしくお願いします。


    日本マイクロソフト株式会社 フォーラム オペレーター 星 睦美

    2013年6月25日 1:44