今回はMP3タグ編集ツールの全ソースコードについて解説します。
前回の記事で説明した操作方法を念頭に、ソースコードに一通り目を通して頂いたあと、個々の解説を読んでいただくと分かりやすいかと思います。
また、Visual Studio 2019 のプロジェクトファイル一式を公開していますので、レイアウトの変更やソースコードの書き換え、機能の追記や変更を行い、動作がどう変わるか試して頂くと、より理解が深まると思います。
プロジェクト一式のダウンロード
プロジェクト一式(TagLib含む)は下記からダウンロードが可能です。
プロジェクトを任意のフォルダに解答の上、Mp3TagEditor.sln をダブルクリックするとVisual Studio 2019 が立ち上がり、ソースコードが表示されると思います。
もしTagLibの参照でエラーになるようなら、一旦TagLibをアンインストールの上、再度NuGetからインストールすることで解決します。
ソースコードの解説
それでは、さっそくソースコードを掲載しておきます。
今までに比べるとコード量が多いので身構えてしまうかもしれませんが、そんなに難しい事はしていません。
ここでは、ザックリと目を通して雰囲気を掴んでもらえればOKです。
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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 |
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; using System.Text.RegularExpressions; namespace Mp3TagEditor { public partial class MainForm : Form { /// <summary> /// フォーマットコンボボックスのドロップダウンリストを保存するファイル名 /// </summary> private const string FORMAT_HISTORY_FILE = "Mp3TagFormat.txt"; /// <summary> /// コンストラクタ /// </summary> public MainForm() { InitializeComponent(); //対象項目コンボボックスのドロップダウンを入力不可に設定 uxTargetColumn.DropDownStyle = ComboBoxStyle.DropDownList; //スクリーンの中央にWindowを表示 this.StartPosition = FormStartPosition.CenterScreen; } /// <summary> /// フォームロード処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_Load(object sender, EventArgs e) { //DataGridViewにドラッグ&ドロップ機能を付加 SetDragDrop(uxMp3List); //DataGridViewに行番号表示機能を付加 SetLineNumber(uxMp3List); //空のDataTableを作成し、DataGridViewに表示 ShowData(CreateDataTable()); //ReadOnlyがfalseの列名を対象項目コンボボックスに設定 var item = uxMp3List.Columns.Cast<DataGridViewColumn>().AsEnumerable().Where(i => ! i.ReadOnly).Select(i => i.Name).ToArray(); uxTargetColumn.Items.AddRange(item); //フィルターの簡易モードをONにする uxIsEasy.Checked = true; //正規表現を用いた置換を有効にする uxUseRegular.Checked = true; //変更前の初期値として、先頭から'-','.',' 'が見つかるまでが対象となる正規表現を設定 uxBeforeString.Text = "^.*?[-. ]"; //フォーマットのドロップダウンリストが存在するか確認 if (File.Exists(FORMAT_HISTORY_FILE)) { //フォーマットのドロップダウンリストの内容をファイルから読み込んで復活 uxFormat.Items.AddRange(File.ReadAllLines(FORMAT_HISTORY_FILE)); } } /// <summary> /// フォームの終了処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_FormClosed(object sender, FormClosedEventArgs e) { //フォーマットのドロップダウンリストの内容をファイルに保存 File.WriteAllLines(FORMAT_HISTORY_FILE, uxFormat.Items.Cast<string>().ToArray()); } /// <summary> /// コントロールにドラッグ&ドロップの機能を付加する /// </summary> /// <param name="control"></param> private void SetDragDrop(Control control) { //ドラッグ&ドロップを受け付けられるようにする control.AllowDrop = true; //ドラッグが開始された時のイベント処理(マウスカーソルをドラッグ中のアイコンに変更) control.DragEnter += (s, e) => { //ファイルがドラッグされたとき、カーソルをドラッグ中のアイコンに変更し、そうでない場合は何もしない。 e.Effect = (e.Data.GetDataPresent(DataFormats.FileDrop)) ? DragDropEffects.Copy : e.Effect = DragDropEffects.None; }; //ドラッグ&ドロップが完了した時の処理(ファイル名を取得し、ファイルの中身をTextプロパティに代入) control.DragDrop += (s, e) => { //ドロップされたすべてのファイル名を取得する string[] filenames = (string[])e.Data.GetData(DataFormats.FileDrop, false); //タグの一覧を表示 ShowData(GetMp3Tag(filenames)); }; } /// <summary> /// DataGridViewに列番号を表示する機能を付加する /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SetLineNumber(DataGridView dgb) { dgb.CellPainting += (sender, e) => { //列ヘッダーかどうか調べる if (e.ColumnIndex < 0 && e.RowIndex >= 0) { //セルを描画する e.Paint(e.ClipBounds, DataGridViewPaintParts.All); //行番号を描画する範囲を決定する //e.AdvancedBorderStyleやe.CellStyle.Paddingは無視しています Rectangle indexRect = e.CellBounds; indexRect.Inflate(-2, -2); //行番号を描画する TextRenderer.DrawText(e.Graphics, (e.RowIndex + 1).ToString(), e.CellStyle.Font, indexRect, e.CellStyle.ForeColor, TextFormatFlags.Right | TextFormatFlags.VerticalCenter); //描画が完了したことを知らせる e.Handled = true; } }; } /// <summary> /// DataTableの内容をDataGridViewに表示し、DataGridViewの体裁を整える /// </summary> /// <param name="dt"></param> private void ShowData(DataTable dt) { uxMp3List.Font = new Font("Meiryo UI", 9f); //フォントの指定 uxMp3List.AllowUserToAddRows = false; //ユーザー操作による行追加を禁止 uxMp3List.AllowUserToDeleteRows = false; //ユーザー操作による行削除を禁止 uxMp3List.BackgroundColor = Color.White; //DataGridViewの背景色を設定 uxMp3List.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(230, 230, 255); //DataGridViewの奇数行の背景色を設定 uxMp3List.DataSource = dt; // DataTablをDataGridViewに表示 uxMp3List.AllowUserToOrderColumns = true; //ユーザー操作による列の入れ替えを許可 //DataGridViewに設定したい列幅の配列を作り、これを使ってDataGridViewの列幅を設定 int[] width = new int[] {200,200,200,200,200,200,100,100,100,200,200,120,120,120,300}; Enumerable.Range(0, width.Length).Select(i => uxMp3List.Columns[i].Width = width[i]).ToList(); //DataGridViewの列に設定したいReadOnly属性の配列を作り、これを使って列のReadOnly属性を設定、 bool[] enable = new bool[] { false, false, false, false, false, false, false, false, false, false, false, true, true, true, true }; Enumerable.Range(0, enable.Length).Select(i => uxMp3List.Columns[i].ReadOnly = enable[i]).ToList(); } /// <summary> /// MP3タグを格納するDataTableの作成 /// </summary> /// <returns></returns> private DataTable CreateDataTable() { DataTable dt = new DataTable(); dt.Columns.Add("ファイル名"); dt.Columns.Add("タイトル"); dt.Columns.Add("アーティスト"); dt.Columns.Add("アルバムアーティスト"); dt.Columns.Add("アルバム名"); dt.Columns.Add("作曲家"); dt.Columns.Add("年"); dt.Columns.Add("トラック番号"); dt.Columns.Add("ディスク番号 "); dt.Columns.Add("ジャンル"); dt.Columns.Add("コメント"); dt.Columns.Add("時間"); dt.Columns.Add("ビットレート(Kbps)"); dt.Columns.Add("タグ"); dt.Columns.Add("ファイルパス"); return dt; } /// <summary> /// MP3タグの読み込み /// </summary> /// <param name="fileNames"></param> /// <returns></returns> private DataTable GetMp3Tag(string[] fileNames) { //読み取ったTagを格納するDataTableを作成 DataTable dt = CreateDataTable(); try { //ドロップされたファイル名にフォルダが含まれていれば、その配下のファイルを追加する List<string> filelist = new List<string>(); foreach (string filename in fileNames) { //ファイル名がディレクトリ名で処理を分岐 if (Directory.Exists(filename)) { //フォルダ名なら配下のファイルを全て取得し、filelistに追加 filelist.AddRange(Directory.GetFiles(filename, "*.mp3", SearchOption.AllDirectories).ToArray()); } else { //ファイル名なら、そのまま filelist に追加 filelist.Add(filename); } } //タグを取得 foreach (string filename in filelist) { //TagLibクラスのインスタンスを生成 TagLib.File mp3 = TagLib.File.Create(filename); //DataRowを作成し DataRow dr = dt.NewRow(); //MP3タグの値をセット dr["ファイル名"] = Path.GetFileNameWithoutExtension(filename); dr["タイトル"] = UTF2Jis(mp3.Tag.Title); dr["アーティスト"] = string.Join(";", mp3.Tag.Artists.Select(i=> UTF2Jis(i))); dr["アルバムアーティスト"] = string.Join(";", mp3.Tag.AlbumArtists.Select(i => UTF2Jis(i))); dr["アルバム名"] = UTF2Jis(mp3.Tag.Album); dr["作曲家"] = string.Join(";", mp3.Tag.Composers.Select(i => UTF2Jis(i))); dr["年"] = mp3.Tag.Year; dr["トラック番号"] = mp3.Tag.Track; dr["ディスク番号 "] = mp3.Tag.Disc; dr["ジャンル"] = string.Join(";", mp3.Tag.Genres.Select(i => UTF2Jis(i))); dr["時間"] = string.Format("{0:D2}:{1:D2}:{2:D2}", mp3.Properties.Duration.Hours, mp3.Properties.Duration.Minutes, mp3.Properties.Duration.Seconds); dr["ビットレート(Kbps)"] = mp3.Properties.AudioBitrate; dr["タグ"] = mp3.Tag.TagTypes; dr["コメント"] = UTF2Jis(mp3.Tag.Comment); dr["ファイルパス"] = filename; //DataTableにDataRowを追加 dt.Rows.Add(dr); } } catch(Exception exp) { //例外が発生した時のメッセージを表示 MessageBox.Show(exp.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } //読み込んだ直後は、DataTableの各行のステータスが「追加」状態になっているので、一旦リセットする。 //こうすることで、以降DataTableに変更を行うと、ステータスが「変更」状態になる。 dt.AcceptChanges(); return dt; } /// <summary> /// MP3タグの書き込み /// </summary> /// <param name="dt"></param> private void SetMp3Tag(DataTable dt) { try { //全ての行に対してタグ書き換えの処理を実行 foreach (DataRow dr in dt.Rows) { //値が変更された行だけタグを書き換えたいので、変更されたかどうかをDataRowのRowStatusでチェック if (dr.RowState == DataRowState.Modified) { var filename = dr["ファイルパス"].ToString(); TagLib.File mp3 = TagLib.File.Create(filename); mp3.Tag.Title = dr["タイトル"].ToString(); mp3.Tag.Artists = dr["アーティスト"].ToString().Split(';').Select(i => i.Trim()).ToArray(); mp3.Tag.AlbumArtists = dr["アルバムアーティスト"].ToString().Split(';').Select(i => i.Trim()).ToArray(); mp3.Tag.Album = dr["アルバム名"].ToString(); mp3.Tag.Composers = dr["作曲家"].ToString().Split(';').Select(i => i.Trim()).ToArray(); mp3.Tag.Year = uint.TryParse(dr["年"].ToString(), out uint year) ? year : (uint)DateTime.Now.Year; mp3.Tag.Track = uint.TryParse(dr["トラック番号"].ToString(), out uint track) ? track : 0; mp3.Tag.Disc = uint.TryParse(dr["ディスク番号 "].ToString(), out uint disc) ? disc : 0; mp3.Tag.Genres = dr["ジャンル"].ToString().Split(';').Select(i => i.Trim()).ToArray(); mp3.Tag.Comment = dr["コメント"].ToString(); //実際のmp3ファイルのタグを更新 mp3.Save(); //ファイル名列に表示されているファイル名とファイルパス列のファイル名が異なるかチェック string new_name = dr["ファイル名"].ToString(); if (new_name != Path.GetFileNameWithoutExtension(filename)) { //ファイルパス列のフルパスからファイル名部分を置き換えた新しいファイルパスを作成 new_name = Path.Combine(Path.GetDirectoryName(filename), new_name) + Path.GetExtension(filename); //ファイル名の変更(Moveメソッドを利用) File.Move(filename, new_name); } } } } catch (Exception exp) //例外処理 { //何らかのエラーが発生した場合、エラー内容を表示 MessageBox.Show(exp.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// <summary> /// 文字コードがShift-JISか否かを判定し、Shift-JISでなければ Shift-JISに変換する /// </summary> /// <param name="str"></param> /// <returns></returns> private string UTF2Jis(string str) { //指定された文字列が null なら、空の文字列を返す if (str == null) { return ""; } //Shift-JISのエンコーディングを取得 var enc = System.Text.Encoding.GetEncoding("shift-jis"); //取得したエンコーディングを使って引数ををバイト列に変換後、再びShift-JISの文字列に変換 string cnv_str = enc.GetString(enc.GetBytes(str)); //上記の変換結果と引数を比較し、異なればShift-JISではないと判断 if (str != cnv_str) { //引数をShift-JISに変換 var l_bytes = System.Text.Encoding.Unicode.GetBytes(str); //Unicodeにバイト列に変換すると1バイト毎に0x00 が付加されるため、0x00を取り除いてShift-JISに変換 return System.Text.Encoding.GetEncoding("shift-jis").GetString(l_bytes.Where(i => i > 0).ToArray<byte>()); } return str; } /// <summary> /// 更新ボタンクリック処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void uxUpdate_Click(object sender, EventArgs e) { //タグ反映の最終確認を行う if(MessageBox.Show("変更内容をMP3ファイルに反映します。よろしいですか?", "確認", MessageBoxButtons.OKCancel, MessageBoxIcon.Asterisk) == DialogResult.OK) { //表示中の行(MP3フィアル)に対してタグを更新 SetMp3Tag((DataTable)uxMp3List.DataSource); } } /// <summary> /// フィルター実行ボタンクリック処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void uxExecFiliter_Click(object sender, EventArgs e) { try { //絞り込み条件の初期値として uxFilter.Textをそのまま代入 string filter = uxFilter.Text; //簡易モードにチェックが入っている場合、uxFilter.Textの内容を解析 if (uxIsEasy.Checked) { //簡易モードの場合、入力された文字列を半角又は全角スペースで分割 string[] items = uxFilter.Text.Split(new char[] { ' ', ' ' }, StringSplitOptions.RemoveEmptyEntries); //列名と条件の組み合わせを順に取り出しListに追加 List<string> list = new List<string>(); for (int i = 0; i < items.Length / 2; i++) { list.Add(items[i * 2] + " like '%" + items[i * 2 + 1] + "%'"); } //取り出した列と条件の組み合わせに対して、間に and を挟んで結合 filter = string.Join(" and ", list.ToArray()); } //DataGridViewが表示中のDataTableに対して、絞り込み条件を設定 ((DataTable)uxMp3List.DataSource).DefaultView.RowFilter = filter; } catch (Exception exp) //例外処理 { //何らかのエラーが発生した場合、エラー内容を表示 MessageBox.Show(exp.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// <summary> /// フィルター解除ボタンクリック処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void uxDeleteFilter_Click(object sender, EventArgs e) { ((DataTable)uxMp3List.DataSource).DefaultView.RowFilter = ""; } /// <summary> /// 置換ボタンクリック処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void uxExecCopy_Click(object sender, EventArgs e) { //対象項目が未入力ならエラーメッセージを表示 if(! uxMp3List.Columns.Contains(uxTargetColumn.Text)) { MessageBox.Show("対象項目を指定して下さい", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } //フォーマットが未入力ならエラーメッセージを表示 if (uxFormat.Text.Trim() == "") { MessageBox.Show("フォーマットを入力して下さい", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } //連番の初期値を設定 int no = 1; try { //DataGridViewに表示されている行をループ処理で取り出す foreach (DataGridViewRow dgr in uxMp3List.Rows) { //フォーマットの中身に{列名}の記述があれば、列の値に置き換える string str = replace(uxMp3List.Columns, dgr, uxFormat.Text); //{連番}の記述があれば、連番に置き換える str = str.Replace("{連番}", string.Format("{0:" + uxNumberFormat.Text + "}", no++)); //フォーマット加工した結果を対象項目の列に代入 dgr.Cells[uxTargetColumn.Text].Value = str; } //過去に使ったフォーマットならなら、ドロップダウンから一旦削除 string format = uxFormat.Text; if (uxFormat.Items.Contains(format)) { //ドロップダウンから削除 uxFormat.Items.Remove(format); //RemoveするとText部がクリアされるので復旧 uxFormat.Text = format; } //ドロップダウンの先頭に登録 uxFormat.Items.Insert(0, format); } catch(Exception exp) //例外処理 { //何らかのエラーが発生した場合、エラー内容を表示 MessageBox.Show(exp.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } return; ///{列名}の記述を列の値に置き換えるローカル関数 string replace(DataGridViewColumnCollection p_col,DataGridViewRow p_dgvr,string p_str) { for(int i = 0;i < p_col.Count;i ++) { // "{列名}"の文字列を作成 string l_sour = "{" + p_col[i].Name + "}"; // "{列名}"の文字列を列の値に置換 p_str = p_str.Replace(l_sour, p_dgvr.Cells[i].Value.ToString()); } return p_str; } } /// <summary> /// フォーマット履歴から表示中のフォーマットを削除 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void uxDelHistory_Click(object sender, EventArgs e) { uxFormat.Items.Remove(uxFormat.Text); } /// <summary> /// 文字列置換の実行ボタンクリック処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void uxExecReplace_Click(object sender, EventArgs e) { //対象項目が未入力ならエラーメッセージを表示 if (!uxMp3List.Columns.Contains(uxTargetColumn.Text)) { MessageBox.Show("対象項目を指定して下さい", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } try { //正規表現クラスのインスタンスを生成 Regex reg = new Regex(uxBeforeString.Text); //表示中の行に対するループ処理 foreach (DataGridViewRow dgr in uxMp3List.Rows) { //置換対象となる文字列を取得 string target = dgr.Cells[uxTargetColumn.Text].Value.ToString(); //正規表現チェックボックスのチェック状態を確認 if (uxUseRegular.Checked) { //正規表現にチェックが入っていると、正規表現として文字列置換を実施 dgr.Cells[uxTargetColumn.Text].Value = new Regex(uxBeforeString.Text).Replace(target, uxAfterString.Text); } else { //正規表現にチェックが入っていない場合、単純な文字列置換を実行 dgr.Cells[uxTargetColumn.Text].Value = target.Replace(uxBeforeString.Text, uxAfterString.Text); } } } catch(Exception exp) { MessageBox.Show(exp.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } } |
ソースコードの解説
各行ごとに極力コメントを記載するようにしていますので、ソースコードと合わせて読んでいただくと理解しやすいと思います。
ただ、すべてを読んで理解する必要はありませんのでご安心下さい。
大切なのは、「どの部分を何処までコピーして、どこを変更したら自分のプログラムに組み込めるか」を理解する事です。
ソースコードを見ていて、少々分からない部分があっても、「なんとなくこういう事をしているんだ」という程度の理解が出来れば十分です。
それよりも、「ここからここまでが○○○を処理する部分だから、その部分を自分のソースコードにコピーしよう」とか、「この部分を自分のソースコードに取り込むのなら、この変数にはこの値を入れとく必要があるな」という観点で理解してもらうのが大切です。
こうすることで、他人が公開してくれているソースコードを流用する力が付いていきます。
前置きが長くなりましたが、今回公開したソースコードを自分様にカスタマイズしたり、あるいは皆さんのソースコードに流用する時のヒントになるような観点で、ポイントとなる部分を解説していきましょう。
コンストラクタとフォームロード処理
コンストラクタにはドロップダウンをMainFormの表示位置を設定するコードを書いています。
1 2 3 4 5 |
//対象項目コンボボックスのドロップダウンを入力不可に設定 uxTargetColumn.DropDownStyle = ComboBoxStyle.DropDownList; //スクリーンの中央にWindowを表示 this.StartPosition = FormStartPosition.CenterScreen; |
これらはVisual Studio のプロパティでも設定することが出来ますので、好みに合わせてもらえればOKです。

フォームロード(MainForm_Load)も
1 2 3 4 5 6 7 8 |
//フィルターの簡易モードをONにする uxIsEasy.Checked = true; //正規表現を用いた置換を有効にする uxUseRegular.Checked = true; //変更前の初期値として、先頭から'-','.',' 'が見つかるまでが対象となる正規表現を設定 uxBeforeString.Text = "^.*?[-. ]"; |
という記述がありますが、これらはコンストラクタに記述しても良いですし、 Visual Studio のプロパティを設定しても構いません。
フォームロードはコンストラクタの後に呼び出されるイベントで、画面が表示されるタイミングで呼び出されますので、
this.StartPosition = FormStartPosition.CenterScreen;
だけはフォームロードの前(つまりコンストラクタが実行される時)に設定しておく必要があります。
フォームロードでは、DataGridViewにドラッグ&ドロップの受付と行番号の表示に対応させるため、次の2行のコードを記述しています。
1 2 3 4 5 |
//DataGridViewにドラッグ&ドロップ機能を付加 SetDragDrop(uxMp3List); //DataGridViewに行番号表示機能を付加 SetLineNumber(uxMp3List); |
これらは他のプログラムで再利用しやすいように、メソッド化しています。
SetDragDropメソッド
DataGridViewにドラッグ&ドロップの機能を付加するプログラムです。
実際にドラッグ&ドロップした時の処理を17行目と20行目に記述していますので、必要に応じて書き換えてください。
DataGridViewに対して、Visual Studio から DragEnterとDragDropのイベントハンドラを作成し、そこに記述することも可能ですが、そうするとメソッド化できない(再利用しにくい)ため、今回はこのような記述をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
private void SetDragDrop(Control control) { //ドラッグ&ドロップを受け付けられるようにする control.AllowDrop = true; //ドラッグが開始された時のイベント処理(マウスカーソルをドラッグ中のアイコンに変更) control.DragEnter += (s, e) => { //ファイルがドラッグされたとき、カーソルをドラッグ中のアイコンに変更し、そうでない場合は何もしない。 e.Effect = (e.Data.GetDataPresent(DataFormats.FileDrop)) ? DragDropEffects.Copy : e.Effect = DragDropEffects.None; }; //ドラッグ&ドロップが完了した時の処理(ファイル名を取得し、ファイルの中身をTextプロパティに代入) control.DragDrop += (s, e) => { //ドロップされたすべてのファイル名を取得する string[] filenames = (string[])e.Data.GetData(DataFormats.FileDrop, false); //タグの一覧を表示 ShowData(GetMp3Tag(filenames)); }; } |
SetLineNumberメソッド
DataGridViewに行番号を付加する部分です。
これはGoogle検索で見つかったソースコードをそのまま掲載しています。
少し改造すると行番号の色を変えたり、別のセルに別の文字列を表示することも出来そうですが、単に行番号を表示したいというだけなら、このまま利用できます。
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 |
private void SetLineNumber(DataGridView dgb) { dgb.CellPainting += (sender, e) => { //列ヘッダーかどうか調べる if (e.ColumnIndex < 0 && e.RowIndex >= 0) { //セルを描画する e.Paint(e.ClipBounds, DataGridViewPaintParts.All); //行番号を描画する範囲を決定する //e.AdvancedBorderStyleやe.CellStyle.Paddingは無視しています Rectangle indexRect = e.CellBounds; indexRect.Inflate(-2, -2); //行番号を描画する TextRenderer.DrawText(e.Graphics, (e.RowIndex + 1).ToString(), e.CellStyle.Font, indexRect, e.CellStyle.ForeColor, TextFormatFlags.Right | TextFormatFlags.VerticalCenter); //描画が完了したことを知らせる e.Handled = true; } }; } |
ShowDataメソッド
これはDataGridViewの体裁を整えつつ、DataTableの内容を表示するメソッドです。
引数に表示したDataTableを渡す仕様になっていて、8行目にDataGridViewのDataSourceプロパティに引数を代入しています。
これは8行目である必要はなく、メソッドの先頭に記述しても構いません。
ただ、列幅や列のReadOnly属性、列の入れ替え許可などの設定は、列が存在しないと出来ません。
逆に言うと、DataSourceにDataTableを代入した時点で新たな列が作られるため、その列に対して設定する必要があるので、ここだけは順番を気にする必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private void ShowData(DataTable dt) { uxMp3List.Font = new Font("Meiryo UI", 9f); //フォントの指定 uxMp3List.AllowUserToAddRows = false; //ユーザー操作による行追加を禁止 uxMp3List.AllowUserToDeleteRows = false; //ユーザー操作による行削除を禁止 uxMp3List.BackgroundColor = Color.White; //DataGridViewの背景色を設定 uxMp3List.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(230, 230, 255); //DataGridViewの奇数行の背景色を設定 uxMp3List.DataSource = dt; // DataTablをDataGridViewに表示 uxMp3List.AllowUserToOrderColumns = true; //ユーザー操作による列の入れ替えを許可 //DataGridViewに設定したい列幅の配列を作り、これを使ってDataGridViewの列幅を設定 int[] width = new int[] {200,200,200,200,200,200,100,100,100,200,200,120,120,120,300}; Enumerable.Range(0, width.Length).Select(i => uxMp3List.Columns[i].Width = width[i]).ToList(); //DataGridViewの列に設定したいReadOnly属性の配列を作り、これを使って列のReadOnly属性を設定、 bool[] enable = new bool[] { false, false, false, false, false, false, false, false, false, false, false, true, true, true, true }; Enumerable.Range(0, enable.Length).Select(i => uxMp3List.Columns[i].ReadOnly = enable[i]).ToList(); } |
また、列幅やReadOnlyは設定したい値を配列に持たせて、Enumerable.Range を使ってuxMp3List.Columns[i] に代入していますが、これは好みの問題です。
以下の様に全ての列に対して、インデックス番号や列名を使って値を代入する方法もあります。
1 2 3 |
uxMp3List.Columns[0].Width =200; uxMp3List.Columns[1].Width =200; uxMp3List.Columns[2].Width =200; |
1 2 3 |
uxMp3List.Columns["ファイル名"].Width =200; uxMp3List.Columns["タイトル"].Width =200; uxMp3List.Columns["アルバムアーティスト"].Width =200; |
分かりやすさを優先するなら列名を使って個々の列を設定する方法が良いと思いますが、ソースコードの行数が長くなるので今回は採用しませんでした。
CreateDataTableメソッド
これはuxMp3List に表示するDataTableを作るためだけのメソッドです。
ShowDataメソッドで列幅を設定した時のように、列名を配列にしてEnumerable.Rangeで列を追加していく方法も出来ますが、別のソースコードに(列名を書き換えて)流用する際、Columnsの第2引数に変数の型を指定したい場合もあるため、今回はこのようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private DataTable CreateDataTable() { DataTable dt = new DataTable(); dt.Columns.Add("ファイル名"); dt.Columns.Add("タイトル"); dt.Columns.Add("アーティスト"); dt.Columns.Add("アルバムアーティスト"); dt.Columns.Add("アルバム名"); dt.Columns.Add("作曲家"); dt.Columns.Add("年"); dt.Columns.Add("トラック番号"); dt.Columns.Add("ディスク番号 "); dt.Columns.Add("ジャンル"); dt.Columns.Add("コメント"); dt.Columns.Add("時間"); dt.Columns.Add("ビットレート(Kbps)"); dt.Columns.Add("タグ"); dt.Columns.Add("ファイルパス"); return dt; } |
DataTableを作る部分をわざわざメソッドにしている理由は、フォームロードとドラッグ&ドロップの2か所で呼び出す必要があるからです。
プログラム起動時、uxMp3List に何も表示されないと寂しいので、せめて列名だけでも表示させたいと考えた時、フォームロードにも同じことを記述する必要が生じます。
それを避けるためメソッド化しました。
GetMp3Tagメソッド
SetDragDropメソッドの中で呼ばれていますが、ドラッグ&ドロップで受け取ったファイル名のMP3タグを読み出し、DataTableに格納して返すメソッドです。
最初にCreateDataTableメソッドでDataTableを作成しています。
次に、引数として渡されたファイル名の配列からファイル名を取り出し、 filelist に追加しています。
何故 filelist に追加しているかと言うと、渡されたファイル名が実はフォルダだった場合、そのフォルダ以下のファイルを全て取り出したかったからです。
その為、渡されたファイル名がフォルダか否かをチェックし、フォルダだったら配下のファイルを全て取り出して filelist に追加しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//ドロップされたファイル名にフォルダが含まれていれば、その配下のファイルを追加する List<string> filelist = new List<string>(); foreach (string filename in fileNames) { //ファイル名がディレクトリ名で処理を分岐 if (Directory.Exists(filename)) { //フォルダ名なら配下のファイルを全て取得し、filelistに追加 filelist.AddRange(Directory.GetFiles(filename, "*.mp3", SearchOption.AllDirectories).ToArray()); } else { //ファイル名なら、そのまま filelist に追加 filelist.Add(filename); } } |
こうやってドラッグ&ドロップで渡されたファイルを全て取り出し、ループ処理にて1ファイルずつMP3タグを抽出、DataTableに追加しています。
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 |
foreach (string filename in filelist) { //TagLibクラスのインスタンスを生成 TagLib.File mp3 = TagLib.File.Create(filename); //DataRowを作成し DataRow dr = dt.NewRow(); //MP3タグの値をセット dr["ファイル名"] = Path.GetFileNameWithoutExtension(filename); dr["タイトル"] = UTF2Jis(mp3.Tag.Title); dr["アーティスト"] = string.Join(";", mp3.Tag.Artists.Select(i=> UTF2Jis(i))); dr["アルバムアーティスト"] = string.Join(";", mp3.Tag.AlbumArtists.Select(i => UTF2Jis(i))); dr["アルバム名"] = UTF2Jis(mp3.Tag.Album); dr["作曲家"] = string.Join(";", mp3.Tag.Composers.Select(i => UTF2Jis(i))); dr["年"] = mp3.Tag.Year; dr["トラック番号"] = mp3.Tag.Track; dr["ディスク番号 "] = mp3.Tag.Disc; dr["ジャンル"] = string.Join(";", mp3.Tag.Genres.Select(i => UTF2Jis(i))); dr["時間"] = string.Format("{0:D2}:{1:D2}:{2:D2}", mp3.Properties.Duration.Hours, mp3.Properties.Duration.Minutes, mp3.Properties.Duration.Seconds); dr["ビットレート(Kbps)"] = mp3.Properties.AudioBitrate; dr["タグ"] = mp3.Tag.TagTypes; dr["コメント"] = UTF2Jis(mp3.Tag.Comment); dr["ファイルパス"] = filename; //DataTableにDataRowを追加 dt.Rows.Add(dr); } |
メソッドの最後(リターンの直前)にAcceptChangesメソッドを読んでいます。
1 |
dt.AcceptChanges(); |
DataTableの各行(DataRow)はステータス(RowStat)を持っていて、その行が追加(Added)されたのか、変更(Modified)されたが分かるようになっています。
後にMP3ファイルに編集内容を書き戻す時、タグを編集したものだけを特定する際にRowStateが使えそうですが、そこには少しだけ工夫が必要です。
直前のforeach ループでMP3タグをDataTableに追加した訳ですから、今の RowState は追加状態(Added)になっています。
この状態でDataRowに変更を加えても、RowStat は Addedが優先されてModifiedにはならないのです。
このRowStateをリセットするメソッドが AcceptChanges であり、このメソッドを呼ぶことでDataTableに登録されている全てのDataRowのRowStateが リセット(Unchanged)されます。
こうすることで、以降このDataTableに対して変更を加えると、RowState が Modified になってくれます。
SetMp3Tag
引数で渡されたDataTableの内容(タグ情報)をMP3ファイルに書き込むメソッドです。
ポイントとしては、 foreach でループしながら DataRow を取り出し、RowStateが変更状態(Modified)である場合のみ、MP3ファイルにタグを書き戻すようにしている点です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
foreach (DataRow dr in dt.Rows) { //値が変更された行だけタグを書き換えたいので、変更されたかどうかをDataRowのRowStatusでチェック if (dr.RowState == DataRowState.Modified) { var filename = dr["ファイルパス"].ToString(); TagLib.File mp3 = TagLib.File.Create(filename); mp3.Tag.Title = dr["タイトル"].ToString(); mp3.Tag.Artists = dr["アーティスト"].ToString().Split(';').Select(i => i.Trim()).ToArray(); mp3.Tag.AlbumArtists = dr["アルバムアーティスト"].ToString().Split(';').Select(i => i.Trim()).ToArray(); mp3.Tag.Album = dr["アルバム名"].ToString(); mp3.Tag.Composers = dr["作曲家"].ToString().Split(';').Select(i => i.Trim()).ToArray(); mp3.Tag.Year = uint.TryParse(dr["年"].ToString(), out uint year) ? year : (uint)DateTime.Now.Year; mp3.Tag.Track = uint.TryParse(dr["トラック番号"].ToString(), out uint track) ? track : 0; mp3.Tag.Disc = uint.TryParse(dr["ディスク番号 "].ToString(), out uint disc) ? disc : 0; mp3.Tag.Genres = dr["ジャンル"].ToString().Split(';').Select(i => i.Trim()).ToArray(); mp3.Tag.Comment = dr["コメント"].ToString(); //実際のmp3ファイルのタグを更新 mp3.Save(); |
実際のMP3ファイルにタグを書き戻すには、Save メソッドを呼ぶだけで完了します。
タグを書き戻した次の処理は、ファイル名の変更処理です。
1列目のタイトル列と、最終列のファイルパスの内容を比較して、違っていたらタイトル列の内容でファイル名を変更しています。
1 2 3 4 5 6 7 8 9 |
//ファイル名列に表示されているファイル名とファイルパス列のファイル名が異なるかチェック string new_name = dr["ファイル名"].ToString(); if (new_name != Path.GetFileNameWithoutExtension(filename)) { //ファイルパス列のフルパスからファイル名部分を置き換えた新しいファイルパスを作成 new_name = Path.Combine(Path.GetDirectoryName(filename), new_name)+ Path.GetExtension(filename); //ファイル名の変更(Moveメソッドを利用) File.Move(filename, new_name); } |
ファイル名の変更はMoveメソッドを使います。
Moveメソッドはファイルの移動を行うメソッドですが、フォルダを同じにしておけば、ファイル名の変更になります。
変更後のファイル名を作るため、現在のフルパスからディレクトリ(フォルダ)と拡張子を取り出しているので、少々ややこしい記述になっています。
UTF2Jisメソッド
文字コードがShift-JISか否かを判定し、Shift-JISでなければ Shift-JISに変換するメソッドです。
MP3タグのタイトル、アーティスト名、アルバム名、コメントなどは日本語が登録できるのですが、MP3タグにもバージョンがあって、SHIFT-JIS で入っていたりUTF16で入っていたりと統一されていません。
これに対応するため、SHIFT-JISか否かを判定し、SHIFT-JIS でなければ SHIFT-JIS に文字コードを変換するようにしています。
具体的には、文字コードを一旦バイトの配列に変換して、再び SHIFT-JIS に変換し直した結果と、元々の文字列を比較しています。
もし元々の文字列がSHIFT-JISなら元に戻るはずなので、この方法で元に戻らなければ少なくともSHIFT-JISではなかったと判断できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Shift-JISのエンコーディングを取得 var enc = System.Text.Encoding.GetEncoding("shift-jis"); //取得したエンコーディングを使って引数ををバイト列に変換後、再びShift-JISの文字列に変換 string cnv_str = enc.GetString(enc.GetBytes(str)); //上記の変換結果と引数を比較し、異なればShift-JISではないと判断 if (str != cnv_str) { //引数をShift-JISに変換 var l_bytes = System.Text.Encoding.Unicode.GetBytes(str); //Unicodeにバイト列に変換すると1バイト毎に0x00 が付加されるため、0x00を取り除いてShift-JISに変換 return System.Text.Encoding.GetEncoding("shift-jis").GetString(l_bytes.Where(i => i > 0).ToArray<byte>()); } |
もしSHIFT-JISでないと判断した場合、UNICODE から 一旦バイト配列に変換後、SHIFT-JIS に変換し直すという手順を踏むわけですが、バイト配列に変換すると1バイトごとに0x00 が付加されてしまいます。
このままだとSHIFT-JIS に正しく変換できないので、苦肉の策で 0x00を取り除く処理を入れています。
1 |
System.Text.Encoding.GetEncoding("shift-jis").GetString(l_bytes.Where(i => i > 0).ToArray()); |
約1000ファイルの様々なMP3ファイルで試したところ、有名どころのフリーのMP3タグと同じ結果が得られたので、多分これで問題無いと判断しました。
フィルター実行ボタンイベントハンドラ
絞り込み処理は、簡易モード(uxIsEasy)にチェックが入っている場合、入力された文字から絞り込み条件を作る処理を行い、チェックが入っていなければ、入力された文字をそのまま絞り込み条件として使っています。
DataTableには DefaulView クラスがプロパティとして公開されており、RowFilterプロパティに条件を入れることで、DataTableの中身を絞り込んでくれます。
DataGridVIew(uxMp3List)のDataSource には DataTable が入っていますが、DataSourceはオブジェクト型なので、型変換を行ってDefaultViewのプロパティにアクセスしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//絞り込み条件の初期値として uxFilter.Textをそのまま代入 string filter = uxFilter.Text; //簡易モードにチェックが入っている場合、uxFilter.Textの内容を解析 if (uxIsEasy.Checked) { //簡易モードの場合、入力された文字列を半角又は全角スペースで分割 string[] items = uxFilter.Text.Split(new char[] { ' ', ' ' }, StringSplitOptions.RemoveEmptyEntries); //列名と条件の組み合わせを順に取り出しListに追加 List<string> list = new List<string>(); for (int i = 0; i < items.Length / 2; i++) { list.Add(items[i * 2] + " like '%" + items[i * 2 + 1] + "%'"); } //取り出した列と条件の組み合わせに対して、間に and を挟んで結合 filter = string.Join(" and ", list.ToArray()); } //DataGridViewが表示中のDataTableに対して、絞り込み条件を設定 ((DataTable)uxMp3List.DataSource).DefaultView.RowFilter = filter; |
簡易モードの時は、半角又は全角で分割し、「列名1」、「値1」、「列名2」、「値2」・・・という前提で2個ずつ取り出し、曖昧検索になるような条件式を作り出しています。
置換ボタン
ここは大きく2つの処理に分かれています。
1つは、フォーマット(uxFormat)の中身を展開しながら指定された列の全ての行に値をセットしていく処理、もう1つは入力されたフォーマットをドロップダウンリストに登録する処理です。
まず、指定された列の全ての行に値をセットしている処理を見てみましょう。
ここでは、DataGridView から 行(DataGridViewRow)を順番に取り出し、{列名} の文字列を、その列の値で置き換えているだけです。
ちなみに、DataGirdViewRowではなく、DataSourceからDataTableを取り出して、DataRowに対して変更を加えても良いのですが、DataGridViewのヘッダをクリックしてソートが行われると、DataGridViewとDataTableの行の並びが一致しなくなるので、ここではあえてDataGirdViewRowを使っています。
replace はこのメソッドの中で使うローカル関数です。
1 2 3 4 5 6 7 8 9 10 11 12 |
//DataGridViewに表示されている行をループ処理で取り出す foreach (DataGridViewRow dgr in uxMp3List.Rows) { //フォーマットの中身に{列名}の記述があれば、列の値に置き換える string str = replace(uxMp3List.Columns, dgr, uxFormat.Text); //{連番}の記述があれば、連番に置き換える str = str.Replace("{連番}", string.Format("{0:" + uxNumberFormat.Text + "}", no++)); //フォーマット加工した結果を対象項目の列に代入 dgr.Cells[uxTargetColumn.Text].Value = str; } |
では、ローカル関数の中身を見ていきましょう。
第1引数はDataGridViewの列を保持しているコレクションクラス、第2引数は DataGridViewRow、第3引数は文字列ですが、ここは uxFormat.Text の値がセットされています。
そして、全ての列に対して以下の3ステップの処理を行うというのが、このローカル関数の役割になります。
- DataGridViewColumnCollection から列名を取り出して前後を “{“、 “}” で挟み、置換前の文字列を作る
- 次に、DataGridViewRow から 列名でセルを特定し、そのセルの値から置換後の文字列を作る
- 第3引数で渡された文字列(uxFormat.Textの値)に対して、Replaceメソッドを使って文字列置換を行う
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
///{列名}の記述を列の値に置き換えるローカル関数 string replace(DataGridViewColumnCollection p_col,DataGridViewRow p_dgvr,string p_str) { for(int i = 0;i < p_col.Count;i ++) { // "{列名}"の文字列を作成 string l_sour = "{" + p_col[i].Name + "}"; // "{列名}"の文字列を列の値に置換 p_str = p_str.Replace(l_sour, p_dgvr.Cells[i].Value.ToString()); } return p_str; } |
次はドロップダウンリストへの登録処理を見ていきましょう。
uxFormat.Text の値がドロップダウンリストに含まれているかを確認し、含まれていれば一旦削除してから、ドロップダウンの先頭に挿入しています。
こうすることにより、最新のフォーマット(最後に入力された uxFormat.Textの内容)が常にドロップダウンの先頭に来るようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 |
//過去に使ったフォーマットならなら、ドロップダウンから一旦削除 string format = uxFormat.Text; if (uxFormat.Items.Contains(format)) { //ドロップダウンから削除 uxFormat.Items.Remove(format); //RemoveするとText部がクリアされるので復旧 uxFormat.Text = format; } //ドロップダウンの先頭に登録 uxFormat.Items.Insert(0, format); |
文字列置換の実行イベントハンドラ
正規表現チェックボックス(uxUseRegular.Checked)にチェックが入っていると正規表現を使った文字列置換を行い、チェックが無ければ単純な文字列置換を行っています。
格安MP3プレーヤーの中には、曲の再生順がファイル名のソート順というケースがあります。
この場合、ファイル名の先頭に連番を付ける事が多いのですが、付けるのは簡単ですが、抹消するのは少々厄介です。
というのは、連番と曲名の間がピリオドだったりアンダーバーだったり、半角スペースだったりする可能性がありますし、桁も1桁~3桁混在していたり、先頭に0が付加されていたりと様々なパターンが考えられます。
これらのパターンに対応するには、正規表現を使うのが一番手っ取り早いのです。
ただ、正規表現はとっつき難く、複雑な条件を書くのは難易度が高いのが難点です。
でも、簡単な条件であれば少し勉強すれば使える様になりますし、使えると何かと便利なので、ここで少しだけ使ってみることにしました。
ちなみに、ここでも DataTable ではなく、DataGridViewに対して文字列置換を行っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//正規表現クラスのインスタンスを生成 Regex reg = new Regex(uxBeforeString.Text); //表示中の行に対するループ処理 foreach (DataGridViewRow dgr in uxMp3List.Rows) { //置換対象となる文字列を取得 string target = dgr.Cells[uxTargetColumn.Text].Value.ToString(); //正規表現チェックボックスのチェック状態を確認 if (uxUseRegular.Checked) { //正規表現にチェックが入っていると、正規表現として文字列置換を実施 dgr.Cells[uxTargetColumn.Text].Value = new Regex(uxBeforeString.Text).Replace(target, uxAfterString.Text); } else { //正規表現にチェックが入っていない場合、単純な文字列置換を実行 dgr.Cells[uxTargetColumn.Text].Value = target.Replace(uxBeforeString.Text, uxAfterString.Text); } } |
フォームロード処理のところで、以下の様に初期値を設定しています。
最初の ‘^’ は、先頭を表しています。
次に ピリオド ‘.’ がありますが、これは任意の1文字を表し、アスタリスク ’*’ は直前の文字が0個以上繰り返し、はてな ‘?’ は直前の文字が0か1回あるという意味になります。
”[ -. ]” は、ハイフンかピリオドか半角スペースのいずれかという意味になります。
つまり、「先頭から数えて、任意の文字が1個以上連続で続いていて、ハイフンかピリオドか半角スペースのいずれかが1つ登場するまで」という意味になります。
1 |
uxBeforeString.Text = "^.*?[-. ]"; |
もし ‘?’ を抜いて “^.*[-. ]” と書くと、「任意の文字が1個以上連続で続いていてハイフンかピリオドか半角スペースが終わるまで」という意味になります。
ちょっとややこしいですが、”.*?” というパターンは良く使いますので、頭の片隅にでも入れておいていただければ、次に役立つと思います。
まとめ
以上でソースコードの解説は終了です。
かなり長くなりましたが、メソッドやイベントハンドラで処理が分割されているので、1つの処理に着目すれば理解しやすいかと思います。
何度も言いますが、全てを理解する必要はありません。
「こういう部分は他にも使えそう」とか、「他のプログラムに流用する場合は、ここからここまで切り出せば良いんだ」とか、そういう観点で理解してもらえれば良いかと思います。
そして、実際に中身をカスタマイズしたくなったら、その部分を集中的に理解するという方法が早道だと思います。
では、次の記事ではTagLib の使い方について解説したいと思います。