ゲームエフェクトデザイナーのブログ | A Real-Time VFX Artist's Blog

About Making Materials on UE, Making Tools with C#, etc

Photoshopのスクリプトを書いてみる

Photoshopにはアクションという便利な機能がありますが、かゆいところに手が届きません。
そういう場合に手作業を挟んでましたが、JavaScriptスクリプトを書けばアクションではやりにくいこと(変数を使うようなこととか)も可能です。

こちら、思いのほかお手軽だったのでメモっておきます。

ちなみに 赤めがねさんより色々と情報をいただきましたので、追記しました。
ありがとうございます!

続きを読む

ノートPC「X205TA」に 仮想HDDを利用して Visual Studio Community を入れてみた

メモするためだけに買った格安ノートPC「X205TA」ですが、身軽なサイズ・1Kgの軽さ・10時間ほど持つ駆動時間・色やデザインと全てにおいて気に入っており、購入直後から今もずっと愛用し続けています。

ただ、購入時から分かってはいたことですがストレージが 32GB しかなく、Officeを入れたため残り 9GB ほどしか残っておらず、これ以上容量を圧迫できない状態です。

しかしC#の勉強を始めてから「Visual Studio Community 2017」を入れたくなりました。ただしこいつはインストール時のコンポーネントでモバイル開発などをONにすると合計30GBほどのサイズに膨れ上がってしまいます。

そこで「ストレージを換装するか」と思って調べてみたら、どうも「eMMC」とやらは基盤に直付けで容量によって形状も違うらしく、素人に交換できるようなシロモノでは無い模様。。

そこで次に「microSDにインストールできないか」と思って調べたところ、いくつか方法があることが分かりました。

こちらの記事ではそのままのインストールとシンボリックリンクについて解説されています。

ただ、もう少し調べてみると「仮想ハードディスク(Virtual Hard Disk / VHD)」を作成してインストールする方が良さそうと思い、下記の2つの記事を参考にこちらを試してみることにしました。


ちなみに購入したmicroSD価格.comで人気1位(2017年8月現在)のこちら。

速度タイプが「CLASS10」というもので今ではかなり遅いみたいですが、安くて大容量なのでこちらに決めました。

仮想HDD作成


上記サイトの手順を参考に進めていきますが、いくつか注意点があります。

仮想HDDの容量設定は少し余裕を持たせる必要があるっぽい
 ⇒ 先ほどの記事を参考に 104 GB に設定してみました

仮想HDD作成に1時間以上かかる
 ⇒ スリープが入らないように設定しておく

仮想HDDのボリュームラベルは分かりやすい名前が良さそう
 ⇒ VHDとかmicroSDとか

再起動で仮想HDDのマウントが外れる
 ⇒ 自動マウントの方法がいくつかあるっぽい(後述)


最初に仮想HDDを作成した際には、時間がかかるということで放置してたのですが、スリープしていたためか、後からロック画面を解除して確認したら作成されたVHDファイルが壊れていました(microSDのボリュームに割り当てられておらず、VHDに接続しようとすると”ファイルが壊れています”と表示される)。

そこでそのままVHDファイルを削除して作成し直しで問題なく進めました。

Visual Studio Community 2017 のインストール


さて、インストールですがインストーラーは1MBほどなので気にせずDLします。

インストーラーを実行したらコンポーネントを選択する画面になりますが、インストールサイズは以下のような感じでした。

・何も選択していない素の状態: 605MB

Windows
・.NET デスクトップ開発: 2.56GB
C++ によるデスクトップ開発: 4.44GB

<Web & クラウド
ASP.NET と Web 開発: 4.64GB
・Azure の開発: 6.18GB
Python 開発: 1.44GB
・Node.js 開発: 2.25GB
・データの保存と処理: 4.81GB
・データサイエンスと分析のアプリケーション: 1.61GB
・Offisce/SharePoint 開発: 3.62GB

<モバイル&ゲーム>
・.NET によるモバイル開発: 27.26GB
・Unity によるゲーム開発: 3.49GB
JavaScript によるモバイル開発: 1.44GB
C++ によるモバイル開発: 10.75GB
C++ によるゲーム開発: 4.08GB

<他のツールセット>
Visual Studio 拡張機能の開発: 2.26GB
C++ による Linux 開発: 1.1GB
・.NET Core クロスプラットフォーム開発: 4.07GB

・すべて選択: 46.44GB


.NET によるモバイル開発” が 27.26GB と巨大な容量ですね。。
こちらは着手してからのインストールで良いと思いパス。

3DCGアーティストなら Python は入れると良いかも知れないですね。
Unity はどのみちこのノートPCでは動かないだろうからパス。

結局 ”.NET デスクトップ開発” のみONにして 3.7GB ほどのインストールサイズになりました。

で、仮想HDDをマウントしているEドライブを指定してインストールしてみた訳ですが‥

蓋を開けば、EではなくCドライブの残容量が見事に 3GB 以上減っています‥Oh My God!!
C://Program Filsに色々と追加されていました。
Visual Studio関連ファイルを別ドライブへはインストールできないということでしょうか‥

Projectファイルもマイドキュメント内の中ですね。。
マイドキュメントはマイドキュメントのプロパティ>場所‥の設定でEドライブに移し替えました。

肝心の仮想HDDのEドライブにインストールされたファイルのサイズは 1.25GB でした。

‥という訳で完全に失敗です。みなさまもご注意ください。

仮想HDDの自動マウント


マウントするバッチファイルを作成してスタートアップに登録するといった方法もあるみたいですが、タスクスケジューラに登録する形で再起動を試してみて正常に動作しているのを確認。

下記の記事を参考にさせて頂きました。
(こちらのページ、自分の環境では画像が表示されませんがクリックすると表示されました)

X205TA、VHDを使って容量を更に確保する。VHDオートマウントも設定! | Appnote

使用感


数GBのファイルを書き込んだりするとかなり遅いですね。。
それもそのばす、転送速度はmicroSDのCLASS10で10MB/s・USB2.0で60MB/s・USB3.0で640MB/sとのことです。

とりあえず様子を見てみたいと思います。

肝心の Visual Studio の使用感についてはまだです。
後日使ってみたら追記予定。

C#でサブフォルダの総サイズのリストアップ(DataGridView編)

前回は同じ内容のツールでListBoxにリストアップする形でしたが、今回はDataGridViewにリストアップする方法を備忘録としてメモしておきます。

外観はこんな感じ。

f:id:moko_03_25:20170812222614j:plain

DataGridViewの扱いに慣れれば、後はCSV読み込みと書き出し、余力があればExelのデータを扱えるライブラリを導入して直接xlsxファイルで書き出すことができるようになれば、業務でのお役立ち度も一気に増して幸せになれそうな気がします。

さて、あるフォルダパス直下のサブフォルダ1つ1つのファイルサイズをリストアップするまでの記述は前回の記事とほとんど変わらないので、ここでは前回と違う部分だけピックアップしたいと思います。

サブフォルダ1つ1つの総ファイルサイズを配列で取得するメソッド


DataGridViewのやり取りの部分がまだよく分かっていないので、恐らくもっと楽な方法があると思うのですが、自分なりに調べて試してみたのは‥1つのサブフォルダに対して「フォルダ名」と「ファイルサイズ」の2つの要素を二次元配列に格納して、DataGridViewの一列目と二列目にそれぞれの要素を追加していく方法です。

//サブフォルダ1つ1つの総ファイルサイズを配列で取得
public string[,] GetSubFolderSize(string subFolders)
{
  //サブフォルダの数分の配列"size"と"sizeSt"を定義
  int el = subFolders.Length; //サブフォルダの要素数(element)
  long size = new long[el]; //整数の配列

  string[,] sizeSt = new string[el, 2]; //文字列の2次元配列
  string fn; //フォルダネームを一時的に保存する変数

  //配列の要素の数だけ処理
  for (int i = 0; i < el; i++)
  {
    //入力したパスのフォルダ名を取得
    fn = Path.GetFileName(subFolders[i]);

    //入力したパスをDIrectoryInfoクラスのオブジェクトに代入
    DirectoryInfo chkPath = new DirectoryInfo(subFolders[i]);
    //総ファイルサイズをMiB単位で配列"size"に代入
    size[i] = GetDirectorySize(chkPath) / 1024 / 1024;
    //文字列に変換して配列"sizeSt"に代入
    sizeSt[i, 0] = fn;
    sizeSt[i, 1] = size[i].ToString("D");
  }

  return sizeSt;
}


戻り値
と変数”sizeSt”をString型の二次元配列にして、フォルダ名ファイルサイズを別々に格納しています。

本当はフォルダ名はString型サイズはLong型で返したいところですが、とりあえず今回は勉強を兼ねて二次元配列でやってみた感じになります。

Enterでパスを取得してDataGridViewにリストアップ


Enterキーを入力した際の挙動の部分だけ抜粋しています。

列のヘッダーの表示名を設定して、ファイルサイズの列は右寄せに設定。
最後に各列に二次元配列の中身を追加していっています。

//サブフォルダのパスを全て取得
string[] subFolders = GetSubFolderPath(path);
//サブフォルダの要素数
int el = subFolders.Length;

//サブフォルダの数分の2次元配列"sizeSt"を定義
string[,] sizeSt = new string[el, 2];

//全てのサブフォルダごとの合計サイズを取得
sizeSt = GetSubFolderSize(subFolders);

//DataGridViewの項目名を指定
dataGridViewResult.Columns.Add("", "フォルダ名");
dataGridViewResult.Columns.Add("", "総サイズ(MB)");
//行ヘッダを非表示にする ※Formデザイナーのプロパティ内でも設定可能
dataGridViewResult.RowHeadersVisible = false;
//2列目を右寄せ表示にする
dataGridViewResult.Columns[1].DefaultCellStyle.Alignment =
  DataGridViewContentAlignment.MiddleRight;

//二次元配列を1列目と2列目に追加していく
for (int i = 0; i < el; i++)
{
  //行を追加
  dataGridViewResult.Rows.Add();
  //行の1列目にフォルダ名を入力
  dataGridViewResult.Rows[i].Cells[0].Value = sizeSt[i, 0];
  //行の2列目にサイズをLongに型変換して入力(ソート問題回避のため)
  dataGridViewResult.Rows[i].Cells[1].Value = long.Parse(sizeSt[i, 1]);
}


二次元配列は初めて試しましたが、一応うまくいっているようでホっ。
ファイルサイズをString型からLong型に変換している理由は後述しています。

下記の様々な記事を参考にさせて頂きました。

【C#】2次元配列の宣言・初期化・代入

【C# DataGridView】一次元配列を設定する | ITLAB51.COM

DataGridの一番下の新しい行(*行)が表示されないようにする: .NET Tips: C#, VB.NET

DataGridViewでの行の追加と削除 - 理想のユーザ・インターフェイスを求めて

文字列を数値に変換する、数値を文字列に変換する: .NET Tips: C#, VB.NET

DataGridViewの内容のクリップボードへのコピーとクリア


最後におまけです。
DataGridViewの内容を全てクリップボードへコピーする方法と、全て消去する方法です。

//「クリップボードへコピーする」ボタンの挙動
private void buttonCopy_Click(object sender, EventArgs e)
{
  //全ての列の全ての行を選択
  for (int i = 0; i < dataGridViewResult.ColumnCount; i++)
  {
    for (int j = 0; j < dataGridViewResult.RowCount; j++)
    {
      dataGridViewResult[i, j].Selected = true;
    }
  }
  //選択内容をクリップボードへコピーする
  //ヘッダを含めて同じ行のセル間はTab区切りにする
  dataGridViewResult.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
  Clipboard.SetDataObject(dataGridViewResult.GetClipboardContent());
}

//「クリア」ボタンでDataGridViewの内容を消去
private void buttonClear_Click(object sender, EventArgs e)
{
  //全ての行の内容をクリア
  dataGridViewResult.Rows.Clear();
  //全ての列の内容をクリア
  dataGridViewResult.Columns.Clear();
} 


こちらも下記の様々な記事からにお世話になりました。

【C# DataGridView】内容をクリップボードにコピーする | ITLAB51.COM

DataGridViewで選択されたセルをクリップボードにコピーできるようにする: .NET Tips: C#, VB.NET

【C#】DataGridViewをプログラムで初期化、列編集する方法 – Output48

他には、Formデザイナーのプロパティの方に以下のような設定があります。

・AllowUser~~ 行や列のサイズ変更や追加/削除等を許可するか
・ClipboardCopyMode クリップボードにコピーする際の設定
・ColumnHeadersHeightSizeMode 列ヘッダーの高さの調整
・ReadOnly ユーザーのセルの編集を許可するか(Trueで編集不可)
・AutoSizeColumnsMode 可視列の自動サイズ調整の設定
・RowHeadersVisible 行ヘッダーを表示するかどうか


それから、DataGridView上で数値を文字列で扱っていると、ヘッダーをクリックして昇順/降順にソートすると正しく数字の大きさ順でソートしてくれません。
なのでファイルサイズはLong型に変換してからDataGridViewに入力した訳です。

下記の記事のような方法もあるようですが。。

DataGridViewの行の並び替えの方法を変更する: .NET Tips: C#, VB.NET

ADO.NET入門記-031 (DataTableにて文字列値の列を数値ソートする) - いろいろ備忘録日記

プロジェクトデータと実行ファイル


Googleドライブにアップしました。ライセンスはMITです。

MyTool_FolderSizeCheck_DataGridView.zip - Google ドライブ

binフォルダの中にexeが入っています。

C#でサブフォルダの総サイズのリストアップ(ListBox編)

お次は、あるフォルダ直下にあるサブフォルダ個々の総ファイルサイズをリストアップするツールについての備忘録です。リストアップを表示するパーツをここでは「ListBox」で試してみました。

下図のような外観で、フォルダパスを入力後にEnterキーでリストアップを実行します。
フォルダのドラッグ&ドロップにも対応する場合は前回の記事が参考になるかと思います。

f:id:moko_03_25:20170812032439j:plain

 

実装したもの


今回実装したのは以下になります。
かなりまどろっこしい感じになってる気がします。
もっと簡潔で良い書き方が色々ありそうですが‥

・あるフォルダ以下にある全てのサブフォルダのパスを配列で取得するメソッド
・あるフォルダ以下の総ファイルサイズを取得するメソッド
・サブフォルダ1つ1つの総ファイルサイズを配列で取得するメソッド

・パス入力欄でEnterを押すとサブフォルダ個々の総サイズをリストアップする

・ListBoxの内容のクリップボードへのコピーとクリア


1つずつ見ていきます。

あるフォルダ以下にある全てのサブフォルダのパスを配列で取得するメソッド


こちらは前回の記事で出てきたもので、やはりDOBON.NETさんをそのままコピーさせて頂いたものです。
青文字はクラス名・メソッド名・変数名です)

//あるフォルダ以下にある全てのサブフォルダのパスを配列で取得する
public string GetSubFolderPath(string path)
{
  string subFolders = Directory.GetDirectories(

    path, "*",
    SearchOption.TopDirectoryOnly); //サブフォルダTOP階層のみ取得

  return subFolders;
}


実質2行でシンプルです。

あるフォルダ以下の総ファイルサイズを取得するメソッド


こちらに関しては完全に下記の記事をまるまるコピーさせていただきました。

フォルダのサイズを取得する: .NET Tips: C#, VB.NET

こんな感じです。

//あるフォルダ以下の総ファイルサイズを取得するメソッド
public static long GetDirectorySize(DirectoryInfo dirInfo)
{
  long size = 0;

  //1つのサブフォルダ内の全ファイルの合計サイズを計算する
  foreach (FileInfo fi in dirInfo.GetFiles())
    size += fi.Length;

  //全サブフォルダのサイズを合計していく
  foreach (DirectoryInfo di in dirInfo.GetDirectories())
    size += GetDirectorySize(di);

  //結果を返す
  return size;
}


サブフォルダの合計をしていく場所で自身のメソッド名が登場していますが、入れ子で実行しているということですかね? まず指定パス直下のファイルを合計してからサブフォルダ1つずつの中を掘り下げて合計していっている感じでしょうか。

そんな記述ができるんですね。。

ただ、変数sizeが繰り返し 0 で初期化されたり return されたりしていて、ステップ実行してもどういう流れになってるのか把握し辛いですね‥

サブフォルダ1つ1つの総ファイルサイズを配列で取得するメソッド


ここが今回の要で、1つめのメソッドで得たサブフォルダのパス情報を引数として受け取って、for文でパス1つ1つに対して2つめのメソッドを実行して総ファイルサイズを順に得ていく感じです。

//サブフォルダ1つ1つの総ファイルサイズを配列で取得
public string GetSubFolderSize(string subFolders)
{
  //サブフォルダの数分の配列"size"と"sizeSt"を定義
  int el = subFolders.Length; //サブフォルダの要素数(element)
  long size = new long[el]; //整数の配列
  string sizeSt = new string[el]; //文字列の配列

  string fn; //フォルダネームを一時的に保存する変数

  //配列の要素の数だけ処理
  for (int i = 0; i < el; i++)
  {
    //入力したパスのフォルダ名を取得
    fn = Path.GetFileName(subFolders[i]);

    //入力したパスをDIrectoryInfoクラスのオブジェクトに代入
    DirectoryInfo chkPath = new DirectoryInfo(subFolders[i]);
    //総ファイルサイズをMiB単位で配列"size"に代入
    size[i] = GetDirectorySize(chkPath) / 1024 / 1024;
    //文字列に変換して配列"sizeSt"に代入
    sizeSt[i] = fn + "\t" + size[i].ToString("D") + " MB";
  }

  return sizeSt;
}


得られるファイルサイズはバイト単位になるので、ここでは1024で割ってKiBに、さらに1024で割ってMiB単位に変えた上で、ToString("D")で文字列(整数)に変換しています。

パス入力欄でEnterを押すとサブフォルダ個々の総サイズをリストアップする


実際にパスを入力してEnterで決定した際の挙動です。

一応ちゃんとパス先にフォルダがあるかチェックしてからGetSubFolderSizeメソッドを実行しています。

//パス入力欄でのキー入力イベント
private void textBoxPath_KeyDown(object sender, KeyEventArgs e)
{
  //Ctrl + A で全選択可能にする
  if (e.Control && e.KeyCode == Keys.A)
  {
    textBoxPath.SelectAll();
  }

  //Enterでパスを取得
  if (e.KeyCode == Keys.Enter)
  {
    string path = textBoxPath.Text;

    //textBoxPathのパス先が存在しているかどうか判定
    if (Directory.Exists(path))
    {
      //サブフォルダのパスを全て取得
      string subFolders = GetSubFolderPath(path);

      //サブフォルダの数分の配列"sizeSt"を定義
      string sizeSt = new string[subFolders.Length];

      //全てのサブフォルダごとの合計サイズを取得
      sizeSt = GetSubFolderSize(subFolders);

      //リストに代入していく
      List<string> lt = new List<string>(sizeSt);

      foreach (var item in lt)
      {
        listBoxResult.Items.Add(item);
      }
    }
    else
    {
      MessageBox.Show("\"" + path + "\"" +
        " は存在しないフォルダです");
    }
  }
}


総サイズを配列で受け取った後はリストに代入して、リストをListBoxに追加しています。
ここは色んな方法がありそうですがListクラスも使ってみたいと思って使ってみた感じです。とは言えここもコピペです。下記の記事を参考にさせて頂きました。

C# 配列を List に変換する | ITLAB51.COM

ListBoxは初めて使ってみましたが、複数行を選択させたいなら「SelectionMode」プロパティをMultiExtendedにすると良いです。MultiSimpleというのもありますがこちらはクリックで選択状態になったあと別の行を選択しても選択状態が解除されないのが気持ち悪いので個人的には「これは使わないなあ」と思いました。

f:id:moko_03_25:20170812063347j:plain

 

ListBoxの内容のクリップボードへのコピーとクリア


最後におまけです。
ListBoxの内容を全てクリップボードへコピーする方法と、全て消去する方法です。

//「クリップボードへコピーする」ボタンの挙動
private void buttonCopy_Click(object sender, EventArgs e)
{
  //LINQでListBoxの中身を1行ずつ配列に代入
  string[] list = this.listBoxResult.Items.Cast<string>().ToArray();
  //文字列を改行を挟みつつ連結
  string st = string.Join("\r\n", list);
  //クリップボードへコピー
  Clipboard.SetText(st);
}

//「クリア」ボタンでListBoxの内容を消去
private void buttonClear_Click(object sender, EventArgs e)
{
  listBoxResult.Items.Clear(); //内容をクリア
}


ListBoxの内容全てを1行ずつ配列の形で取得してから全ての行を連結させています。
もっと直接クリップボードにコピーできないのかなと思いましたがちょこっとググってみただけでは分かりませんでした。

ちなみにListBoxの内容を配列で取得する方法は、こちらのYahoo!知恵袋のベストアンサーの記述方法をそのまま参考にさせて頂きました。

C#のリストボックスの項目取得で困っています。選択されていない項目も含めて... - Yahoo!知恵袋

これがLINQというやつですか。。覚えるととても便利そう!ですね。

それから、文字列の配列を改行を挟みつつ連結する方法はこちらの記事を参考にさせて頂きました。

文字列を連結する: .NET Tips: C#, VB.NET

内容をクリアはメソッドが用意されていますね。

プロジェクトデータと実行ファイル


Googleドライブにアップしました。ライセンスはMITです。

MyTool_FolderSizeCheck_ListBox.zip - Google ドライブ

binフォルダの中にexeが入っています。

C#でサブフォルダをリストアップ

前回に続いて、任意のフォルダの直下にあるサブフォルダ名をリストアップするツールについて、備忘録としてメモしておきたいと思います。

恐らくプログラム初心者ならではの全くスマートではない見苦しいコードになってるのではと思うので、そのあたりご注意ください。。

ツールの外観は下図のような感じで、上段のテキストボックスにフォルダのパスを入力してEnterを押すか、またはフォルダやファイルをツール上へドラッグ&ドロップすることで下段の「サブフォルダリスト」内にサブフォルダ名をずらずらっとリストアップします。

f:id:moko_03_25:20170807235416j:plain

 

実装したもの


ここで実装した内容は以下になります。

・あるパスがファイルかフォルダかを判定してフォルダパスを取得するメソッド
  - ファイルの場合は親フォルダのパスを取得
  - フォルダの場合は自身のフォルダパスを取得
・フォルダパスから直下のサブフォルダ名を取得してリストアップするメソッド

・パスを入力してEnterキー入力でリストアップを実行する
・ファイルかフォルダをドラッグ&ドロップするとリストアップを実行する


1つずつ見ていきます。

usingと変数の宣言

 

using System.IO;

~~~~省略~~~~

//変数の宣言
string filePath; //ファイルのフルパス
string folderPath; //親フォルダのフルパス


既存のファイル情報を得るクラスを使用するので「System.IO」を using に追記します。

また、パスのややこしい部分ですが、ファイルパスとフォルダパスの扱いを明示的に分けたかったので変数を2つ用意しました。

ファイルかフォルダか判定してフォルダパスを取得するメソッド


次に、あるパスがファイルかフォルダかを判定して、どちらの場合でもフォルダのパスを取得するよう記述したメソッドが下記です。
青文字はクラス名・メソッド名・変数名です)

//ファイルかフォルダか判定してフォルダパスを取得するメソッド
private void determine()
{
  if (File.Exists(filePath)) //ファイルの場合
  {
    folderPath = Path.GetDirectoryName(filePath);
  }
  else if (Directory.Exists(filePath)) //フォルダの場合
  {
    folderPath = filePath;
  }
}


File.Exists」「Directory.Exists」を使うことで、パス先がファイルなのかフォルダなのか簡単に判定できるようです。便利!

それから、パスの取得周りはルールがちょっとややこしいです。
こちらの記事を参考にしました。

パスからファイル名、拡張子、ディレクトリ名、ルートディレクトリ名等の情報を取得する: .NET Tips: C#, VB.NET

こちらの一覧表を見ると分かりますが、現在のフォルダ名(上記サイトの例だと"sub"がそれにあたる)を取得するには、フォルダパスに対して「Path.GetFileName」で行うことになるかと思います。

しかしドラッグ&ドロップで入力したものがファイルだった場合に、ファイルパスに対して「Path.GetDirectoryName」でフォルダパスを得ています。

フォルダパスからサブフォルダ名を取得してリストアップするメソッド


今回の要となる、サブフォルダをリストアップするメソッドがこちら。

//フォルダパスからサブフォルダをリストアップするメソッド
private void listUp()
{
  //テキストの内容をTextBoxに表示
  textBoxPath.Text = folderPath;

  //指定フォルダ以下のサブフォルダをすべて取得する
  string subFolders = Directory.GetDirectories(
    folderPath, "*",
    SearchOption.TopDirectoryOnly); //サブフォルダTOP階層のみ取得

  //一旦内容をクリア
  textBoxList.ResetText();

  //取得したサブフォルダ群を1つずつテキストボックスに追加していく
  for (int i = 0; i < subFolders.Length; i++)
  {
    //サブフォルダのパスからフォルダ名を取得
    string stFileName = Path.GetFileName(subFolders[i]);
    //フォルダ名を改行しながら追加
    textBoxList.AppendText(stFileName + "\r\n");
  }
}


先ほどの判定メソッドで得たフォルダパスを利用して、そのフォルダ直下のサブフォルダ名を「Directory.GetDirectories」で取得して配列に代入しています。

こちらの記事を参考にさせて頂きました。

あるフォルダ以下にあるサブフォルダをすべて取得する: .NET Tips: C#, VB.NET

その後にfor分で1つずつテキストボックスに改行しながら追記していきます。

パスを入力してEnterキー入力でリストアップを実行する


まず探すきっかけとなるパスをどう取得するかですが、今回は手動での入力とドラッグ&ドロップに両対応することにしました。

専用のテキストボックスにパスを手動で入力してEnterキーを押した時の挙動がこちら。

//TextBoxPathのキー入力による挙動
private void textBoxPath_KeyDown(object sender, KeyEventArgs e)
{
  //Ctrl + A で全選択可能にする
  if (e.Control && e.KeyCode == Keys.A)
    textBoxPath.SelectAll();

  //Enterでパスを取得
  if (e.KeyCode == Keys.Enter)
  {
    //フォルダが存在しているかどうか判定
    if (Directory.Exists(textBoxPath.Text))
    {
      //TextBoxの内容からパスを取得
      filePath = textBoxPath.Text;
      //ファイルかフォルダかを判定してフォルダパスを取得
      determine();
      //フォルダパスからサブフォルダをリストアップする
      listUp();
    }
    else
    {
      MessageBox.Show("存在しないフォルダです");
    }
  }
}


テキストボックスは1行だけのボックスであっても全選択したいものだと思うので、前回の記事同様にKeyDownイベントに Ctrl + A による全選択を記述しています。

また、Enterキーによる実行もKeyDownイベントなので、こちらも平行して記述します。
まずテキストボックスに入力したパス先にフォルダがあるか判定してから、最初に解説したメソッド2つを実行します。ここはとてもシンプルです。

フォルダと判定できなかった場合はメッセージボックスを表示します。

f:id:moko_03_25:20170809021356j:plain

ファイルかフォルダをドラッグ&ドロップするとリストアップを実行


最後にドラッグ&ドロップされたフォルダ(またはファイル)からパスを取得した時の挙動がこちらになります。

//Formへのドラッグ&ドロップの挙動1
private void Form1_DragEnter(object sender, DragEventArgs e)
{
  //ドロップされたものがファイルかどうかチェックする定番の書き方
  if (e.Data.GetDataPresent(DataFormats.FileDrop))
  {
    //ファイルのときは受け付ける
    e.Effect = DragDropEffects.All;
  }
  else
  {
    //ファイル以外は受け付けない
    e.Effect = DragDropEffects.None;
  }
}
//Formへのドラッグ&ドロップの挙動2
private void Form1_DragDrop(object sender, DragEventArgs e)
{
  //ファイルが複数ドロップされた時のためにフルパスを配列に代入
  string multiPass = (string[])e.Data.GetData(DataFormats.FileDrop, false);
  //0番目のファイルだけフルパスを取得
  filePath = multiPass[0];
  //ファイルかフォルダかを判定してフォルダパスを取得
  determine();
  //フォルダパスからサブフォルダをリストアップする
  listUp();
}


ドラッグ&ドロップの対応はこちらの記事を参考にさせて頂きました。

Drag&Dropされたファイルのファイル名を取得する: .NET Tips: C#, VB.NET

他にもいくつかのサイトを参考にしましたが、この形がお約束のようで、ドラッグ時のウインドウ上部への侵入時の挙動と、ドロップ時のふたつの挙動がセットになるようです。

複数のファイルがドロップされた時のために、配列に代入するのもお約束のようです。
そうして得たパスに対して、最初に解説したメソッド2つを実行します。

プロジェクトデータと実行ファイル


Googleドライブにアップしました。ライセンスはMITです。

MyTool_FolderList.zip - Google ドライブ

binフォルダの中にexeが入っています。

C#で正規表現による置換を行う

C# で最初に作ったツールは「特定のリストを作って専用のフォーマットに整形した上でテキストデータで出力するもの」という感じでしたが、そこで躓いた部分は正規表現による置換でした。

その置換についてメモしておきたいと思って用意したのが下図のツールです。

f:id:moko_03_25:20170807211406j:plain

ここでいきなりUE4の話になりますが、UE4のアセット管理ツールであるコンテンツブラウザ上でアセットを右クリック「リファレンスをコピー」を選択すると、クリップボードにアセットの情報がコピーされます。

f:id:moko_03_25:20170807211352j:plain

それは以下のような書式です。

アセットの種類 + ' + アセットのフルパス + . + アセット名 + '


で、例えばあるアセットの場所をメールでお知らせするような時には、フルパスの前後の文字列が邪魔だったりします。それを正規表現の置換で除去するのが今回のツールです。

ちなみに余談ですが、コンテンツブラウザ上でアセットを選択して Ctrl + C でリファレンスをコピーできることを最近同僚に教えてもらいました。
知りませんでした。。これは便利!

f:id:moko_03_25:20170807211403j:plain

そしてアセットのリファレンス情報をツールの上段のテキストボックス内にペーストします。
すると下図のように貼り付けられました。
先に書いた通り、メールに記載したりするには余計な情報がごちゃっと付いています。

f:id:moko_03_25:20170807211400j:plain

次に「パスから余計な情報を除去」ボタンを押すと、下段のテキストボックスに置換された結果が表示されます。

あとはオマケで「クリップボードへコピー」ボタンと「クリア」ボタンも追加しました。

f:id:moko_03_25:20170807211349j:plain

実装したもの


さて、ここで実装した内容は以下になります。

・テキストボックス内で Ctrl + A を入力すると文字列を全選択する
・「クリア」ボタンでテキストボックス内の文字列を全消去する
・「クリップボードへコピー」ボタンで文字列をクリップボードにコピーする
・「パスから余計な情報を除去」ボタンでリファレンス情報を単なるフルパス情報に整形する

1つずつ見ていきます。

テキストボックス内での文字列の全選択


ツールを作ってみて初めて気付いたのですが、普段はテキスト入力できるパネル内で当たり前のように可能な全選択ショートカットですが、これは実装しないと可能になりません。

実装は簡単で、テキストボックスの KeyDown イベントに下記の2行を追記するだけです。
青文字部分はテキストボックスのNameプロパティで設定した名前です)

//Ctrl + A で全選択可能にする
private void textBoxInput_KeyDown(object sender, KeyEventArgs e)
{
    if (e.Control && e.KeyCode == Keys.A)
    textBoxInput.SelectAll();
}

 

文字列のクリアとクリップボードへのコピー


これらも実装は簡単で、ボタンのクリックイベントそれぞれに下記1行を追加するだけです。
青文字部分はテキストボックスのNameプロパティで設定した名前です)

//「クリア」ボタンでtextBoxの内容を消去
private void buttonInputClear_Click(object sender, EventArgs e)
{
    textBoxInput.ResetText();
}
//「クリップボードへコピーする」ボタンの挙動
private void buttonResultCopy_Click(object sender, EventArgs e)
{
    Clipboard.SetText(textBoxResult.Text);
}
//「クリア」ボタンでtextBoxの内容を消去
private void buttonResultClear_Click(object sender, EventArgs e)
{
    textBoxResult.ResetText();
}

 

正規表現による置換


問題はここからです。

正規表現の置換には「RegularExpressions」を利用しました。
LINQを使うとか色々方法があるっぽいのですが、よく検索で出てくるこちらを試しました。

まずは Form1.cs の先頭に下記の1行を追加します。

using System.Text.RegularExpressions;


次にボタンクリックイベントでの置換の内容です。

//「パスから余計な情報を除去」ボタンの挙動
private void buttonReplace_Click(object sender, EventArgs e)
{
    //一時的に文字列を扱うための変数
    string st = textBoxInput.Text;

    //先頭の余分な情報を置換でカット
    st = Regex.Replace(
        st,
        "^[^/]+'", //行頭から連続するスラッシュ以外の文字列
        "", //空に置換(文末以外の空白行も削除される)
        RegexOptions.Multiline); //改行記号も1つの文末として処理する
    //末尾の余分な情報を置換でカット
    st = Regex.Replace(
        st,
        "[.][^/]+'", //行末まで連続した.で始まるスラッシュ以外の文字列
        "", //空に置換
        RegexOptions.Multiline); //改行記号も1つの文末として処理する
    //文末の空行を削除
    st = Regex.Replace(
        st,
        "[\r\n]*$", //文末で0回以上繰り返す改行
        "\r\n"); //改行1つだけに置換

    textBoxResult.AppendText(st);
}


文字列を改行部分で分割して配列に代入し、for文で1行ずつ置換していく‥といったような面倒なことをしなくても、改行を含んだ文字列に対して「Multiline(複数行)」オプションを指定してそのまま置換するだけでいけました。

3度の 置換によって

 ・行頭の情報の除去(文末以外の空行も同時に除去)
 ・行末の情報の除去
 ・文末に空行が複数ある場合に1行だけにする

‥を行っています。

正規表現のルールはこちらの記事を参考にさせて頂きました。

正規表現の基本: .NET Tips: C#, VB.NET


正規表現の厄介なところは、文字列の指定が記号だらけになって内容も結果も分かり辛く、なぜ置換がうまくいかないかも判断し辛いところですね。。

ただ、上記の置換のコード先頭でブレークポイントを設定して、変数 st をウォッチリストに追加して、1つの処理を順に進めて(ステップ実行して)いくと、変数 st の内容がどう変わっていくかをチェックすることができます。
(このあたりは1つ前に投稿した記事で紹介した書籍でも丁寧に解説されています)

例えばピリオド(\n以外の任意の文字列)を使って .+ で置換するとキャリッジリターンまで置換対象になって改行文字の「\r\n」が「\n」だけになってしまい、テキスト出力すると謎の連続改行が入ってしまうといったことが起こり、原因究明にしばらく手間取りましたが、ステップ実行して変数の中身を調べると原因がすぐに分かりました。

行末の情報を除去するための対象文字列に、行末を表す $ を使用していないのはそのためで、$ を使うと「.+」によって「\r」が削られてしまいました。
ここでは Path.GetFileName のようなメソッドで親フォルダパスを取得する方法は使わずあくまで置換でやりたかったので対処療法な感じになりましたが、どこかのタイミングで本職の方にちゃんとした指定方法を教えてもらおうと思っています。。

ウォッチリストの変数 st の状態。最初は空っぽ(null)

f:id:moko_03_25:20170807220954j:plain

▼ステップ実行でテキストボックスの内容が代入された様子("\r\n"が改行を表す部分)

f:id:moko_03_25:20170807220950j:plain

▼行頭の余計な情報が除去された(文末以外の空行も一緒に除去)

f:id:moko_03_25:20170807220947j:plain

▼行末の余計な情報が除去された

f:id:moko_03_25:20170807220944j:plain


ちなみに、置換結果の文字列はテキストボックスの中へ書き換えるのではなく「AppendText」を使って追記する形にしています。
すでに別の置換結果の文字列が存在する場合に新しい行へ追記したいと思ったからです。

そのためには、置換の最後には必ず1回分の改行を行って終わるように記述しています。

プロジェクトデータと実行ファイル


Googleドライブにアップしました。

MyTool_UE4PassReplace.zip - Google ドライブ

binフォルダの中にexeが入っています。

 

2017.11.18追記

 

C#のはじめ方

CGアーティストだってWindows用ツールを作りたい!

ということで C# を少しばかり触って簡単なツールをいくつか作ってみました。
思っていたよりもずっと敷居は低かったので、備忘録がてら軽くまとめておこうと思います。

※プログラム自体の初心者が書くアーティスト向けの記事です

基本的な文法の習得


とは言え最低限の文法をまずは覚える必要があります。
「何か作りたい!」のに作れない、この第一歩が最初のもどかしい時間とは思いますが、どうしてもここは頑張るしかないところだと思います。

さて、どんなプログラミング言語でも大体同じなのかなと思いますが、最初に覚える必要がある要素は以下のあたりと思います。

 変数の宣言・代入・型
 メソッド・引数・戻り値
 if文による条件分岐
 配列とfor文による繰り返し
 クラス・オブジェクトの生成
 using(pythonで言うところのimport)
 アクセス修飾子(publicとか)

とりあえずここまで覚えれば、クラスの継承とかコンストラクタとかオーバーロードといったクラス周りのややこしいルールの学習は後回しで良いと思います。
ほんのちょっとしたツールだとクラスを自作しなくても大丈夫な感じですし、実装したいことは工程ごとにGoogleで調べてコピペで大体いけます。

ではどうやって覚えるかというと、自分の場合はこちらのシリーズにお世話になりました。
(正確に言えば同じ投稿者の方のJava超入門シリーズでまずはJavaから覚えました。JavaC#と文法がかなり近いです)

メモ帳でC#超入門 #1 環境構築 by T.Umezawa - ニコニコ動画

この方の動画はとても丁寧で分かりやすく解説されていて、以下のあたりが特徴です。

・声が聞きとりやすい
・メモ帳でコーディングしてコマンドプロンプトコンパイルするスタイル
RPGゲームを作っていきながらの解説
・エラーが出て修正する手順を丁寧に繰り返して解説
・煩雑になったコードを修正対応しやすい&読みやすいコードにどう整形していくかを丁寧に解説
・ドキュメントの調べ方を解説


‥という感じで、単に文法の説明で終わらずとてもポイント高いと感じます。

また、いきなり本を買って手元でページをめくりながら試すよりも、動画を流しながら試す方が初心者にも入りやすくてとても良いですね!

いやいや、メモ帳とかコマンドプロンプトじゃなくて最初からIDE(開発環境)でやりたい!
手っ取り早く文法だけ知りたい!

‥という方にはこちらのシリーズがおすすめです。

【プログラミング講座】第1回 プログラム言語の種類【独り言】

こちらの動画の良いところは以下のあたりです。

Visual Studioを使ったスタイル
・WindowsFormでのツールを制作していきながらの解説
・若干ゲームを作るパートもある
・WindowsFormの各コントロール(ウインドウを構成するパーツ)の解説が充実している


どうすればウインドウサイズを変えても崩れないレイアウトを作成できるのかというあたりの解説も含まれていて、これが案外ググっても出てこないため個人的にとてもポイント高いと思いました。

そうして基本的なところが一通り馴染んだら、次のステップに移って良いと思います。

逆に、上に挙げたあたりは最初に覚えておかないと、入門書を買って読んでもついていけない可能性がありますし、やりたいことをネットで検索してサンプルコードを見つけてもそのままでは動かない時に、自力では対応することができなくて詰んでしまいます。。

実際に何かツールを作ってみる


早速色々作りたいところですが、自分の場合はこちらの書籍を購入して進めました。

作って覚えるVisual C# 2017 デスクトップアプリ入門

作って覚えるVisual C# 2017 デスクトップアプリ入門

 

色んなツールを作りながらステップアップしていける、とても良い本でおすすめです!

C#で具体的なツールを実際に作ってみよう的な本って思いのほか少ないんですね。
多くの書籍が文法の解説や逆引きだったりします。

こちらの書籍は Visual Studio Community 2017 を導入して、.NET向けAPI(?)の「Windowsフォームアプリケーション」というWindowsでお馴染なGUIのツールをC#で作っていく内容になっています。

Visual Studioというと、本格的なプログラミング環境でとても大層でハードル高いイメージがありましたが、むしろ初心者に優しい機能がてんこ盛りなんですね。

何といってもWindows向けのツールのGUIを、ボタンやテキストボックスなどペタペタ貼り付けるだけでほとんどコーディングせず作れてしまいます。

また、変数や関数などに色が付いたり(シンタックスハイライト)、コンパイル時にエラーが出る部分がリアルタイムで表示されたり、定義済みの変数や関数が予測変換でリストアップされたりと便利すぎます。

1命令ずつ順に実行(ステップ実行)してその時々の変数の値を確認しながらデバッグできたりといったあたりも大変便利。。

ただこちらの書籍、最初は簡単なツールから始まって色んなツールを順番に作っていくのですが、最後の家計簿ツールで難易度が急に上がって感じました。。

家計簿ツールの途中でどこを間違ったのか動かなくなってしまい、手順を追っていっても何をしているのか理解できなくなってきていたため、いったん書籍は中断して、好きなものを作っていくステップに移りました(家計簿ツールは一年後に再挑戦してみたらちゃんと完成しました!)。

結果的にもそれで良かったです。

書籍を最後までやらずともツールを作る手順は大体学べます。
あとはネットで調べつつ色々作ってみるべし!という感じでしょうかっ。

ツールを作っていく過程で、クラスをnewしなくても直接使えるものがあったり、見つけたサンプルコードで使われている型が int や string ではなく「var」であったり、触っている上で色々と「?」に遭遇しますが、そういうのは気になったタイミングで調べて、よく分からなければ後回し‥で良いと思います。


という訳で、まずは基礎文法を勉強して上記書籍を一通り試してみるという感じでツール作りに入っていきました。ここまでで、休日などがっつりと費やして一気に進めたら 3週間ほどという感じでした。

その後さらに2週間ほどでほんのちょっとした小さなツール(テキストボックスに入力したファイルパスからファイル名の部分を削るだけのツールとか)を6つ7つ作ってみたらツール作りにも徐々に慣れてきて、今後も徐々にステップアップしながら色々と作っていける手応えを感じました。

楽しくてお仕事にも役に立つので、CGアーティストな皆さまもぜひぜひC#でツールを作ってみてみてください!

エクセルで条件付き書式を適用したのち書式を残してクリアする方法

エクセルの備忘録。

下図のようなリストで、B列の値が一定の値以上ならその行の書式を変えたいといったような場合には「条件付き書式」機能を使うと便利です。

f:id:moko_03_25:20160831022607p:plain

まず書式を反映させたい列、または範囲を選択しておきます。

f:id:moko_03_25:20160831022608p:plain

ホームタブにある「条件付き書式」の「新しいルール」を選択。

f:id:moko_03_25:20160831022609p:plain

数式を使用して、書式設定するセルを決定」を選択し、下図のように計算式を記述した後に「書式」ボタンを押して自由に書式を設定します。

=$B1>=80

この図の場合は「B列に対してB1のセル以降の値が80以上なら赤文字にする」です。

f:id:moko_03_25:20160831022610p:plain

するとB列の値が80以上の行の文字が赤くなりました。

f:id:moko_03_25:20160831022611p:plain

ただしこれで表が完成した場合、このままでは少し問題があります。
下図のように行を挿入すると赤くなる文字がズレました‥
今はまだ「条件付き書式」の情報が残っていて編集する際に厄介なので、これを通常の書式に換えて条件付き書式はクリアしてしまいます。

f:id:moko_03_25:20160831022612p:plain

この時、もう一度「条件付き書式」から「ルールのクリア」を行うだけで書式を残したままクリアがすんなりいく場合と、書式が全て失われてしまう場合があります。
オプション設定なのかエクセルのバージョンなのか‥
とりあえず後者の場合の対処方法を追記しておきます。

まず条件付き書式を外したいセルを選択して一旦コピーします。
次にホームタブの「貼り付け」の右下にあるクリップボードオプションのボタンを押して「すべて貼り付け」ボタンを押して、そのまま上から貼り付けてしまいます。

f:id:moko_03_25:20160831022613p:plain

これで書式自体は移せたので、あとは「条件付き書式」>「ルールのクリア」で条件付き書式をすっきりと除去することができます。

f:id:moko_03_25:20160831022614p:plain

以上です。