CSVファイルの読み書き設定をC#スクリプトで記述するWPFアプリをDesktop App Converterで変換してストアに公開しました

デスクトップアプリをUWPに変換してWindowsストアに公開可能な状態にするDesktop App Converterを試してみたい!
ということで、ブログのネタで作成していたWPFアプリ(CsvEditSharp)をDesktop App Converterに掛けてストアに公開するまでをチャレンジしてみました。

ひとまず、公開まで漕ぎつけることができたので、アプリの宣伝をしておきます。

CsvEditSharp

CsvEditSharpは、CSVファイルの読み書き設定をC#スクリプトで記述するCSVエディタです。
スクリプトでは、C#CSVファイルを扱うためのクラスライブラリCsvHelperAPIを利用して各種設定を記述します。

Windows Storeから無料でダウンロードできます。(Windows10 Anniversary Update 以降のデスクトップPCのみで利用可能)

www.microsoft.com

ソースコードGitHubに公開しています。

github.com

基本操作

設定スクリプトのひな型を生成してCSVファイルを読み込む

初めて扱うCSVファイル等、設定スクリプトが存在していない場合に、CSVファイルの読み込みと同時に設定スクリプトのひな型を生成することができます。

  • ツールバーの「Configuration Script」コンボボックスで "(Auto Genarate)" を選択します

f:id:pierre3:20170106142156p:plain

  • 「Open」ボタンをクリックして読み込むCSVファイルを選択します

  • 以下のダイアログで、自動生成される設定スクリプトの名前、CSVファイルのエンコーディングおよびヘッダレコードの有無を入力して[OK]をクリックします

f:id:pierre3:20161220135026p:plain

次のように、選択したCSVファイルのヘッダ情報を基に設定スクリプトが自動生成されます。

f:id:pierre3:20161220134928p:plain

レコード格納クラスがFieldData という名前で生成されます。

  • クラス内の各プロパティが1つのカラムを表します
  • ヘッダレコードに定義されているカラム名が、そのままプロパティ名となります
  • プロパティのデータ型は全て文字列(string)型となります
  • カラム名にプロパティ名として使用できない文字が含まれる場合、またはヘッダレコードが存在しない場合のプロパティ名にはcolumn_ + カラム番号が割り当てられます

設定スクリプトのひな型をカスタマイズする

設定スクリプトの編集

設定スクリプトを、読み込んだCSVファイルの内容に応じて書き換えます。

Encoding = Encoding.GetEncoding("utf-8");

//Genderの選択肢を定義するenum
enum Gender
{
    Male,
    Female
}

//レコード格納クラスの名前を変更
class Person
{
  //プロパティの型をデータの種類に応じて変更
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
    public Gender Gender { get; set; }public bool Married { get; set; }
    public double PocketMoney { get; set; }
}

//クラスマッピングの設定も、扱うデータの種類に応じたものに変更
RegisterClassMap<Person>(classMap =>
{
    classMap.Map(m => m.Name).Name("Name");
    classMap.Map(m => m.Birthday).Name("Birthday")
        .TypeConverterOption("M/d/yyyy");
    classMap.Map(m => m.Gender).Name("Gender");
    classMap.Map(m => m.Married).Name("Married")
        .TypeConverterOption(true,"Y")
        .TypeConverterOption(false,"N");
        
    var culture = System.Globalization.CultureInfo.GetCultureInfo("en-us");
    classMap.Map(m => m.PocketMoney).Name("PocketMoney")
        .TypeConverterOption("C")
        .TypeConverterOption(NumberStyles.Currency)
        .TypeConverterOption(culture);
});

編集が完了したら[Run]ボタンをクリックして、結果を確認します。

f:id:pierre3:20170106150619p:plain

編集したコードに問題がなければ、編集後の設定でCSVファイルが再読み込みされ、内容がCSVエディタに表示されます f:id:pierre3:20161220135128p:plain

設定スクリプトの保存

編集した設定スクリプトは[Save] (上書き保存)ボタン、[SaveAs...] (名前を付けて保存)ボタンで保存できます。

[SaveAs...]ボタンをクリックすると、以下のダイアログが表示されます。

f:id:pierre3:20170107221931p:plain

  • 「Save as a new file」を選択

  • 「Save into the current directory as "Default.config.csx"」を選択

    • 設定スクリプトを、読み込み中のCSVファイルと同じフォルダに"Default.config.csx"という名前で保存します。
    • 読み込むCSVファイルと同じフォルダに"Default.config.csx"ファイルが存在する場合、常にこのファイルが設定スクリプトとして使用されます。(「Configuration Script」コンボボックスでの選択は無視されます)

設定スクリプトを指定してCSVファイルを読み込む

対応する設定スクリプトが既に存在する場合、「Configuration Script」で対応するスクリプトの名前を選択後、CSVファイルを読み込みます。

f:id:pierre3:20170107225016p:plain

設定スクリプトの管理

[Settings...]ボタンで表示される以下のダイアログで、作成済みの設定スクリプトの名前変更や削除が可能です。  

f:id:pierre3:20170110130931p:plain

値の編集

CSVエディタ(読み込んだCSVファイル名のタブ)内のセルを直接編集することができます。

クラスマッピングでマップしたプロパティのデータ型に応じて入力方法や入力可能な値が変わります。

  • データ型がenumの場合、enumのメンバーを選択肢としたコンボボックスで値を選択します
  • データ型がboolの場合、チェックボックスのON/OFFでTrue/Falseを切り替えます
  • データ型が数値型やDateTime型の場合、セルに入力した文字列が目的の型に変換できない場合にエラーメッセージを表示します。

f:id:pierre3:20170110224957p:plain

AddValidation() メソッドを使用してより詳細な入力検証を設定することも可能です。

編集したCSVデータの保存

ツールバーの[SaveAs]ボタンをクリックして、編集後のCSVデータを別のCSVファイルとして保存することができます。 CSVエディタに表示されている状態がそのまま保存されます。(Queryメソッドでフィルタ・ソートを行った場合も、表示されている内容がそのまま保存されます。)

f:id:pierre3:20170111104107p:plain

設定スクリプトAPI

Encoding プロパティ

Encoding Encoding { get; set; }

対象CSVファイルのエンコーディングを指定します。 省略した場合は Encoding.Defaultが割り当てられます。

Encoding = Encoding.GetEncoding("utf-8");

RegisterClassMap メソッド

void RegisterClassMap<T>();
void RegisterClassMap<T>(Action<CsvClassMap<T>> propertyMapSetter);
void RegisterClassMap<T>(Action<CsvClassMap<T>> propertyMapSetter, RegisterClassMapTarget target);

CsvHelperを利用したクラスマッピングの設定を記述します。

  • 登録するクラスマッピングオブジェクトを引数に取るデリゲートpropertyMapSetter内に設定内容を記述します。
  • 第2引数で、登録したクラスマッピングを使用するタイミングを指定できます。
    • RegisterClassMapTarget.Reader : 読み込み時のみ
    • RegisterClassMapTarget.Writer : 書き込み時のみ
    • RegisterClassMapTarget.Both : 読み書き両用

クラスマッピング設定の記述方法については、CsvHelper 公式ドキュメントのMappingのセクションを参照ください。

//既定のクラスマッピング設定を使用
RegisterClassMap<Person>();

//読み書き両用
RegisterClassMap<Person>(classMap => {
    classMap.Map(m => m.Name);
    classMap.Map(m => m.Birthday);
    classMap.Map(m => m.Gender);
    classMap.Map(m => m.Married)
        .TypeConverterOption(true,"Y")
        .TypeConverterOption(false,"N");
    classMap.Map(m => m.PocketMoney)
        .TypeConverterOption("C")
        .TypeConverterOption(NumberStyles.Currency);
});

//読み込み時のみ
RegisterClassMap<Person>(classMap => {
    classMap.Map(m => m.Name);
    classMap.Map(m => m.Birthday);
    classMap.Map(m => m.Gender);
    classMap.Map(m => m.Married)
        .TypeConverterOption(true,"Y")
        .TypeConverterOption(false,"N");
    classMap.Map(m => m.PocketMoney)
        .TypeConverterOption("C")
        .TypeConverterOption(NumberStyles.Currency);
}, RegisterClassMapTarget.Reader);

SetConfiguration メソッド

void SetConfiguration(Action<CsvConfiguration> configurationSetter);

引数configurationSetterデリゲートに渡されるCsvConfigurationオブジェクトの値を書き換えることで、CSVファイル読み書き時の詳細な設定を記述することができます。

ここで設定可能な項目の詳細は CsvHelper 公式ドキュメントのConfigurationのセクションを参照ください。

SetConfiguration(config =>
{
    config.HasHeaderRecord = false;
    config.AllowComments = true;
    config.Comment = '#';
    config.Delimiter = ';';
    //etc...
});

AddValidation メソッド

void AddValidation<TType, TMember>(Expression<Func<TType, TMember>> memberSelector, Func<TMember, bool> validation, string errorMessage);

値変更時の入力検証機能を追加することができます。

  • memberSelector デリゲート(式木)で対象となるカラムのプロパティを指定します。
  • validation デリゲートで検証を通過する条件を指定します。
  • errorMessage に検証NG時に表示する文字列を指定します。
AddValidation<Person,DateTime>(
    m => m.Birthday , 
    dt => dt <= DateTime.Now.Date,
    "Cannot enter a future date.");

AddValidation<Person, double>(
    m => m.PocketMoney , 
    n => (n > 0) && (n < 10000.0),
    "PocketMoney must be in the range $0 to $10000.");

Query メソッド

void Query<T>(Func<IEnumerable<T>, IEnumerable<T>> query);
void Query<T>(Action<IEnumerable<T>> query);

データのフィルタ、ソート

LINQの拡張メソッドを利用して、表示するデータのフィルタ、ソートを行うことができます。

Query<Person>(source => source
    .Where(m => m.Gender == Gender.Female )
    .Where(m => m.Married )
    .OrderBy(m => m.PocketMoney) );

データの一括更新

ForEach()拡張メソッドを使用して、データを書き換えることも可能です。

Query<Person>( record => record
    .Where( m => m.Gender == Gender.Male )
    .Where( m => m.Married )
    .ForEach( m =>
    {
        m.Name += " *";
        m.PocketMoney = 0;
    })
);

(Queryメソッドは、設定スクリプトのタブではなく、CSVデータ表示タブの下部にあるテキストエディタ内に記述します。下記のようなコードを記述後、[Execute]ボタンをクリックすることで、コードが実行され、表示に反映されます。)

f:id:pierre3:20170110161754p:plain