2013年02月11日

イクスプローラーからのドラッグ&ドロップ

以前TreeViewへのファイルのドラッグ&ドロップを実装したときと、途中までは同じ。

1.treeView の AllowDropプロパティをTrueに変更。
2.DragEnterイベントで、DragDropEffects を指定。
3.DragDropイベントハンドラを自動生成。
4.そこでファイル名を取得し、コピー、移動の機能を実装。

と言う順番になる。

イクスプローラー側にドラッグ&ドロップするときは、イクスプローラー側の処理にある程度任せておけば良かったが、自アプリ側の方にドラッグ&ドロップするときはエフェクトの事などを考えて、シフトキーで判断し、シフトキーが押されているときだけ移動と言うことにしておく。
DragEventArgs.KeyState プロパティ を参照して、コードはこういう風にした。
private void listView1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
if ((e.KeyState & 4) == 4) // シフトキー判定
{
e.Effect = DragDropEffects.Move ;
}
else
{
e.Effect = DragDropEffects.Copy;
}
else
e.Effect = DragDropEffects.None;
}


後は、DragDropイベントで、e.Effect でコピーか移動かを判定し、FileクラスのCopy Move コマンドで指定されたファイルを表示中のディレクトリにコピー・移動すれば良い。
private void listView1_DragDrop(object sender, DragEventArgs e)
{
// コピー先のディレクトリが未選択なら、何もしない。
if (strListViewFullPath == "") { return; }

// Drag & Drop されたファイルのデータを取得
string[] s = (string[])e.Data.GetData(DataFormats.FileDrop, false);

// 取得したファイル名(複数)のコピー・移動を行う
int i;
for (i = 0; i < s.Length; i++)
{
// コピー元のフルパスファイル名
string wk_s = s[i] ;
// コピー先のフルパスファイル名
System.IO.FileInfo wk_info = new System.IO.FileInfo(wk_s);
// コピー先のフルパスファイル名
string wk_file = strListViewFullPath + @"\" + wk_info.Name;

// コピーは上書き前提で行う。
if (e.Effect == DragDropEffects.Copy)
{
File.Copy(wk_s, wk_file, true);
}
else
{
File.Move(wk_s, wk_file);
}
}
// ファイル名を再表示
show_FileName_To_ListView(strListViewFullPath);
}


次は、ツリーノードへ直接のドラッグ&ドロップかな。これが出来れば、アプリ単体でディレクトリのコピー、移動が出来るし。
タグ:YuruFiler
posted by ゆるきま at 21:53| Comment(0) | アプリ作成

2013年01月26日

イクスプローラーへのドラッグ&ドロップ

ファイルの表示の方は、とりあえず基本が出来たので、次はファイルのコピー、ペーストの機能を加える。

ファイルを表示しているリストビューの MultiSelect プロパティを true にしておけば、イクスプローラーと同様のコントロールキーとシフトキーの操作でアイテムを複数選択が出来る。

今回は、複数選択されたファイルを、ファイルイクスプローラーの方にドラッグ&ドロップで、コピー・移動が出来る様にする。

参考にさせて頂いたページはエクスプローラへファイルをDrag&Dropする

対象がリストボックスと、使って居る部品は違うが、DataObjectオブジェクトを DataFormats.FileDrop で作成して、DoDragDrop メソッドに渡すと言うやり方でOK。この辺は、ツリービューでドラッグ&ドロップを受け取るやり方の逆になる。

ドラッグ&ドロップの開始を行うイベントとしては、参考ページに倣って MouseDown イベントハンドラで行った。

ところで、ファイル名はリストビューのアイテムから取得出来るが、フルパスが必要となる。
ツリービューの方からも作れるが、リストビューを表示するときにフルパスを渡されているわけだから、現在表示中のフルパスをフォームクラス変数として保存しておく。

<追加のクラス変数>
string strListViewFullPath = "";
<リストビュー表示を行っている関数(treeView1_AfterSelec)の最後に1行追加>
strListViewFullPath = strPathName;


これを使って、

// ListView のドラッグが開始された事を、マウスボタンダウンイベントで判断
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
//マウスダウンした場所のアイテムが無効ならば何もしない。
ListViewItem wk_item = listView1.GetItemAt(e.X, e.Y);
if (wk_item == null) return ;
if (wk_item.Selected == false) return;

// 選択されているファイル名(パス付)を文字列配列として生成
ArrayList aryFileNames = new ArrayList();
foreach (ListViewItem i_item in listView1.SelectedItems)
{
string strPathName = strListViewFullPath + @"\" + i_item.Text;
aryFileNames.Add(strPathName);
}
string[] strFileNames = (string[])aryFileNames.ToArray(typeof(string));

//DataObjectを作成する
DataObject dataObj = new DataObject(DataFormats.FileDrop, strFileNames);

//ドラッグを開始する
DragDropEffects dde =
listView1.DoDragDrop(dataObj, DragDropEffects.All);

// ファイル名を再表示
show_FileName_To_ListView(strListViewFullPath);
}

複数ファイル名を DataObject として渡すためには、文字列配列として渡す必要があるが、個数が判らない場合は、文字列配列は使いにくい。このため いったん ArrayList に貯め込んでから、ToArrayメソッドで文字列配列に変換している。

この方法は、配列のサイズを変更するには? の記事を参考にしている。


これで、ドラッグ&ドロップを行えば、後はファイルイクスプローラーの方の処理に任せることになる。

コピーの場合は問題無いのだが、移動の場合は表示しているファイルが無くなるわけだから、リストアイテムを削除するか再表示する必要がある。

ここは、再表示の方が実体を表すわけだし、既にロジックは作ってあるわけだから、再表示の方法をとる。

リストビュー表示を行っている関数(treeView1_AfterSelec)は、イベントハンドラなので、直接呼び出すにはあまり適した形になっていないと言うか、余分な処理が入ってる。

このため、リストビュー表示を行う部分をプライベート関数として抜き出す。

// ディレクトリフルパスを受け取って、全ファイル名をリストビューに表示
private void show_FileName_To_ListView(string strPathName)
{
// 指定したディレクトリの全ファイル名(フルパス)を取得
string[] file_info_array = System.IO.Directory.GetFiles(strPathName, "*");

// 情報表示するリストビューをクリアしておく。
listView1.Items.Clear();

// それぞれのファイルの属性を取得して、リストビューに追加
foreach (string w_file in file_info_array)
{
// ファイルの情報を取得
System.IO.FileInfo wk_info = new System.IO.FileInfo(w_file);

// リストビューにアイテム追加
ListViewItem file_item_data = new ListViewItem();
file_item_data.Text = wk_info.Name; // ファイル名
file_item_data.SubItems.Add(wk_info.LastWriteTime.ToString()); // 更新時刻
file_item_data.SubItems.Add(wk_info.Length.ToString()); // サイズ
file_item_data.SubItems.Add(wk_info.Extension); // 拡張子

// 作ったItemをリストビューに追加
listView1.Items.Add(file_item_data);
}

// 現在表示しているファイルのフルパス作成用に、ディレクトリフルパスを保存
strListViewFullPath = strPathName;
}



抜き出された treeView1_AfterSelec イベントハンドラは、次の様になる。

// TreeView の ノードをクリックしたときのイベントハンドラ
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
// クリックされたノードの対応するフルパスを取得
string strPathName = get_FullPath_From_Node(e.Node);

// 指定したディレクトリの全ファイル名を表示
show_FileName_To_ListView(strPathName);
}


これでイクスプローラーに対する、コピー・移動は出来たが、逆にコピー・移動が出来ないので、次回はこちらの機能を実装する予定。


タグ:YuruFiler
posted by ゆるきま at 23:05| Comment(0) | アプリ作成

2013年01月24日

ファイル表示のソート

ファイル表示のさい、デフォルトを第1列目の昇順にしておいたけれど、これだとファイル名だけのソートで、更新日付や拡張子でソート出来ないと使い勝手が悪い。

それで、ヘッダーをクリックすると、その行の内容でソートする様にロジックを入れることにした。

ルールは、

・ヘッダーをクリックすると、その列でソート。
・ソートの昇順・降順の別は他の列ヘッダをクリックしても変わらない。
・同一のヘッダーを再度クリックすると、昇順・降順が反転。

1行目以外の項目でソートを行う方法は、次のページを参照した。

MSDN ListView.ListViewItemSorter プロパティ

これに従い、ソート用のクラスを作成する。

色々作法はあるのだろうけれど、1クラス1ファイルで良かろうと、CComparerForListView.cs ファイルを新規追加。ソート用クラスの中身は、次の様に作った。

デフォルトの参照に次の2つを追加

using System.Collections;
using System.Windows.Forms;

クラスのコードは次の通り

class CComparerForListView : IComparer
{
public int sort_flg = 0; // ソートの昇順・降順を指定(プロパティ)
public int col_no = 0; // ソート対象とする列位置(プロパティ)

// 比較するオブジェクトが2個渡される。
// この場合渡されるオブジェクト ListViewItem である事に注意。
public int Compare(object x, object y)
{
int ret_code = 0;
int sort_ad = 0;

// sort_flg が 0 の時は昇順、それ以外は降順としておく。
if(sort_flg == 0)
{
sort_ad = 1;
}
else
{
sort_ad = -1;
}

//対象となる列のItemのテキストを、文字列として比較を行い、その結果を返す。
ListViewItem i_x = (ListViewItem)x;
ListViewItem i_y = (ListViewItem)y;

string s_x = i_x.SubItems[col_no].Text ;
string s_y = i_y.SubItems[col_no].Text;

if (s_x.CompareTo(s_y) > 0)
{
ret_code = 1;
}
else
{
ret_code = -1;
}

// ソートの昇順・降順によって結果を反転させ、結果を返す。
return (ret_code * sort_ad);
}
}

参考にした資料だと、ソートする行を示すためのプロパティを作って、コンストラクタで行を指定するようになっていたが、ソートの昇順・降順を指定する都合もあるし、プロパティで作るのも冗長な気がして、Public 変数をプロパティ代わりに設置しておいた。

このため、リストビューの ListViewItemSorter に渡す前に、ローカル変数にソート用クラスのインスタンスを生成し、列、ソート昇順降順 を設定してから、そのローカル変数を渡す様にしている。

リストビューの変数をクリックした時のイベントハンドラは、
listView1_ColumnClick(object sender, ColumnClickEventArgs e)

ソート列とソート昇順・降順の切り替わりを判定するため、フォームクラス変数として、次の変数を設定しておいた。

int int_col_old = 0; // ListView のソート列番号を保存
int sort_flg_old = 0; // ListView の昇順・降順を保存

そのうえで、作ったヘッダークリック時のイベントハンドラの内容は次の通り。

private void listView1_ColumnClick(object sender, ColumnClickEventArgs e)
{
//クリックされた列が前回と同じなら、ソート昇順、降順を入れ替える。
if (e.Column == int_col_old)
{
sort_flg_old = (sort_flg_old + 1) % 2 ;
}
int_col_old = e.Column; // 現在のソート列番号を保存。

// ソートロジックを組み込んだクラス(IComparer 派生)のインスタンスを設定。
CComparerForListView sorter_obj = new CComparerForListView();
sorter_obj.col_no = e.Column;
sorter_obj.sort_flg = sort_flg_old;

//並び替え用のコンポーネントとして設定。
listView1.ListViewItemSorter = sorter_obj;
}


正直なところ、比較用のコンポーネントを指定するのに、IComparer インタフェース(クラス?)から、継承したクラスインスタンスを使用しなければならないのか、良く分からなかったりする。Compare 関数を持っているなら、別に継承しなくても良いような気がするのだが。

まあ、おいおいその辺も勉強していけば良いだけのこと。とりあえず、ソート関連はこのくらいにしておいて、次は選択ファイルをコピー(&ペースト)する方法だな。

タグ:YuruFiler
posted by ゆるきま at 23:06| Comment(0) | アプリ作成