以前の記事で、とあるサーバの使用量の増加推移を調べるため、「指定フォルダ配下に存在するファイルの情報(ファイル名、ファイルサイズ、作成日など)を全て表示するコマンド」を作成したという記事を書きました。
あの時は、結果をEXCELに読み込んでグラフ化していたのですが、今後定期的にこの作業を行う必要がありそうなので、ファイル情報取得~集計~グラフ作成までの一連の処理を行うツールを作ってみました。
皆様のプログラム作成において、何かの参考になればと思ったのでソースコード一式を公開したいと思います。
プログラムの概要
今回作ったのは下記のようなツールです。
グラフのX軸ラベルが重なったり、処理中のプログレスリングが少々ぎこちない動きだったり、対象フォルダ指定が単純なテキスト入力だったりと、結構手を抜いている部分は多々ありますが、その辺はご了承下さい。
このツールは、指定したフォルダ配下のファイルをサブフォルダも含めて検索し、ファイル作成日を対象にファイルサイズを集計、グラフ化するものです。
また、次のような機能を持っています。
- 集計する単位は、年ごと、月ごと、日ごとの3つが選択可能です。
- 一覧部分は検索した全てのファイル情報を表示(ファイルサイズの単位はバイト)
- グラフ部分は集計単位の棒グラフと、累計の折れ線グラフを表示(単位はメガバイト)
- グラフ部分はマウス操作で拡大・縮小が可能
- ESCキーで検索を中断し、その時点の集計結果を表示

使い方
対象フォルダの入力欄に、集計したいフォルダ名を入力して、「解析」ボタンをクリックするだけです。
ちなみに、ファイル名をワイルドカード付きで指定することが可能で、例えば
“D:\My Documents\Visual Studio 2019\Projects\*.c” と入力すると、拡張子が ”.c” のものだけを集計対象にすることが可能です。
また、グラフ部はマウスホイールで拡大/縮小が行え、ドラッグ&ドロップで上下左右にグラフをスクロールさせることが可能です。

使用しているライブラリなど
グラフを描画する部分はフリーのグラフ描画ライブラリ「ScottPlot」を使っています。
「ScottPlot」のインストール方法は、こちらの記事をご覧ください。
また、ScottPlotをそのまま使うのではなく、以前こちらの記事で紹介したユーザーコントロールを使っています。
処理中に表示するプログレスリングは、こちら で紹介した方法(WindowsFormsHost経由でPictureBoxにアニメーションGIFを登録して表示)を使っています。

ソースコードの解説
それでは、XAMLとC#のソースコードについて、簡単な解説を交えて紹介いたします。
XAMLのソースコード
単純にコントロールを張り付けているだけなので、特に難しい部分は無いかと思います。
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 |
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:FolderAnalyzer" xmlns:ChartUserControl="clr-namespace:ChartUserControl" x:Class="FolderAnalyzer.MainWindow" xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" Title="FolderAnalyzer" Height="360" Width="600" MinHeight="360" MinWidth="600"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="64"/> <RowDefinition Height="13*"/> <RowDefinition Height="5"/> <RowDefinition Height="12*"/> </Grid.RowDefinitions> <Label Content="対象フォルダ" HorizontalAlignment="Left" Margin="10,7,0,0" VerticalAlignment="Top" Height="26" Width="70"/> <Button x:Name="uxAnalyze" Content="解析" Margin="0,10,10,0" VerticalAlignment="Top" Height="23" HorizontalAlignment="Right" Width="85" Click="uxAnalyze_Click"/> <GridSplitter Grid.Row="2" HorizontalAlignment="Stretch" /> <TextBox x:Name="uxTarget" Height="23" Margin="85,10,100,0" TextWrapping="Wrap" VerticalAlignment="Top"/> <DataGrid x:Name="uxView" ItemsSource="{Binding}" Margin="10" Grid.Row="3" AlternatingRowBackground="Aqua" IsReadOnly="True" Background="{x:Null}" /> <ChartUserControl:ScottLineBarChartControl x:Name="uxChart" Grid.Row="1"/> <RadioButton x:Name="uxYear" Content="年単位" HorizontalAlignment="Left" Margin="85,38,0,8" Click="uxRadio_Click" Width="55"/> <RadioButton x:Name="uxMonth" Content="年月単位" HorizontalAlignment="Left" Margin="156,38,0,8" IsChecked="True" Click="uxRadio_Click" Width="67"/> <RadioButton x:Name="uxDay" Content="年月日単位" HorizontalAlignment="Left" Margin="240,38,0,8" Click="uxRadio_Click" Width="79"/> <WindowsFormsHost x:Name="uxHost" Grid.Row="3" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Hidden"> <wf:PictureBox x:Name="uxProgress" SizeMode="StretchImage" /> </WindowsFormsHost> </Grid> </Window> |
C#のソースコード
グラフ表示で使っているユーザーコントロールについては、こちら の記事にソースコードを丸ごと掲載していますので、ここでは省略します。
気を付ける点としては、WindowsFormのPictureBoxを使用しているため、次の3つのアセンブリを参照設定に追加しておくくらいです。

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 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.IO; using System.Data; using System.Windows.Threading; using System.Drawing; namespace FolderAnalyzer { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { //解析結果(ファイル一覧)を保持するDataTable private DataTable _dt; //コンストラクタ public MainWindow() { InitializeComponent(); //DataGridに行番号を表示 EnableRowNum(); //プログレスリングの画像を登録 uxProgress.Image = Bitmap.FromFile(@".\Resources\progress.gif"); } /// <summary> /// 解析ボタンクリック処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void uxAnalyze_Click(object sender, RoutedEventArgs e) { try { //プログレスリングの表示 uxHost.Visibility = Visibility.Visible; //表示内容を消去 uxView.DataContext = null; //ファイル情報の取得 _dt = ScanFolder(uxTarget.Text); //ファイル情報をDataGridに表示 uxView.DataContext = _dt; //グラフの表示 DrawChart(_dt); //プログレスリングの非表示 uxHost.Visibility = Visibility.Hidden; } catch(Exception exp) { //プログレスリングの非表示 uxHost.Visibility = Visibility.Hidden; MessageBox.Show(exp.Message, "エラー", MessageBoxButton.OK, MessageBoxImage.Error); } } /// <summary> /// 指定したパス配下のファイルを全て取得し、DataTableで返す /// </summary> /// <param name="folder"></param> /// <returns></returns> private DataTable ScanFolder(string path) { DataTable dt = new DataTable(); dt.Columns.Add("ファイル名"); dt.Columns.Add("サイズ", typeof(long)); dt.Columns.Add("作成日", typeof(string)); dt.Columns.Add("最終更新日", typeof(string)); dt.Columns.Add("最終アクセス日", typeof(string)); foreach (var file in Files(path, SearchOption.AllDirectories)) { //ファイル情報をDataRowに格納 DataRow dr = dt.NewRow(); dr[0] = file.FullName; dr[1] = file.Length; dr[2] = file.CreationTime.ToString("yyyy/MM/dd HH:mm:ss"); dr[3] = file.LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss"); dr[4] = file.LastAccessTime.ToString("yyyy/MM/dd HH:mm:ss"); dt.Rows.Add(dr); //溜まっているメッセージ キューを処理 DoEvents(); //ESCキーが押されたら処理を中断 if(Keyboard.IsKeyDown(Key.Escape)) { break; } } return dt; } private void DrawChart(DataTable dt) { if (dt != null) { var len = ((bool)uxYear.IsChecked) ? 4 : ((bool)uxMonth.IsChecked) ? 7 : ((bool)uxDay.IsChecked) ? 10 : 7; var datas = Summary(dt, len); uxChart.Labels = datas.Select(i => i.key).ToArray(); uxChart.YValues = new List<double[]>() { datas.Select(i => (double)i.len / 1000000).ToArray(), datas.Select(i => (double)i.add / 1000000).ToArray() }; uxChart.BarStyleDataNo = 0; uxChart.Draw(""); } } /// <summary> /// 作成日の先頭から指定した文字数でグルーピング集計を行う /// </summary> /// <param name="dt"></param> /// <param name="len"></param> /// <returns></returns> private (string key, long len,long add)[] Summary(DataTable dt,int len) { //日付けでグルーピング集計を行う var res = dt.AsEnumerable().GroupBy(i => i["作成日"].ToString().Substring(0, len)). Select(i => (i.Key, i.Sum(j => long.Parse(j["サイズ"].ToString())))).ToArray(); //各要素の累積値を求める long add = 0; return res.Select(i => (i.Key, i.Item2, add += i.Item2)).ToArray(); } /// <summary> /// 指定したパス配下のファイルを全て抽出する /// </summary> /// <param name="path"></param> /// <param name="searchOption"></param> /// <returns></returns> public IEnumerable<FileInfo> Files(string path, SearchOption searchOption = SearchOption.AllDirectories) { string drive = System.IO.Path.GetPathRoot(path); //ドライブ名を取得 string directory = System.IO.Path.GetDirectoryName(path); //ディレクトリ名を取得 string filter = System.IO.Path.GetFileName(path); //ファイル名を取得 //ディレクトリが未指定ならドライブ名をディレクトリ名とする directory = directory ?? drive; //ファイル名が指定されているとファイル名を、指定されていなければワイルドカードをフィルターに設定 filter = (filter == "") ? "*.*" : filter; //フォルダ名が指定された場合 if (!filter.Contains("?") && Directory.Exists(path) == true && drive != path) { directory = path; filter = "*.*"; } return Files(directory, filter, searchOption); } /// <summary> /// ファイル情報の一覧を取得する。 /// アクセス権限が無いディレクトリは無視する(例外を発生させない)。 /// </summary> /// <param name="directory"></param> /// <param name="filter"></param> /// <param name="searchOption"></param> /// <returns></returns> static public IEnumerable<FileInfo> Files(string directory, string filter, SearchOption searchOption = SearchOption.AllDirectories) { List<FileInfo> infos = new List<FileInfo>(); //ディレクトリが未指定ならカレントディレクトリを対象とする。 directory = (directory.Trim() == "") ? System.IO.Directory.GetCurrentDirectory() : directory; //指定されたディレクトリの情報を取得 DirectoryInfo dir_top = new DirectoryInfo(directory); try { //指定されたディレクトリ直下に存在するファイル情報を取得 foreach (var info in dir_top.EnumerateFiles(filter)) { infos.Add(info); } } catch { } //取得したファイル情報を返す foreach (var info in infos) { yield return info; } //サーチオプションが配下のディレクトリを検索対象にしている場合 if (searchOption == SearchOption.AllDirectories) { //指定されたディレクトリ直下にあるディレクトリを全て取得 foreach (var dir_info in dir_top.EnumerateDirectories("*")) { infos.Clear(); try { //取得したディレクトリ直下から末端までの階層をたぐって全てのファイル情報を取得 foreach (var info in dir_info.EnumerateFiles(filter, SearchOption.AllDirectories)) { infos.Add(info); } } catch { } //取得したファイル情報を返す foreach (var info in infos) { yield return info; } } } } /// <summary> /// ラジオボタンが押された時の処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void uxRadio_Click(object sender, RoutedEventArgs e) { DrawChart(_dt); } /// <summary> /// 行Noを表示するためのイベント処理を登録 /// </summary> private void EnableRowNum() { uxView.LoadingRow += (sender, e) => { e.Row.Header = (e.Row.GetIndex() + 1).ToString().PadLeft(10); }; } /// <summary> /// メッセージ キューに現在ある Windows メッセージをすべて処理する。 /// </summary> private void DoEvents() { DispatcherFrame frame = new DispatcherFrame(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(f => { ((DispatcherFrame)f).Continue = false; return null; }), frame); Dispatcher.PushFrame(frame); } /// <summary> /// プログレスリングの表示/非表示 /// </summary> /// <param name="sw"></param> private void ProgreeRing(bool sw) { if (sw) { uxHost.Visibility = Visibility.Visible; } { uxHost.Visibility = Visibility.Hidden; } } } } |
プロジェクト一式のダウンロード
下記リンクからプロジェクト一式のダウンロードが可能です。
NuGetでインストールしたScottPlotも一緒に入っています。
まとめ
今回は指定したフォルダ配下のファイルからファイル情報を取得し、作成日ごとにファイル容量を集計、グラフ化するツールについて、使い方とソースコードを紹介しました。
このサイトで今までに紹介した内容(ScottPlot、複合グラフ作成ユーザーコントロール、PictureBoxを使ったプログレスリングの表示、フォルダ配下のファイル情報取得)を使っています。
個々の記事を見て使い方がピンとこなかった方においても、今回の記事で具体的な利用方法が掴めたのではないかと思います。
皆さんの何かのお役に立てれば幸いです。