では、CSV結合ツールの解説に入りたいと思います。
今回は、全ソースの掲載と、ファイル選択ダイアログ、フォルダ選択ダイアログを表示する方法に関する部分です。
全体のソースコード
全体のソースコードは以下の通りです。
今は中身が理解できないと思いますので、ここでは全体の雰囲気を把握するという意味で、軽く目をとおしていただければ結構です。
Visual Studio 2019 のプロジェクトファイル一式は、下記からダウンロードできますので、入力が面倒という方はご利用ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; namespace CsvJoiner { public partial class MainForm : Form { /// <summary> /// コンストラクタ /// </summary> public MainForm() { InitializeComponent(); //文字コードの選択項目を配列として保持 var charcode = new string[] { "shift-jis", "utf-8" }; //文字コード選択用コンボボックスに選択項目を登録 uxTargeCharCode.Items.AddRange(charcode); uxOutputCharCode.Items.AddRange(charcode); //文字コード選択用コンボボックスに対して最初の項目を選択済みにしておく uxTargeCharCode.SelectedIndex = 0; uxOutputCharCode.SelectedIndex = 0; } /// <summary> /// 結合対象フォルダ選択ボタンクリック処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UxSelectTargetFolder_Click(object sender, EventArgs e) { //フォルダ選択ダイアログのインスタンスを生成 FolderBrowserDialog folder = new FolderBrowserDialog() { SelectedPath = uxTargetFolder.Text }; //フォルダ選択ダイアログの表示と戻り値のチェック uxTargetFolder.Text = (folder.ShowDialog() == DialogResult.OK) ? folder.SelectedPath : uxTargetFolder.Text; } /// <summary> /// 出力ファイル名選択ボタンクリック処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UxSelectOutputFile_Click(object sender, EventArgs e) { //ファイル選択ダイアログのインスタンスを生成 SaveFileDialog folder = new SaveFileDialog() { FileName = uxOutputFile.Text,Filter = "csv|*.csv|全てのファイル|*.*" }; //ファイル選択ダイアログの表示と戻り値のチェック uxOutputFile.Text = (folder.ShowDialog() == DialogResult.OK) ? folder.FileName : uxOutputFile.Text; } /// <summary> /// 結合実行ボタンクリック処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UxExecJoin_Click(object sender, EventArgs e) { //ヘッダ行数の取得 var head_cnt = (int.TryParse(uxHeaderCount.Text, out int val)) ? val : 0; //結合対象CSVファイルの文字コード取得 var target_enc = Encoding.GetEncoding(uxTargeCharCode.Text); //出力CSVファイルの文字コード取得 var output_enc = Encoding.GetEncoding(uxOutputCharCode.Text); //結合対象フォルダにあるファイル名を全て読み込む var files = Directory.GetFiles(uxTargetFolder.Text); //最初のファイルを識別するためのフラグにTrueを代入 bool first_file_flg = true; //ファイルの数だけループ foreach(var file in files) { //ファイルを改行で分割し、List形式に変換 var lines = File.ReadAllLines(file, target_enc).ToList(); //最初のファイルか否かで処理分岐 if(first_file_flg) { //最初のファイルの場合、ヘッダごとファイル保存(既にファイルがあれば上書き) File.WriteAllLines(uxOutputFile.Text, lines, output_enc); first_file_flg = false; } else { //2ファイル以降は、ヘッダ行数を削除 for(int i = 0;i < head_cnt;i ++) { //Listの先頭行を削除 lines.RemoveAt(0); } //追加モードでファイル保存(既存ファイルに追加していく) File.AppendAllLines(uxOutputFile.Text,lines, output_enc); } } } } } |
先頭のusing 宣言
まず、先頭のusing ですが、9行まではVisual Studio が自動で追加してくれます。
10行目の using System.IO はファイルを読み書きするためのクラスなので、今回手で追加したものです。
1 2 3 4 5 6 7 8 9 10 |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; |
コンストラクタ
まず最初にコンストラクタの解説です。
ここでは、コンボボックスの初期設定を行っています。
具体的には、選択項目を登録し、最初の候補(shift-jis)を選択状態にしています。
1 2 3 4 5 6 7 8 9 10 |
//文字コードの選択項目を配列として保持 var charcode = new string[] { "shift-jis", "utf-8" }; //文字コード選択用コンボボックスに選択項目を登録 uxTargeCharCode.Items.AddRange(charcode); uxOutputCharCode.Items.AddRange(charcode); //文字コード選択用コンボボックスに対して最初の項目を選択済みにしておく uxTargeCharCode.SelectedIndex = 0; uxOutputCharCode.SelectedIndex = 0; |
フォルダとファイルの選択イベントハンドラ
結合対象フォルダの選択ボタンを選択した場合、フォルダ選択ダイアログを表示しますが、それが以下のコードになります。
1 2 3 4 5 |
//フォルダ選択ダイアログのインスタンスを生成 FolderBrowserDialog folder = new FolderBrowserDialog() { SelectedPath = uxTargetFolder.Text }; //フォルダ選択ダイアログの表示と戻り値のチェック uxTargetFolder.Text = (folder.ShowDialog() == DialogResult.OK) ? folder.SelectedPath : uxTargetFolder.Text; |
まず、ファイル選択ダイアログのインスタンスを生成するため、FolderBrowserDialogクラスをNewしていますが、そのタイミングで{ } にプロパティの初期設定を記述しています。
SelectedPathはFolderrowserDialogのプロパティで、通常は選択したフォルダ名が格納されます。
逆に、インスタンス生成時に任意のフォルダを代入しておくことで、そのフォルダが選択された状態でダイアログが表示されるようになります。
そして、ShowDialogメソッドを読んで画面にダイアログを表示し、何らかのボタンが押されて制御が戻ってきたとき、その結果OKボタンが押されていたなら、uxTargetFolderというTextboxコントロールに、選択したフォルダ名を代入しています。
同様に、ファイル選択ダイアログについても同じ方法で表示とボタンの判定、選択結果の取得を行っています。
1 2 3 4 5 |
//ファイル選択ダイアログのインスタンスを生成 SaveFileDialog folder = new SaveFileDialog() { FileName = uxOutputFile.Text,Filter = "csv|*.csv|全てのファイル|*.*" }; //ファイル選択ダイアログの表示と戻り値のチェック uxOutputFile.Text = (folder.ShowDialog() == DialogResult.OK) ? folder.FileName : uxOutputFile.Text; |
ファイル選択ダイアログには Filter というプロパティがあり、ここにフィルター条件を登録しておくことで、ファイル選択味に関係のないものを表示しないようにしています。
書式はタイトルとフィルター条件を縦棒 ‘|’ で区切ります。
例えば、
1 |
csv|*.csv|全てのファイル|*.* |
と記述すると、ダイアログには “csv” と”全てのファイル” が選択できるようになり、”csv”を選択すると、 *.csv というフィルター条件にヒットしたファイル名だけがダイアログに表示されます。
結合実行ボタンクリック処理
結合実行ボタンをクリックした際、まずヘッダ行数、結合対象の文字コード、出力CSVの文字コードを画面から取得します。
1 2 3 4 5 6 7 8 |
//ヘッダ行数の取得 var head_cnt = (int.TryParse(uxHeaderCount.Text, out int val)) ? val : 0; //結合対象CSVファイルの文字コード取得 var target_enc = Encoding.GetEncoding(uxTargeCharCode.Text); //出力CSVファイルの文字コード取得 var output_enc = Encoding.GetEncoding(uxOutputCharCode.Text); |
次に、結合対象フォルダに格納されているファイルの一覧を読み出し、files変数に格納しています。
1 2 |
//結合対象フォルダにあるファイル名を全て読み込む var files = Directory.GetFiles(uxTargetFolder.Text); |
次のソースコードが今回のポイントとなる部分で、first_file_flg という変数にtrue を代入しています。
フラグを使った1個目と2個目以降の判定
この変数は、読み込んだファイルが1個目なのか、2個目以降なのかを識別するためのものです。
この様に2つの状態のどちらであるかを表現するための変数を「フラグ変数」と呼んでいます。
1 2 |
//最初のファイルを識別するためのフラグにTrueを代入 bool first_file_flg = true; |
このフラグは初期状態として true をセットしておき、1個目を読み込んだ時点でfalse に設定します。
こうすることで、1個目のファイルならヘッダを含めて出力し、2個目以降ならヘッダを抜いて出力するという制御が可能となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
bool first_file_flg = true; foreach(var file in files) { //省略 if(first_file_flg) { //省略 first_file_flg = false; } else { //省略 } } |
それでは、具体的に1個目と2個目以降についての処理を見ていきましょう。
1個目と2個目以降の処理分岐
以下はループ処理でファイルを順次読み込み、1個目か2個目以降かを判断し、処理を分けている部分です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
//ファイルの数だけループ foreach(var file in files) { //ファイルを改行で分割し、List形式に変換 var lines = File.ReadAllLines(file, target_enc).ToList(); //最初のファイルか否かで処理分岐 if(first_file_flg) { //最初のファイルの場合、ヘッダごとファイル保存(既にファイルがあれば上書き) File.WriteAllLines(uxOutputFile.Text, lines, output_enc); first_file_flg = false; } else { //2ファイル以降は、ヘッダ行数を削除 for(int i = 0;i < head_cnt;i ++) { //Listの先頭行を削除 lines.RemoveAt(0); } //追加モードでファイル保存(既存ファイルに追加していく) File.AppendAllLines(uxOutputFile.Text,lines, output_enc); } } |
ファイルの読み込みは File.ReadAllLinesメソッドを、ファイルの書き込みは File.WriteAllLinesメソッド、File.AppendAllLines メソッドを使っています。
いずれも引数に文字コードをセットすることができますので、今回はComboboxのText部に設定されている文字コード名をセットしています。
WriteAllLineメソッドとAppendAllLinesメソッドの違いは、前者が既存のファイルに対して上書きするのに対し、後者は追加をしてくれるという点です。
既存ファイルがあった場合は追加せず作り直したいので、1個目のファイルはWriteAllLinesを使っています。
ヘッダ削除のためListクラスに変換
ReadAllLinesは読み込んだファイルを文字列の配列として返してくれます。
2個目以上はヘッダを抹消したいのですが、その処理はListクラスのRemoveAtを使うのが一番簡単なので、ファイルを読み込む際はListクラスに変換しています。
1 |
var lines = File.ReadAllLines(file, target_enc).ToList(); |
文字列の配列にはLinqが使えるので、ToList()メソッドを呼ぶことでListクラスに変換することが出来ます。
Linqについては こちら の記事をご覧ください。
まとめ
いいかがでしたでしょうか。
CSV結合ツールの全ソースと主要部分の解説を行いました。
CSVを分割するよりも結合する方が簡単ですね。
あとは好みに合わせてレイアウトを変更したり、機能追加にチャレンジしてみて下さい。
次回は、「結合実行ボタンクリック処理」を別のアルゴリズムで置き換えてみたいと思います。