2012年11月11日

RingListプラグインをアップデートしました

長らく放置していたRingListプラグインをアップデートしてみました。

Ring List Plugin

更新内容は「ドラッグでリングを回転できるようにする」です。PC版のブラウザに対しては簡単に出来たのですが、スマホ版のブラウザはマウス関係のイベントを持っていません。少し調べたところ、以下のページが参考になりました。

iPhone/Android/PC 対応。jQuery で書くタッチイベント

上のページのテンプレートだけ引用しておきます。

PC版、スマホ版対応のドラッグイベント
var isTouch = ('ontouchstart' in window);
$('#hoge').bind({
'touchstart mousedown': function(e) {
e.preventDefault();
this.pageX = (isTouch ? event.changedTouches[0].pageX : e.pageX);
this.pageY = (isTouch ? event.changedTouches[0].pageY : e.pageY);
},
'touchmove mousemove': function(e) {
e.preventDefault();
},
'touchend mouseup': function(e) {
}
});


クリック、またはタッチしたときの座標の取得方法が違います。



posted by ぺるたご at 21:21| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2012年10月28日

Visual Studio 2012 便利機能

今回も知人より寄稿してもらいました。この場を借りて感謝します。
※ 前回の記事(Visual Studio 2012 Web機能)もよろしくどうぞ。

Visual Studio 2012
-- セミナーフィードバック その2 --



この夏〜秋のMicrosoftセミナーを梯子しました。
今回はその中でも特に、Visual Studio 2012の新機能で便利そうな印象を受けた機能を、備忘録としてまとめたいと思います。
※ 一部機能はVisual Studio 2010でも拡張機能をインストールすると利用できます。前回投稿した記事とあわせて、Visual Studio の拡張機能については、別の記事としてまとめる予定です。


ソリューションエクスプローラー



Visual Studioでファイルや関数などを探す時、「ctrl+shift+f」で検索し、検索結果に表示された一覧から、自分の探しているものを見つけていました。
「えいや!」の一発で見つかることもあれば、一通り確認しても目的のものが見つからなかった時もありました。
そんな時は、たくさん開いてしまったファイルタブのおかげで、当初開いていたファイルタブが深い所に埋もれてしまうこともしばしばありました。

そんな私に朗報が!
それがソリューションエクスプローラーの検索機能です。
検索した項目にヒットしたものだけでソリューションエクスプローラーが構成されます。
沢山のノードを開いて閉じてと探す手間がなくなりました。
しかもこの検索機能の便利なところは、ファイル(クラス)だけじゃなく、メンバー(関数や変数)まで検索できてしまうことです。
ソリューション検索

※ 少し難をいえば、上記の機能はWebサイトプロジェクトでは利用できません。(WebアプリケーションならばOK)コードメトリクスの機能もWebサイトでは利用できないなど、Webサイトの冷遇っぷりに、涙で袖を濡らしました。

そしてソリューションエクスプローラーの検索に抱き合わせで利用できる、Viewタブ。
検索にヒットしたファイルが、自分の探し求めているファイルか確認するために、ファイルの中身を参照する際、Viewタブで開けば新しいタブがどんどん増えてゆくということもありません。
Viewタブが一番右側に一つ設けられ、そのタブの中身が切り替わることで、ファイルの中身を確認できます。

無駄なタブを開かなくてもOKというのは、結構使い勝手が良いです。
それに関連して、地味に便利な機能として、画像ファイルのホバー表示があります。
画像ファイルの中身を確認するのに、わざわざファイルを開かなくても、画像ファイルにカーソルを当てれば、画像イメージが表示されるのです。
画像のホバー表示

検索機能の強化



ソリューションエクスプローラーもそうですが、Visual Studio 2012は全体的に検索機能が強化されています。
沢山存在しているツールボックスのコントロールも検索で見つけることができます。
ツールボックスの検索
※ 上記の画像のように、ドラッグドロップで追加したJavaScriptの「hoge」という関数(「全般」タグに属しています)も含めて検索することができます。

また、Visual Studio全体に対して、検索することも可能です。
設定を変更したいなど、日常的に利用しない機能は階層が深い所にあり、基本覚えていません。
探すのに時間がかかりますし、見つからない時にはWebで調べるなどVisual Studioを離れないといけません。
そんな作業ロスが少なくなりそうです。


Page Inspector



※ 前回に引き続き、Web系のお話になってしまうのですが。。
ASP.NETをやり始めて間もなかった時。初めて自分の作成したWebサイトのページのソースの表示をしたとき、そこで確認できるHTMLが、開発で作っていたaspxとずいぶん違っていたことにびっくりしました。

Page Inspectorを利用すれば、ブラウザにレンダリングされたUIとaspx上のサーバーコントロールを紐付けて確認することが可能です。
もちろん、UIとDOM要素の紐付け、各要素ごとのスタイルの把握も行えますが、これらの機能は、各ブラウザに搭載されている開発ツールでも利用可能な機能で、それほど目新しさはありません。

以下には、個人的なPage Inspectorに対して抱いた所感をまとめたいと思います。

所感@
サーバーコントロールを含めて紐付けがおこなえることの意義が、あまり感じられませんでした。
ASP.NETの経験が浅かった頃、aspxがHTMLに展開された時には、ソースの表示で戸惑いもありましたが、現在は「大体こんな感じで紐づいているのかな」というのはわかります。
これはあくまで個人的な意見になります。
正直、ユーザーコントロールの中身まで検証してくれた時には、「おぉ!すばらしい!」と感動しました。
Page Inspector ユーザーコントロール
※ こちらの図では、テキストボックスとボタンのグループをユーザーコントロールとして作成しています。テキストボックスの要素を検証した際には、ユーザーコントロール内のテキストボックスがハイライトされています。

ASP.NETのWebフォームのサーバーコントロールがどのようなDOMになるのか、勉強する過程にこれらはとても有用な機能だと思います。
※ サーバーコントロールは、複数のDOMのまとまりをプログラマーが意識することなく、1つの抽象的なコントロールとして利用できるところがポイントだと、当初言われていたことを不意に思い出しました。
現在は(DOMを厳密に把握し、それを踏まえてスタイルしていくため)そんなことを言ってはいられないのでしょうね。

所感A
JavaScriptのデバッグが行えなかったことは少し残念でした。
JavaScriptはUIの形成に深く関わる部分になるため、Page Inspector(ページ検証)機能として、JavaScriptのデバッグは是非ほしいものだと思いました。

Visual Studio上でJavaScriptのデバッグは可能ですが、それはPage Inspectorとはまったく独立した機能として提供されています。
そしてデバッグの際にはブラウザとVisual Studioの行き来が発生してしまい、各ブラウザに付属の開発者ツールに比べて、画面の切り替えなどが少し煩雑である印象があります。
しかも、Visual Studio上でJavaScriptをデバッグ中は、IEの開発者ツールでJavaScriptのデバッグができなくなるため(Visual StudioのほうでJavaScriptデバッグプロセスにアタッチしてしまっているため)、IEの開発者ツールで、DOMの検証・スタイルの確認・JavaScriptのデバッグを一元的に行える状況を阻害します。
DOMの検証・スタイルの確認と紐付けて利用できないVisual StudioのJavaScriptのデバッグ機能をあえて利用するメリットは、特に思いつきません。

現在はブラウザの検証ツールには、もっぱらChromeの開発者ツールのお世話になっています。
Chromeはサーバーコントロールと紐付けて検証などできませんが、多くの便利機能を備えているため、作業効率化に寄与してくれています。
ただ、Visual Studio内で完結できるのならば、それに越したことはないと思っています。
このPage InspectorはVisual Studio 2012で初めて搭載された機能ですので、今後どのような成長を遂げるか、期待しています。
最終的には、IEの開発者ツールで行えるようなことが、Visual Studioに含められたら最高!って手前勝手ながら願っています。



posted by ぺるたご at 22:06| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2012年10月14日

CSVインポートをデザインパターンで実装してみる

今回はデザインパターンを使ってCSVのインポート機能を実装してみます。デザインパターンという名前は有名ですが、どんなときに使ったらいいのかが悩みどころ。

サンプルコードはC#4.0+.NET Framework 4で実装していますが、古いバージョンでもたぶん動きます。VB使いの人は・・・、適当に読み替えてください。



仕様

画面のボタンを押したらユーザーのデータが書かれたCSVファイルを読み込み、それをUserデータクラスのコレクション(今回はList<User>)に格納する。CSVの中身は以下のような感じ。

01,ももこ,8
02,ひろし,40
03,すみれ,40
04,さきこ,11
05,ともぞう,76



とりあえず実装

とりあえず仕様を満たすように実装してみましょう。特に何の工夫もなく、以下のような感じで。

とりあえず実装
//ボタンのイベントハンドラ
private void button1_Click(object sender, EventArgs e)
{
string fileName = "C:\\user.csv";
List<User> userList = new List<User>();
using (TextFieldParser parser = new TextFieldParser(fileName))
{
parser.Delimiters = new string[] { "," };
while (!parser.EndOfData)
{
string[] fields = parser.ReadFields();
User user = new User();
user.UserCode = fields[0];
user.UserName = fields[1];
user.Age = Convert.ToInt32(fields[2]);
userList.Add(user);
}
}
}

//データクラス
public class User
{
public string UserCode { get; set; }
public string UserName { get; set; }
public int Age { get; set; }
}



CSVの読み込みにはTextFieldParserクラスを使っています。こいつはMicrosoft.VisualBasic.FileIO名前空間のクラスなので、C#から使うときはusingでインポートしましょう。Userクラスはユーザーのデータを格納する単なる入れ物です。

とにかく仕様どおりに動くプログラムができました。それではケチをつけていきます。
 ・CSVインポート機能って他でも使いそうなので、Formクラスに書きたくない。
 ・「CSVを読み込む処理」と「データクラスに詰め直す処理」が同じ関数内に書かれてて何か嫌。
 ・他のCSV(例えば住所とか)もインポートするようになったら、冗長な記述が増えそう。




Strategyパターンで実装してみる

Strategyパターンとは、ロジックを別クラスに切り出して簡単に差し替えられるようにしようよ、ってパターンです。

Strategyパターンで実装
//ボタンのイベントハンドラ
private void button1_Click(object sender, EventArgs e)
{
string fileName = "C:\\user.csv";
//ロジックの差し替えポイント。
CsvImporter<User> csv = new CsvImporter<User>(new UserCsvAnalysis());
List<User> userList = csv.Import(fileName);
}

//データクラス(ユーザー)
public class User
{
public string UserCode { get; set; }
public string UserName { get; set; }
public int Age { get; set; }
}

//データクラスへの詰め替え機能を司るインターフェイス
public interface ICsvAnalysisStrategy<T>
{
T Analyze(string[] csvFields);
}

//ユーザーCSVの1行をデータクラスに詰め替えるクラス
public class UserCsvAnalysis : ICsvAnalysisStrategy<User>
{
public User Analyze(string[] csvFields)
{
User user = new User();
user.UserCode = csvFields[0];
user.UserName = csvFields[1];
user.Age = Convert.ToInt32(csvFields[2]);
return user;
}
}

//CSVインポートの窓口的なクラス
public class CsvImporter<T>
{
private ICsvAnalysisStrategy<T> analyzer;

public CsvImporter(ICsvAnalysisStrategy<T> analyzer)
{
this.analyzer = analyzer;
}

public List<T> Import(string fileName)
{
List<T> list = new List<T>();
using (TextFieldParser parser = new TextFieldParser(fileName))
{
parser.Delimiters = new string[] { "," };
while (!parser.EndOfData)
{
list.Add(analyzer.Analyze(parser.ReadFields()));
}
}
return list;
}
}



Strategyパターンの肝としては、ロジックを司るクラス(ここではデータ詰め替え用のUserCsvAnalysisクラス)にインターフェイスを付けておきます。インターフェイスを付けた各クラスは「同じような物」として扱えます。「同じような物」だから簡単に差し替えられる、という理屈です。

コード中に出てくる「T」というのはデータクラスの型、ここではUserクラスのことです。ユーザーだけでなく、別のCSV(例えば住所とか)をインポートすることを見越して、ジェネリックで一般化しています。

では、さきほどケチをつけたポイントについて確認してみます。

・CSVインポート機能って他でも使いそうなので、Formクラスに書きたくない。
 →インポート機能をFormクラス以外に分離したのでどこからでも使えます。

・「CSVを読み込む処理」と「データクラスに詰め直す処理」が同じ関数内に書かれてて何か嫌。
 →「CSVを読み込む処理」はCsvImporter<T>クラス、「データクラスに詰め直す処理」はUserCsvAnalysisクラスに分離されてます。

・他のCSV(例えば住所とか)もインポートするようになったら、冗長な記述が増えそう。
 →この点については、実際に住所CSVもインポートできるように改造してみましょう。




住所CSVもインポートできるようにしてみる

前回からの変更点を赤文字にしています。

住所CSVもインポートできるようにしてみる
//ボタンのイベントハンドラ
private void button1_Click(object sender, EventArgs e)
{
string fileName = "C:\\user.csv";
//ロジックの差し替えポイント。ユーザー用と住所用を差し替える。
CsvImporter<Address> csv = new CsvImporter<Address>(new AddressCsvAnalysis());
List<Address> addressList = csv.Import(fileName);
}

//データクラス(ユーザー)
public class User
{
public string UserCode { get; set; }
public string UserName { get; set; }
public int Age { get; set; }
}

//データクラス(住所)
public class Address
{
public string ZipCode { get; set; }
public string Prefecture { get; set; }
public string City { get; set; }
public string StreetNumber { get; set; }
}


//データクラスへの詰め替え機能を司るインターフェイス
public interface ICsvAnalysisStrategy<T>
{
T Analyze(string[] csvFields);
}

//ユーザーCSVの1行をデータクラスに詰め替えるクラス
public class UserCsvAnalysis : ICsvAnalysisStrategy<User>
{
public User Analyze(string[] csvFields)
{
User user = new User();
user.UserCode = csvFields[0];
user.UserName = csvFields[1];
user.Age = Convert.ToInt32(csvFields[2]);
return user;
}
}

//住所CSVの1行をデータクラスに詰め替えるクラス
public class AddressCsvAnalysis : ICsvAnalysisStrategy<Address>
{
public Address Analyze(string[] csvFields)
{
Address address = new Address();
address.ZipCode = csvFields[0];
address.Prefecture = csvFields[1];
address.City = csvFields[2];
address.StreetNumber = csvFields[3];
return address;
}
}


//CSVインポートの窓口的なクラス
public class CsvImporter<T>
{
private ICsvAnalysisStrategy<T> analyzer;

public CsvImporter(ICsvAnalysisStrategy<T> analyzer)
{
this.analyzer = analyzer;
}

public List<T> Import(string fileName)
{
List<T> list = new List<T>();
using (TextFieldParser parser = new TextFieldParser(fileName))
{
parser.Delimiters = new string[] { "," };
while (!parser.EndOfData)
{
list.Add(analyzer.Analyze(parser.ReadFields()));
}
}
return list;
}
}




どうでしょう?「CSVを読み込む処理」を司るCsvImporter<T>クラスは全く変更されていません。変更されているのはまさに「住所」に関する部分だけです。

ではまたケチを付けてみましょう。
CsvImporter<Address> csv = new CsvImporter<Address>(new AddressCsvAnalysis());
「ロジックの差し替えポイント」において「Address」と「AddressCsvAnalysis」が冗長です。住所用の詰め替えロジックを使ったら、戻り値は住所に決まってますから。ということで、「詰め替えロジックのインスタンスを作る処理」を別クラスに切り出します。いわゆるFactoryですね。「サルでもわかる 逆引きデザインパターン」を参考にしています。



Factoryクラスを作ってみる

前回からの変更点を赤文字にしています。

Factoryクラスを作ってみる
//ボタンのイベントハンドラ
private void button1_Click(object sender, EventArgs e)
{
string fileName = "C:\\user.csv";
//ロジックの差し替えポイント。
CsvImporter<Address> csv = new CsvImporter<Address>(/*引数を撤去*/);
List<Address> addressList = csv.Import(fileName);
}

//データクラス(ユーザー)
public class User
{
public string UserCode { get; set; }
public string UserName { get; set; }
public int Age { get; set; }
}

//データクラス(住所)
public class Address
{
public string ZipCode { get; set; }
public string Prefecture { get; set; }
public string City { get; set; }
public string StreetNumber { get; set; }
}

//データクラスへの詰め替え機能を司るインターフェイス
public interface ICsvAnalysisStrategy<T>
{
T Analyze(string[] csvFields);
}

//ユーザーCSVの1行をデータクラスに詰め替えるクラス
public class UserCsvAnalysis : ICsvAnalysisStrategy<User>
{
public User Analyze(string[] csvFields)
{
User user = new User();
user.UserCode = csvFields[0];
user.UserName = csvFields[1];
user.Age = Convert.ToInt32(csvFields[2]);
return user;
}
}

//住所CSVの1行をデータクラスに詰め替えるクラス
public class AddressCsvAnalysis : ICsvAnalysisStrategy<Address>
{
public Address Analyze(string[] csvFields)
{
Address address = new Address();
address.ZipCode = csvFields[0];
address.Prefecture = csvFields[1];
address.City = csvFields[2];
address.StreetNumber = csvFields[3];
return address;
}
}

//CSVインポートの窓口的なクラス
public class CsvImporter<T>
{
private ICsvAnalysisStrategy<T> analyzer;

public CsvImporter(/*引数を撤去*/)
{
this.analyzer = CsvAnalysisFactory<T>.Create();
}

public List<T> Import(string fileName)
{
List<T> list = new List<T>();
using (TextFieldParser parser = new TextFieldParser(fileName))
{
parser.Delimiters = new string[] { "," };
while (!parser.EndOfData)
{
list.Add(analyzer.Analyze(parser.ReadFields()));
}
}
return list;
}
}

//詰め替えロジックのインスタンスを作るクラス
public static class CsvAnalysisFactory<T>
{
public static ICsvAnalysisStrategy<T> Create()
{
if (typeof(T) == typeof(User))
{
return new UserCsvAnalysis() as ICsvAnalysisStrategy<T>;
}
else if (typeof(T) == typeof(Address))
{
return new AddressCsvAnalysis() as ICsvAnalysisStrategy<T>;
}
return null;
}
}




新設したCsvAnalysisFactory<T>クラスでは、ジェネリック引数Tによってどの詰め替えロジックを使うかを判断しています。以前は画面側で判断していたので、これで、画面側プログラマがどうやってインスタンスを作るか悩む必要がなくなります。今回は引数もプロパティも与えないので悩むこともありませんが・・・。この状態のクラス図は以下のようになります。

CSVインポートのクラス図


ここまででいったん完成としますが、試しに仕様を追加してみましょう。
・CSVの種類によってはヘッダー行が存在しています。
・ヘッダーのありなしはCSVの種類によって固定です。
・ヘッダー行はインポートしたくありません。
・ヘッダー行は1行目だけです。




ヘッダー行のありなしを実装してみる

前回からの変更点を赤文字にしています。

ヘッダー行のありなしを実装
//ボタンのイベントハンドラ
private void button1_Click(object sender, EventArgs e)
{
string fileName = "C:\\user.csv";
//ロジックの差し替えポイント。
CsvImporter<Address> csv = new CsvImporter<Address>();
List<Address> addressList = csv.Import(fileName);
}

//データクラス(ユーザー)
public class User
{
public string UserCode { get; set; }
public string UserName { get; set; }
public int Age { get; set; }
}

//データクラス(住所)
public class Address
{
public string ZipCode { get; set; }
public string Prefecture { get; set; }
public string City { get; set; }
public string StreetNumber { get; set; }
}

//データクラスへの詰め替え機能を司るインターフェイス
public interface ICsvAnalysisStrategy<T>
{
bool HasHeader { get; }
T Analyze(string[] csvFields);
}

//ユーザーCSVの1行をデータクラスに詰め替えるクラス
public class UserCsvAnalysis : ICsvAnalysisStrategy<User>
{
//ヘッダー行あり
public bool HasHeader { get { return true; } }

public User Analyze(string[] csvFields)
{
User user = new User();
user.UserCode = csvFields[0];
user.UserName = csvFields[1];
user.Age = Convert.ToInt32(csvFields[2]);
return user;
}
}

//住所CSVの1行をデータクラスに詰め替えるクラス
public class AddressCsvAnalysis : ICsvAnalysisStrategy<Address>
{
//ヘッダー行なし
public bool HasHeader { get { return false; } }

public Address Analyze(string[] csvFields)
{
Address address = new Address();
address.ZipCode = csvFields[0];
address.Prefecture = csvFields[1];
address.City = csvFields[2];
address.StreetNumber = csvFields[3];
return address;
}
}

//CSVインポートの窓口的なクラス
public class CsvImporter<T>
{
private ICsvAnalysisStrategy<T> analyzer;

public CsvImporter()
{
this.analyzer = CsvAnalysisFactory<T>.Create();
}

public List<T> Import(string fileName)
{
List<T> list = new List<T>();
using (TextFieldParser parser = new TextFieldParser(fileName))
{
parser.Delimiters = new string[] { "," };
if (analyzer.HasHeader)
{

//ヘッダーありの場合は1行読み飛ばす。
parser.ReadLine();
}

while (!parser.EndOfData)
{
list.Add(analyzer.Analyze(parser.ReadFields()));
}
}
return list;
}
}

//詰め替えロジックのインスタンスを作るクラス
public static class CsvAnalysisFactory<T>
{
public static ICsvAnalysisStrategy<T> Create()
{
if (typeof(T) == typeof(User))
{
return new UserCsvAnalysis() as ICsvAnalysisStrategy<T>;
}
else if (typeof(T) == typeof(Address))
{
return new AddressCsvAnalysis() as ICsvAnalysisStrategy<T>;
}
return null;
}
}




ヘッダーのありなしはCSVの種類に固有の話なので、詰め替え機能を司るインターフェイス(ICsvAnalysisStrategy<T>)にHasHeaderプロパティを追加しました。あとは、「ヘッダー行は読み込まない」という仕様なので、インポートの窓口クラス(CsvImporter<T>)で読み飛ばすように処理を追加しています。



まとめ


Strategyパターンはわりと使う機会がありそうです。使いどころとしては、if文で似たようなロジックに分岐しているところでしょうか。





posted by ぺるたご at 22:46| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。