none
C#でFileSystem.MoveFile()を使ったファイルの上書き確認ダイアログボックスで押したボタンを知りたい。 RRS feed

  • 質問

  • C#のファイル操作に関する質問です。

    Microsoft.VisualBasic.FileIO.FileSystem.MoveFile()メソッドでファイルを別の場所に移動させたいのですが、

    (参考 http://msdn.microsoft.com/ja-jp/library/ms128011(v=vs.85).aspx)

    移動先に同名のファイルがあった場合に、上書き確認のダイアログボックスで「上書き」か「別名で保存」(ex, xxx(2).jpgとか)のどちらのボタンを押したか知りたいのですが、方法わからなくて困っています。

    MessageBoxのMessageBoxResultのようにどのボタンを押したのか知る方法はないのでしょうか?

    2012年7月12日 13:44

すべての返信

  • Fuda1です。tatsushiさん、おはようございます。

    残念ながら、必要性が明確に理解できないのです。理由ですが、『移動先に同名のファイルがあった場合』これは、FileExistsでファイルの存在を確認できます。その後、『MessageBoxのMessageBoxResultのように』とおっしゃように、メッセージボックスで「上書き」しますか?、Noであれば、「別名」にしますか?、Yesであれば、別名入力を行います。ファイル名が決まった所で、最後に、「Microsoft.VisualBasic.FileIO.FileSystem.MoveFile()メソッドでファイルを別の場所に移動させる」か、中止するを決定すると思います。

    従って、『どちらのボタンを押したか知りたい?』、このご要求が発生するケースのソースコードが、私は、必要と思いますが、如何でしょうか?

    2012年7月12日 17:01
  • そのメソッドでは無理でしょう。
    仮にメソッド実行前後でファイルの存在確認や日付などを検証したところで、100% 検出できるとは限らないため。

    ファイルの移動には SHFileOperation という API を使いつつ、UI は自分で出すとか、そういった工夫が必要かもしれません。

    • 回答の候補に設定 山本春海 2012年8月2日 8:54
    2012年7月12日 22:13
    モデレータ
  • Fuda1です。

    そのメソッドでは無理でしょう。
    仮にメソッド実行前後でファイルの存在確認や日付などを検証したところで、100% 検出できるとは限らないため。

    これは、謎めいたご内容と思います。 私は、100%検出できると考え、安心してプログラムを制作しておりますが?

    using System;
    namespace ExistsTest01
    {
        class Program
        {
            static void Main(string[] args)
            {
                // 検出するパスとファイル名
                string fileName = @"C:\hogepath\fooName.txt";
                // ファイルが存在するか?
                if (System.IO.File.Exists(fileName))
                {
                    Console.WriteLine(fileName + ":は存在します。");
                }
                else
                {
                    Console.WriteLine(fileName + ":は存在しません。");
                }
            }
        }
    }
    こんなコードが有るとしますと、正しく、ファイルの存在を確認出来ない場合が有るとおしゃっておりますが、どのようなケースを想定されているのでしょうか?
    2012年7月13日 0:27
  • イメージとしては、FileOperation の RenameOnCollision が実行されたとき、結果として保存されたファイル名が知りたいという話でしょうかね。それとも上書きされたかどうかを調べたいでしょうか?

    > 正しく、ファイルの存在を確認出来ない場合が有るとおしゃっておりますが、

    違います。かかれていることは「存在の確認ができない」ではなく「上書きされたか確認できない」です。

    Exists を見てファイルが存在するかチェックする

    1. ファイルが存在しているか確認し、存在していればFileSystemWatcherを作成する
    2. MoveFileでファイルを移動する
    3. FileSystemWatcherで更新が通知されれば、ファイルは上書きされている。通知がなければ、別名で保存されている。

    というのはどうでしょうか。やはり、Azulean さんがかかれているように確実な方法ではない、ですが。

    • 回答の候補に設定 山本春海 2012年8月2日 8:54
    2012年7月13日 0:58
  • 移動先に同名のファイルがあった場合に、上書き確認のダイアログボックスで「上書き」か「別名で保存」(ex, xxx(2).jpgとか)のどちらのボタンを押したか知りたいのですが、方法わからなくて困っています。

    質問の意味がよくわかりませんでした。MoveFileメソッドは「上書き」か「別名で保存」を選択させるようなダイアログを表示することはないと思いますが、私が勘違いしていますでしょうか?
    上書き確認のダイアログボックスはどのタイミングで、どのように出されているのでしょうか、もしくは出ているのでしょうか?


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年7月13日 1:15
    モデレータ
  •  K.Takaokaさん、こんにちは、Fuda1です。

    正しく、ファイルの存在を確認出来ない場合が有るとおしゃっておりますが、違います。かかれていることは「存在の確認ができない」ではなく「上書きされたか確認できない」です。

    どうも、誤解されているようです。 上書きされたか確認をする為には、ファイルの存在を100%正確に認識する必要があるとの意味です。(日本語は、前後関係のご理解も重要です。もう一度、ストーリをご確認ください。)

    2012年7月13日 1:25
  • 何となくですけど、

    「C#でFileSystem.MoveFile()を使ったファイルの上書き確認ダイアログボックスで押したボタンを知りたい。」

    という質問文が誤解を生んでそうですね。

    「FileSystem.MoveFile()を使う」
    ってのと、
    「ファイルの上書き確認ダイアログボックスで押したボタンを知りたい」
    ってのは別の話ですよね?

    今回の質問者さんが求める回答としては、
    ダイアログボックス出して、押したボタンを判断したいのだと思ったのでそうだと想定して・・・

            private void FileMove(string sourceFileName, string destinationDirectoryPath)
            {
                string fileName = Microsoft.VisualBasic.FileIO.FileSystem.GetName(sourceFileName);
                string destinationFileName = Microsoft.VisualBasic.FileIO.FileSystem.CombinePath(destinationDirectoryPath, fileName);
    
                if (Microsoft.VisualBasic.FileIO.FileSystem.FileExists(destinationFileName))
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine("書込みファイルが存在します。上書きしますか?");
                    sb.AppendLine("Yes:上書き保存");
                    sb.Append("No :別名で保存");
                    switch (MessageBox.Show(sb.ToString(), "上書き確認", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question))
                    {
                        case DialogResult.Yes:
                            Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(sourceFileName, destinationFileName, true);
                            break;
                        case DialogResult.No:
                            //別名を判定して保存する処理
                            break;
                    }
                }
                else
                {
                    Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(sourceFileName, destinationFileName);
                }
            }

    みたいなメソッドを用意して、

    FileMove([移動元ファイルパス], [移動先フォルダパス]);

    と指定する感じかと思います。

    見当違いたっだらすいません。

    にしても・・・Microsoft.VisualBasic.FileIO.FileSystem 使う習慣が無いから使いにくい・・・

    ※System.IO名前空間のPathクラスやFileクラスを使い分けた方が好きです。

    2012年7月13日 2:05
  • 私もこの路線でよさそうと思っています。(確認を出さない設定で)

    元の MoveFile が表示するダイアログの選択結果を取る方法は恐らくないこと、処理前後のファイルの存在や日時比較ではその選択結果を類推できないことを指摘していました。

    なお、上書きする、上書きしない、別名で移動して保持は Win7 のエクスプローラーがそのように動いているので MoveFile もそうなると予想しています。

    ただ、存在しないことを確認したあとの MoveFile 実行する前の神がかり的なタイミングで移動先にファイルができたら、事前チェック方式ではアウトになりますが、そんなに起きないかなあ…

    2012年7月13日 3:28
    モデレータ
  • aviatorさん、こんにちは、Fuda1です。折角ここまで作られたのですから、別名入力処理を加えて見ました。

    using System;
    using System.Text;
    using System.Windows.Forms;
    
    namespace FIleExistTest
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
            // テスト開始ボタン
            private void button1_Click(object sender, EventArgs e)
            {
                // カレントディレクトリを取得する
                string strCurrentDir = System.IO.Directory.GetCurrentDirectory();
                // 移動元パス名+ファイル名   移動先パス名+ファイル名
                FileMove(strCurrentDir + @"\test1.txt", @"c:\work\test2.txt");
            }
            private void FileMove(string sourceFileName, string destinationDirectoryPath)
            {
                string fileName = Microsoft.VisualBasic.FileIO.FileSystem.GetName(sourceFileName);
                string destinationFileName = Microsoft.VisualBasic.FileIO.FileSystem.CombinePath(destinationDirectoryPath, fileName);
                do
                {
                    if (Microsoft.VisualBasic.FileIO.FileSystem.FileExists(destinationFileName))
                    {
                        StringBuilder sb = new StringBuilder();
                        sb.AppendLine("書込みファイルが存在します。上書きしますか?");
                        sb.AppendLine("はい:上書き保存する");
                        sb.Append("いいえ:別名で保存する");
                        switch (MessageBox.Show(sb.ToString(), "上書き確認", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question))
                        {
                            case DialogResult.Yes:
                                //上書き処理 
                                Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(sourceFileName, destinationFileName, true);
                                break;
                            case DialogResult.No:
                                //別名を判定して保存する処理
                                destinationFileName = Microsoft.VisualBasic.Interaction.InputBox("パス名とファイル名入力を入力します。",
    "別名入力ダイアログ", destinationFileName, -1, -1); if (destinationFileName == ""){break;}else{continue;} } } else { try { // 移動先に同じファイルが無い場合の処理 Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(sourceFileName, destinationFileName); } catch (Exception ex) { // 元ファイルが無いなどのエラー表示 MessageBox.Show(ex.Message,"エラー表示",MessageBoxButtons.OK,MessageBoxIcon.Error); } } break; } while (true); } } }


    2012年7月13日 4:16
  • 様々なご返信ありがとうございます。質問の説明が足らず申し訳ありませんでした。いま個人でWPFで画像ビューワを作成しており(Picasaのようなものをご想像ください)
    選択した画像を別項目で選択したフォルダーに移動させたいと考えていました。

    もともとなぜこのメソッドを使いたかったかというと、

    Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(sourceFileName, destinationFileName,AllDialogs,DoNothing);

    このメソッドで「移動して置換」、「移動しない」、「移動するが両方のファイルを保持(xxx(2).jpgとか)」と表示されるダイアログが、
    こちら側の「頻繁にUI上で移動が行われることを想定し、あまり何度もダイアログを表示させたくない」という希望に適しており、
    かつ移動元と移動先の画像プレビューも出て見やすく便利だったということがあります。


    ただ、どのボタンを押したかによってDataContentsにバインドした元のインスタンスのプロパティのツリー構造が変わってくるという問題があったため、
    選択結果を知る必要がありました。

    やはりみたところMoveFileでは選択結果をとれないようなので、以下の方針でやってみようとおもいます。(結構しちめんどくさいですが)

    1.MoveFileメソッド実行後に移動元のフォルダにファイルが残っているか調べる。残っていたら操作はキャンセルされたと判断。
    2.移動が行われたならば、上書きか別名で保存されたか調べる。そのためにメソッド実行前後で移動先フォルダのファイル数が一つ増えたか調べる。

    素直にMessageBoxを使うのもありですが・・・とりあえずこんな感じでやってみようかと思います。

    2012年7月14日 0:35
  • > MoveFileメソッドは「上書き」か「別名で保存」を選択させるようなダイアログを表示することはないと思いますが

    VB の MoveFile は SHFileOperation を呼び出して、確認ダイアログを表示します。

    2012年7月14日 0:51
  • Fuda1です。後から見た人が混乱するとおもいますので、画像です。

            private void button1_Click(object sender, RoutedEventArgs e)
            {
                string sourceFileName = @"C:\work\test1.txt";
                string destinationFileName = @"C:\work\test2.txt";
                Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(sourceFileName, destinationFileName, Microsoft.VisualBasic.FileIO.UIOption.AllDialogs, Microsoft.VisualBasic.FileIO.UICancelOption.DoNothing);
            }
    上記コードを実行すると、表示されるダイアログです。
    2012年7月14日 3:01
  • Fuda1です。

    1.MoveFileメソッド実行後に移動元のフォルダにファイルが残っているか調べる。残っていたら操作はキャンセルされたと判断。

    using System;
    using System.Windows;
    using Microsoft.VisualBasic.FileIO;
    namespace WpfTest01
    {
        /// <summary>
        /// Window1.xaml の相互作用ロジック
        /// </summary>
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
            }
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                string sourceFileName = @"F:\work\test1.txt";
                string destinationFileName = @"F:\work\test2.txt";
                try
                {
                    FileSystem.MoveFile(sourceFileName, destinationFileName,
                        UIOption.AllDialogs, UICancelOption.ThrowException);
                }
                catch (Exception ex)
                {
                    // キャンセルを表示する
                    MessageBox.Show(ex.Message);
                }
            }
        }
    }
    「キャンセル」ボタン押下は、エラーメッセージが出ますので、上記コードでもお手軽で便利かと思います。

    2.移動が行われたならば、上書きか別名で保存されたか調べる。そのためにメソッド実行前後で移動先フォルダのファイル数が一つ増えたか調べる。

    通常では問題は思い当たらないのですが、フォルダーが、共有やシステムの場合は、ファイルの増減や、ファイル数が非常に多い場合も考えられますし、隠れファイルなどもございます。全ての条件でクリアしているか検討する必要があると感じました。最近は、複雑なシステムも有ります。他のOSからのアクセスなど、若干心配な気も致します。(ファイルの書込み遅延、キャッシュなど、正しいファイル数が常に入手できるどうか?特に、複数OS管理での並列処理的なディスク環境では疑問です。)

    2012年7月14日 4:21
  • 素直にMessageBoxを使うのもありですが・・・とりあえずこんな感じでやってみようかと思います。

    前にも書きましたが、100% 確実な方法ではありません。
    自分のアプリとは別のアプリがたまたまそのフォルダーにファイルを生成するとか、ファイルが消されてファイル数が変わらないとか、共有フォルダーでファイルの増減が激しいとか、ウィルス対策やセキュリティ対策ソフトの妙な挙動で悩まされるとか。

    このメソッドで「移動して置換」、「移動しない」、「移動するが両方のファイルを保持(xxx(2).jpgとか)」と表示されるダイアログが、
    こちら側の「頻繁にUI上で移動が行われることを想定し、あまり何度もダイアログを表示させたくない」という希望に適しており、
    かつ移動元と移動先の画像プレビューも出て見やすく便利だったということがあります。

    あくまで、特定のバージョンの Windows の実装でそうなっているだけで、すべての Windows でプレビューが出るわけではありません。
    たとえば、Windows XP ではプレビュー画像が出ません。Windows 8 がどうなっているかは未確認ですが…。
    プレビューつきで移動確認を Windows XP を含めて実現したいのであれば、自作するしかありません。(Windows XP だと上書き確認が何回も出ますので、「何度も表示したくない」という希望も満たしません)
    2012年7月14日 12:53
    モデレータ
  • で、手元で MoveFile 実行しました。
    今回の実行環境は Windows XP ではないと予想されるので、スレッドに投稿される皆さんご留意ください。

    Windows 7 での MoveFile で上書きになる場合に表示されるダイアログ:

    2012年7月14日 12:55
    モデレータ
  • Fuda1です。桑名さんガンバレではないですが、ここからさきは~歌。マニアの世界です。

    VB の MoveFile は SHFileOperation を呼び出して、確認ダイアログを表示します。

    Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(sourceFileName, destinationFileName,AllDialogs,DoNothing)このメッソッドも

    Friend Shared Function SHFileOperation(ByRef lpFileOp As SHFILEOPSTRUCT) As Int32 
                If (IntPtr.Size = 4) Then ' 32-bit platforms
                    Return SHFileOperation32(lpFileOp) 
                Else ' 64-bit plaforms
             省略
    

    SHFileOperationでした。このシェル系のファイル操作をフックする事をC++では行い、特定のフォルダーにファイルを書き込もうとすると、「このフォルダーには書き込んではいけません。」など、面白いメッセージを出しているようです。http://eternalwindows.jp/shell/shellex/shellex05.html
    ここを参考にしてC#でも何か出来そうな気も致します。もう一つ、操作メッセージのフックも検討されているようです。

    2012年7月16日 13:09
  • たとえば、Windows XP ではプレビュー画像が出ません。Windows 8 がどうなっているかは未確認ですが…。
    プレビューつきで移動確認を Windows XP を含めて実現したいのであれば、自作するしかありません。(Windows XP だと上書き確認が何回も出ますので、「何度も表示したくない」という希望も満たしません)
    Windows 8ではファイル管理機能についてのフィードバックへの対応で紹介されているダイアログになります。プレビューを見ながら選択しますし、ファイルごとに上書き / リネームを選択できます。
    2012年7月17日 0:32
  • > MoveFileメソッドは「上書き」か「別名で保存」を選択させるようなダイアログを表示することはないと思いますが

    VB の MoveFile は SHFileOperation を呼び出して、確認ダイアログを表示します。

    失礼いたしました。そのオーバーロードを見逃していました。m(_ _)m

    #aviator__さんも書かれていますが、何か特別な事情でもない限り、Microsoft.VisualBasic.FileIO.FileSystemに拘る必要はないように思います。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年7月18日 0:48
    モデレータ
  • > 何か特別な事情でもない限り、Microsoft.VisualBasic.FileIO.FileSystemに拘る必要はないように思います

    個人的には、ユーザビリティの観点から、特別な事情がないかぎりは SHFileOperation にこだわる必要があると思っています。各OSにあわせて、ユーザが普段見慣れている画面を表示することができます。これを独自にきちんと実装するには、非常にコストがかかりメンテナンスも難しいです(あくまで一般論で、tatsushiさんのケースがどうかは別ですが)

    C# で SHFileOperation を手軽に呼び出す手段として VB の FileSystem クラスを使用することも、まあまあ妥当なのではないかな・・・と思います。(私ならやりませんが)

    問題は、OS の進化にともなって SHFileOperation の機能が拡張されていっているのに、その結果をうまくアプリケーションで検出できないという OS やクラスライブラリ側の考慮不足ではないかと思います。ただ、その場合に VB の FileSystem を使用していることは問題にはなるでしょう。FileSystem はクラシックな VB の時の操作と互換性のある機能を提供しているだけなので、VB の FileSystem を使用するかぎりは要望としても通らない仕様であると思います。


    2012年7月19日 0:08
  • 個人的には、ユーザビリティの観点から、特別な事情がないかぎりは SHFileOperation にこだわる必要があると思っています。各OSにあわせて、ユーザが普段見慣れている画面を表示することができます。

    なるほど。そういう考え方は気づいていませんでした。確かにそういう考え方もあると思います。ただ私は、今回スレッド主さんが実現されたいことを、何が何でもSHFileOperationで行う必要はないと単純に思っただけです。時に拘りも必要ですが、アプリケーションの仕様を実現するためにはどこまで拘るかを判断しなければなりません。
    あと余談になりますが、Azuleanさんが画像を掲載されているWindows 7のファイル置換のダイアログはどうも好きになれません。ですから、このダイアログは見慣れてはいますが、Windows Xpのように置換先のファイル情報も表示された方が私は好みです。特に「移動するが両方のファイルを保持する」の場合は、自分で置換先のファイル名を指定できないので使ったことがありません。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年7月19日 1:12
    モデレータ
  • 特に「移動するが両方のファイルを保持する」の場合は、自分で置換先のファイル名を指定できないので使ったことがありません。


    なるほど、逆に僕なんかはファイル名をいちいちダイアログで記入するのが面倒で、このような「勝手にリネームしてくれる」機能が便利だと思ってしまいます。SHFileOperationを使うにしてもOS依存の問題はあまり考えていませんでしたが・・・Windows8で仕様が変わったとしたら、場合によってはプログラムを変える必要もありますし、汎用性という点では厳しいですね。
    2012年7月27日 16:23