SQLとオブジェクト指向とO/Rマッピングの隙間で

ベンチャー社長で技術者で: オブジェクト指向言語で処理したら保守性が悪い!
http://el.jibun.atmarkit.co.jp/g1sys/2009/12/post-af96.html
SQLに依存することの危険性 ー 単体DBサーバでは終わらない時代の考え方 | 独り言v6
http://www.nonsensecorner.com/wp25/?p=4075

どちらも4年程前の記事ので、直接の言及は避けて、その延長上にメモを残して置かんと欲す…まあ、当時の状況とかもろもろの業務的な事情とか、その当時の「流行り」みたいなものがあるので、なんとも言えないところがあるのです。特に、オブジェクト脳とO/Rマッピング、さらに最近のNoSQLのあたりは数年たつと熱がおさまるもので、業務的にはうまく枯れてくれるとよいかなという分野です。どれも長く使うものですからね。

先に、先の2つの記事の欠けているところを補っておきます。

■ストアドプロシージャはロジックを含めたほうが効率が良い

私もやったことがあるのですが、一時期オブジェクト指向的にストアドプロシージャを使おうと思っていたことがあります。うまく再利用すると良いのでは?と考えていたのですが、ちまちまと小さなストアドプロシージャを作ると、先の「※なぜ、WHERE 売価 <> fn_標準価格(得意先コード, 商品コード)としてないか分かりますか?」に陥ります。答えはマッチングする件数が多くなって、ストアド fn_標準価格 を何度も呼び出してしまうから…なのですが、実はこのノウハウ自体が不要なのです。一見、共通化して fn_標準価格 がうまく隠蔽化しているように見えますが、これのために、インデックスの配置やらストアドを使ったときのクエリのノウハウやらが混在してしまい、データベースの性能をうまく引き出せなくなります。なので、ちまちまとした共通化をするよりも、がっつりとコピペをしてクエリを作ってしまったほうが、ストアド自体の性能が引き上げられるし、バッドノウハウを埋め込まなくて済みます(勿論、この記事にあるのは「例え」なので、その例えが悪かったってことなのですが)。

なので、ストアドを作るならば、100行位にわたる関数を作ったほうが、ストアド自体の本領を発揮できます。複数のクエリと内部変数と制御文を組み合わせて、普通の関数として使えるところがストアドの良いところなので、業務ロジックを含めたいのであれば、そうしたほうがよいでしょう。逆に、オブジェクト指向的なメソッドのまとまりとして作る場合には、ストアドは諦めて C# などのコードのほうにクエリを持ってきたほうがうまく動きます。これは私の経験上なので、それぞれの経験により、別々なのです。逆に言えば、ストアドをクエリから呼び出すのは極力避けたほうが良いでしょう。先の記事にあるように、最終的な SELECT の結果でストアドを使う(条件を絞ったあと)ってことになります。以前、WHERE 句に入れられて性能的にえらい目にあったという…私です。

ちなみに、ストアドは SQL を事前にコンパイルするから何度も使う時に早い、という以前の事情があったのですが、最近ではあまり変わりません。いや、1000行ぐらいのクエリなら意味があるかもしれませんが、最近のクエリ解析のほうでは、以前のクエリを内部で覚えていたりするので動的にクエリを作らない限り以前のクエリ解析のものが使われます。結果的に SQL 文を文字列で送っているのと変わらなくなります。ただし、文字列で構成するときにできるだけ変数を活用するといいです。これは変数以外のところはクエリ文が変わらないという利点があるのと、コマンドラインなどのチェックが楽になります。まあ、SQL インジェクションの問題もありますが、変数自体はプレコンパイルのクエリでもよいし、ストアドにしてもよいでしょう。ストアドにすると、ソース管理自体が DB 内とプログラム内に分離されてしまうので当時から避ける傾向にあるのですが、そこはプログラムコードを書く人の配置によりけりというところです。プレコンパイルのクエリを確認しても、なんらかの API 用のクラスを作ってひとまとめにしておけばソース管理が楽になります。単体テストもやりやすいですからね。

■老婆心ながら、SQL を知らずに O/R マッピングだけを使っていると危険

いま CakePHP を使っているのですが、O/R マッピングには色々なものがあります。.NET でも ADO.NET Entity Framework があるわけで、SQL 文を覚えなくてもプログラム内でこなすことができます。以前は、SQL を書くメリットは高速化にあった(O/R マッピングが遅いという意味で)わけですが、LINQ が内部的に SQL を作成していたり、Web API を使った場合はそもそものボトルネックはネットワーク帯域にあったりするわけで、O/R マッピング自体の速度性能だけが重視される時代は終わった気がします。勿論、内部インフラを高速化する必要がある場合は、なんらかのマッパーやSQLを調節するわけですが、8割以上のデータベース検索は、漠然とした O/R マッピングの機能で十分まかなえます。特に、CakePHP や RoR(ごめん使ったことがない)、Lightswitch などで作られたマスター定義の画面は、実にあっという間にできるので「生産性」が高いことこの上ないです。10年程前は、ちまちまと DB のマスター画面を作っていたのですが、顧客が操作を覚えてくれさえすれば、これらのチープなマスター定義の画面で十分です。逆に言えば、「顧客が操作を覚えてくれなかった」り、これらの O/R マッピングツールの自動生成では追いつけない画面は、なんらかの UI インターフェースを作らないと駄目ってことです。

O/R マッピングの join の仕方は色々あるのですが、やや面倒臭いところがあります。CakePHP の join は簡単な部分しか使ってなくて、面倒なところは query を使ってしまっている私なのですが、なんらかの SELECT を高速に回そうと思うと SQL 文を書いたほうが手早く書けます。SQL というのはそういう言語なのです。なので、O/R マッピングの join に直す場合は、SQL 文を書いた後に O/R マッピングの join を見直す、という手順にすると作業時間が少なくて済みます。このあたりは、相互に行き来できるほうが楽だったりします。よく、Access の ER 図を使って、テーブル間の連結を書いた後に SQL 文を書き出させて、それに WHERE 句を追加する、また Access の ER 図に戻す、という手順で学習をしていました。SQL 文は「文字」で構成されているために手早く書くには向いているのですが、全体を掴もうとすると難しいところがあります。しかし、サブクエリを使う場合には、あらかじめサブクエリを作ってチェックしてから FORM に入れ込むとか、そういう書き方ができます。さらっとした SQL 文であっても、1時間以上も時間が掛かったりします。これは、SQL 文自体に情報量が多いからなんですね。複雑怪奇なことをやる LINQ も似た位の時間が掛かってしまう(いや、複雑怪奇すぎるものは LINQ では無理だったりしますが)のですが、そんなときも SQL に立ち返ると以外とすんなりできたりします。

なので、いろいろな O/R マッピングがあるのですが、SQL 文は一通り覚えておくと作業効率が違いますし、試行錯誤がしやすくなります。なので、特定の O/R マッピング「だけ」を覚えてるのは避けておいたほうがよいでしょう。

■インピーダンスミスマッチの愚考

さて、本題に戻って、データベース(SQL) ?> O/R マッピング ?> オブジェクト指向 の変換を考え直してみます。オブジェクト脳と O/R マッピングが流行ったあたりなので、今となっては「うまく忘れ去れてている」ことを希望しているのですが、その中に出て来た「インピーダンスミスマッチ」という考え方は、あまり意味を持ちません。確かに、ミクロなレベルで、日付型だとか文字列だとか微妙な差異があってデータ変換的にはやらないといけないことなのですが、実は随分「些末なこと」です(と最近私は気づきました)。

例えば、オブジェクト指向的に次のように Person クラスを定義したとしましょう。

class Person {
  public int ID { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
}

このような場合、DB 的には次のようなマッチングを想定している訳です。

create table Person {
  ID int not null,
  Name varchar(256) not null,
  Age int not null, 
  PRIMARY KEY ( ID )
}

素直に DB とオブジェクト思考のマッピングができていますね。ID は更新用にプライマリーキーにあになる必要があります。大抵の「インピーダンスミスマッチ」は、このあたりの要素のレベルの議論になるのですが、実は現実はもっと大雑把/複雑です。

create table Person {
  ID int not null,
  Name varchar(256) not null,
  Birthday datetime, 
  PRIMARY KEY ( ID )
}

データベースのほうは、こんな風に誕生日(Birthday)が設定されているのです。当然ですよね。年齢は年ごとに代わるので年齢(Age)があるよりも、誕生日を入れておく方が永続化のデータベースとしては正しい設計です(勿論、当時の年齢を残すためにAgeを使う場合もありますが)。更に、誕生日が入力されないこともあるので、NULLが許容されています。

これを素直にオブジェクト思考にマッピングするときに、Birthday のプロパティを加えることができるのですが…ここで問題です。オブジェクト思考(プログラムのUI)のほうでは「年齢」が欲しいわけです。確かに、誕生日からいちいち計算することもできるのですが、それを Person クラスで使う側に設定させるのは変な話ですよね。オブジェクト指向のクラスがデータベースに寄りすぎています。

class Person {
  public int ID { get; set; }
  public string Name { get; set; }
  public int Age { 
    get { 
        if ( Birthday == null ) {
            return 0;
        } else {
            int age = DateTime.Now - Birthday.Year ;
            if ( Datetime.Now.AddYears(-age) &lt; Birthday ) {
                age--;
            }
            retrun age;
        }
    }
  public DateTime? Birthday { get; set; }
}

こんな感じに、年齢(age)プロパティを書き換えます。Ageプロパティをnull許容にするかどうかは好みの問題で変えるとして、オブジェクト指向のクラスをもっと UI 層に寄せるならば、こんな書き方ができるはずです。これも「インピーダンスミスマッチ」の一種です。

もちろん、この作り方とは違って、O/R マッピング専用のクラス(DataPersonクラス)を作っておいて、UI で使う場合には Persosn クラスを使う、という方法もあります。大抵はそんな感じになっているのハズなんですが、やたら層ばっかりが多くなるし、DB が変更になったり、相互変換手順が多くなったりして面倒ですよね。バグの温床になりそうです。O/R マッパーを完全自動化するのであれば、DataPerson ?> Person タイプの二重変換が必要ですし、また、「完全に自動化」するのであれば、O/R マッパー自体のコードは手を入れないという方法もあります。実際、Entitiy Framework の場合には、完全自動化されているので手を入れません。別のメソッドを使ってクラスを拡張するし、そのほうがやりやすいでしょう。

なので、オブジェクト指向的なクラスを UI により寄り添う形にした場合は、必ずミスマッチが発生します。というか、発生しなほうがおかしいでしょう?ってな訳で、必ず「相互変換が必要」として考えるのが良いかと思います。逆に言えば、O/R マッピングを使っているから、クラスを自動生成できるし、そのまま UI に使えるから便利、という「直観的な」流れに固執すると危ないかな、ということです。

■しかし、状況によりけりで、O/R マッピングを使い分ける

とはいえ、巷に O/R マッピングのツールはあるし、作業効率上それを使ったほうが手早くできるし、間違いも少ないです。実際、Excel アプリ的なツールであれば、Lightswitch で簡単にできます(逆に言えば、それ以上のことをしようとすると難しいかも…ってことです)。それぞれの適用範囲があるので、それに逸脱しないように道具を使い分けていくのがベターでしょう。

SQL もできる。O/R マッピングツールも持っている。オブジェクト指向で画面を作れる(MVVMのバインドが作れる)という3つの道具が揃った段階で、それぞれの守備範囲をざっと書いておきます。経験則と、その時代のツールの状況次第なので、随時変わるかもしれませんが、現状ではという但し書きで。

  • ひとつのテーブルを扱うCRUD(マスター定義画面)は、O/R マッピングのツールで一気に作る。利用者は、それぞれの O/R マッパーの操作に慣れて頂く。
    → 利用者がIT素人の場合には、UI のみ自作。内部は、O/R マッパーで。
  • 2つ程度のテーブルの join ならば O/R マッピングで解決
    → 3つ以上ならば、SQL 文を書いたほうが無難。
  • テーブル定義とUIの表現が大きく異なる場合は、オブジェクト指向寄りで作成
    → 「データ」と「表現(プレゼンテーション)」の違いになるので、主にプレゼンテーション層を作るオブジェクト指向寄りにクラスを作ると、画面のテストがやりやすい。
    → 1対1のような場合は、O/R マッピングの CURD 画面にしてしまうのも良い。

まあ、ゲーム的のハードなホスティングの場合は別なんでしょうが、中小企業のWEB廻りとか社内の業務アプリなんかは、この方針でやると「手が抜ける/バグが少ない」っていうメリットがあります。

カテゴリー: 開発 | 1件のコメント

独自プロトコルを使って、コマンドラインからストアアプリへ通知する

先日の [WinRT] 独自プロトコルを使ってデスクトップアプリからストアアプリへ通知する – Moonmile Solutions Blog は、デスクトップアプリからプロトコルを通じて、ストアアプリへ通知した訳ですが、そうなると、コマンドラインからも(当然、powershell からも)起動ができますよね、ってことで、試してみました。

image

 C:Usersmasuda> start mm-message:送信する 

なるほど、普通に通知ができます。デスクトップアプリに WinRT を組み込むのは意外と手間なので、逆に、独自プロトコルを作って、ターゲットのストアアプリで処理をするほうが良いかもしれません。この方法を使うと、コマンドラインからストアアプリを起動するのも簡単。コマンドラインを使えるので、perl とか、他のスクリプト系の言語を使って、最終的にストアアプリに引き渡すことも可能です。

カテゴリー: WinRT | 独自プロトコルを使って、コマンドラインからストアアプリへ通知する はコメントを受け付けていません

[WinRT] 独自プロトコルを使ってデスクトップアプリからストアアプリへ通知する

デスクトップアプリからストアアプリに通信をするには、あらかじめストアアプリのほうからデスクトップアプリのほうにソケットを張っておく必要があり…と思っていたのですが、試してみるとストアアプリ間の独自プロトコルはデスクトップアプリとストアアプリ間にも使えるのですね。なるほど。

[WinRT] 独自プロトコルを使ってストアアプリ間で通信する – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/5023

で、作ったストアプリの「mm-message:」というプロコルを使って、デスクトップから通知を出せます。

■普通に Process.Start を使えば OK

実はブラウザを開く時のように普通に Process.Start メソッドが使えます。

private void button1_Click(object sender, EventArgs e)
{
    var text = textBox1.Text ;
    Process.Start("mm-message:" + text);
}

ちなみにストアアプリから通知する場合は、Launcher.LaunchUriAsync を使います。

private void Button_Click(object sender, RoutedEventArgs e)
{
    string text = this.text1.Text;
    var ret = Windows.System.Launcher.LaunchUriAsync(new Uri(
        "mm-message:" + text));
}

Windows 7 以前では独自プロトコルを登録するのは結構な手間だったのですが、Windows 8 の場合は、ストアアプリから登録してやると手軽にできるかも、という例として。このサンプルの場合は、ターゲットのストアアプリを OnActivated でわざとアクティブにしているので、アクティブにしないようにしてトーストだけ出すようにすれば、デスクトップアプリ→ストアアプリ→トースト通知、が手軽にできるかもしれません。デスクトップから直接出す方法は、艦これ諜報員で使っているのですが、意外と手間なのとスタート画面にタイルがデスクとトップアプリのアイコンになってしまいダサいので、ストアアプリとの組み合わせ技でできるならば、それで OK かも。

■サンプルコード

サンプルはこちら
http://sdrv.ms/17KCLxr

カテゴリー: C#, WinRT | [WinRT] 独自プロトコルを使ってデスクトップアプリからストアアプリへ通知する はコメントを受け付けていません

[WPF] 矩形をドラッグしてスイムレーンにスナップさせる

こちらは、仕事用のネタのために公開しておきます。

WPFでコントロールをドラッグ(1) – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/709
[win8] パンダ福笑いを作るために ManipulationDelta を使う – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3877

のところで、ManipulationDelta を WinRT(ストアアプリ)で使ったのですが、改めて WPF で作ってみると、ManipulationDelta 関係はタッチイベントでしか発生せず、マウスイベントでは発生しないのですね…勘違いしていました。ストアアプリの場合は、タッチイベントもマウスイベントも統合されているので、問題ないのですが、WPF の場合はやっぱりマウスの移動量を計算する方式ではないと駄目、ということで。

■デザイン時

デザイン時は、こんな風に Grid を使って描画をしておきます。この3つのスイムレーンに、赤、青、黄の3つのタスクを配置する、というイメージです。左下にある薄い赤の矩形は、ドラッグ時の描画用の矩形ですね。

■ドラッグ時のイベント処理

/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        // ドラッグ用の矩形は隠しておく
        manRect.Visibility = System.Windows.Visibility.Collapsed;
        // マウスイベントを割り付ける
        foreach (var el in grid1.Children)
        {
            var rec = el as Rectangle;
            if (rec != null)
            {
                if (rec.Fill == new SolidColorBrush( Colors.Beige)) continue;
                rec.MouseDown += manRect_MouseDown;
                rec.MouseMove += manRect_MouseMove;
                rec.MouseUp += manRect_MouseUp;
            }
        }
    }

    // ドラッグ時のフラグ
    private bool _isDrag = false;
    private Point _dragOffset;
    private Rectangle _capRect;

    /// <summary>
    /// マウスダウンイベント
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void manRect_MouseDown(object sender, MouseButtonEventArgs e)
    {
        UIElement el = sender as UIElement;
        if (el != null && e.LeftButton == MouseButtonState.Pressed )
        {
            // ドラッグ開始
            _isDrag = true;
            // オブジェクトとのオフセットを取得
            _dragOffset = e.GetPosition(el);
            // キャプチャ開始
            el.CaptureMouse();
            // ドラッグ対象の矩形(タスク)を保存しておく
            _capRect = (Rectangle)el;
            // ドラッグ用の矩形を表示開始
            manRect.Visibility = System.Windows.Visibility.Visible;
            // ドラッグ用の矩形のサイズを設定
            Point pt = Mouse.GetPosition(board);
            manRect.Width = ((Rectangle)el).ActualWidth;    // 描画時のサイズを取得
            manRect.Height = ((Rectangle)el).ActualHeight;
            manRect.Margin = new Thickness(
                pt.X - _dragOffset.X,
                pt.Y - _dragOffset.Y,
                0, 0);
            // ドラッグ対象のタスクを半透明に
            _capRect.Opacity = 0.5;

        }
    }

    /// <summary>
    /// マウス移動時
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void manRect_MouseMove(object sender, MouseEventArgs e)
    {
        if (_isDrag == true)
        {
            Point pt = Mouse.GetPosition(board);
            UIElement el = sender as UIElement;
            manRect.Margin = new Thickness(
                pt.X - _dragOffset.X,
                pt.Y - _dragOffset.Y,
                0, 0);
        }
    }

    /// <summary>
    /// マウスアップ時
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void manRect_MouseUp(object sender, MouseButtonEventArgs e)
    {
        if (_isDrag == true)
        {
            UIElement el = sender as UIElement;
            // マウスキャプチャをリリース
            el.ReleaseMouseCapture();
            // ドラッグ終了
            _isDrag = false;
            manRect.Visibility = System.Windows.Visibility.Collapsed;
            // タスクをスナッピングさせる
            Point pt = Mouse.GetPosition(grid1);
            // 3x3 の位置で特定
            int col = (int)( pt.X * 3 / grid1.Width);
            int row = (int)(pt.Y * 3 / grid1.Height);
            Grid.SetColumn(_capRect, col);
            Grid.SetRow(_capRect, row);
            _capRect.Opacity = 1.0;
        }
    }
}

元の矩形(タスク)をドラッグしても良いのですが、キャンセルや移動時にもともと何処にあったのかを分かりやすくするために、元の矩形は残したままドラッグさせます。なので、ドラッグ用の矩形(manRect)を移動させています。マウスキャプチャ el.CaptureMouse() をすると、元の矩形の位置からはみ出てもマウス移動を追ってくれるので、これを使います。元の矩形を移動させる場合にもマウスキャプチャを使ったほうが、マウスを高速に移動させたときにも追随します。

矩形のサイズを ActualWidth 等で取ってきているのは、スイムレーンを Grid で作って、これに完全にスナップさせているからです。このため、元の矩形は Width/Height ではサイズが取れないので(設定されていないので)、ActualWidth で実際の描画時のサイズを取得します。

■実行してみる

移動中のカラーを揃えていないのと、横長の青い矩形のスナップ判定がいまいちなのですが、ひとまずドラッグは綺麗に動いています。同じことを、ストアアプリでやる場合には、ManipulationDelta を使うとドラッグ中のフラグとかが必要なくなります。

サンプルはこちら http://sdrv.ms/17ZZ5UF

こんな風なタスクの置き換えツールは、マウスでやるよりもタブレット使ってタッチでやるほうが素早くできるので、Excel で PERT 的にタスク管理をするよりも、何らかのタブレットツールがあると計画の立て直しとかが手早くできるのではないかな…と思いつつ数年たっていますが。まあ、今回のタスク配置の画面で使うといとで、メモ的に。

 

カテゴリー: 開発, C# | [WPF] 矩形をドラッグしてスイムレーンにスナップさせる はコメントを受け付けていません

HttpClient を使って建造送信を偽装する…ってのは取り止め

艦これの「建造」をハックする記事は、一度取りやめておきます。
HttpClient の Content-type の設定方法のネタ絡みだったのですが、ちょっとまずいですね。反省しています。

ちなみに運営側の「建造」ハックの対抗策としては(他のコマンドもそうなんですが)、

  • コマンドの通番IDを振る。
  • コマンドと一緒にハッシュ値を送る。
  • Cookieでtokenを隠蔽化する。

などのガードの方法が考えられます。ひとまず、先のコードは Apache ログで一発で引っ掛けられます。

Content-Type のハックは、iPhone サイト等の偽装に変えて、公開しなおしましょう。

カテゴリー: C#, 艦これ | HttpClient を使って建造送信を偽装する…ってのは取り止め はコメントを受け付けていません

[WinRT] 画像を丸く切り抜いて表示する方法

アプリ開発企画 Spotlight スタート! – 高橋 忍のブログ – Site Home – MSDN Blogs
http://blogs.msdn.com/b/shintak/archive/2013/09/03/10445963.aspx

の中に「艦これ」の単語があるので、そのまま戦略ボードを作るのはつまらないので、昨晩つくってみたのがこれです。

戦略ボードとはなんか違う…というか、全く違うものになってしまったのでアレですし、そのままで版権の問題があるからストアには出せない(と思う)ので、テクニック的に晒しておきます。
元画像から円形でクリッピングして、それを覗いてみる、というサーチライトみたな効果ですね。ひとつだけのサーチライトの場合、黒の透過画像を使ってクリッピングするほうが楽なのですが、今回のように複数の円をクリッピングして表示させる場合には、直接クリッピングが必要になります。でも、需要はあるのか?

■WinRTではClipが四角しかできない

画像を丸く切り抜くためには、WPF では EllipseGeometry を使うのですが残念ながらストアアプリの WinRT にはこれがありません。RectangleGeometry という矩形のクリッピングしかできないので、矩形以外のクリップには ImageBrush を使います。

クイック スタート: Image と ImageBrush (Windows)
http://msdn.microsoft.com/ja-jp/library/windows/apps/hh868203.aspx

WFP では、できていたものが、WinRT で使えないのはどうなの?という気もしますが、まあ、描画速度を考えた処置なのでしょう。ちなみに、8.1 でも RectangleGeometry 以外は使えないので、実装漏れというわけではなさそうです。

■円でクリップするためにはFillを使う

ここでは、動的に画像を設定したいので、コードで書いていますが、XAML だけで書くとこんな感じになりあmす。

<Ellipse StrokeThickness="4" Stroke="Green"
         Canvas.Left="60" Canvas.Top="54" Width="200" Height="200">
    <Ellipse.Fill>
        <ImageBrush
            Stretch="None"
            ImageSource="ms-appx:///images/64[8].png">
        </ImageBrush>
    </Ellipse.Fill>
</Ellipse>

Stretch=”None” にしてあるのは、元のサイズのまま表示させるためなのですが、None の時は画像の中心で表示されるのが曲者ですね。これを左上を原点にするために、画像のサイズが必要になります。

■拡大縮小のためにManipulationDeltaを使う

コントロールの移動とサイズ変更には、ManipulationDelta イベントを使います。拡大縮小の場合は、コントロールの中心を原点にして行いたいので、こんな風に x,y 座標を調節する必要あります。Canvas をつかうと計算が多少楽になります。

private void Grid_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var el = e.OriginalSource as UIElement;

    if (e.Delta.Scale == 1.0)
    {
        double x = Canvas.GetLeft(el) + e.Delta.Translation.X;
        double y = Canvas.GetTop(el) + e.Delta.Translation.Y;
        Canvas.SetLeft(el, x);
        Canvas.SetTop(el, y);
    }
    else
    {
        double w = ((Circle)el).Width;
        double h = ((Circle)el).Height;
        double x = Canvas.GetLeft(el);
        double y = Canvas.GetTop(el);
        x -= (e.Delta.Scale-1.0) * w /2.0;
        y -= (e.Delta.Scale-1.0) * h /2.0;
        w *= e.Delta.Scale;
        h *= e.Delta.Scale;
        Canvas.SetLeft(el, x);
        Canvas.SetTop(el, y);
        ((Circle)el).Width = w;
        ((Circle)el).Height = h;
    }
}

■画像の位置合わせをするためにTranslateTransformを使う

移動させるためのコントロールは、ユーザーコントロールとして作っておきます。
参照させたいところ(TranslateTransform の X,Y の座標)は、あらかじめ名前を付けておきます。TranslateTransform というのは、先の ImageBrush で描画するときの原点を設定する方法です。

    x:Class="SearchLight.Circle"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SearchLight"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="200"
    d:DesignWidth="200"
    ManipulationDelta="UserControl_ManipulationDelta" SizeChanged="UserControl_SizeChanged"
    >
    <Grid>
        <Ellipse StrokeThickness="4" Stroke="Red" >
            <Ellipse.Fill>
                <ImageBrush
                    x:Name="ib"
                    Stretch="None">
                    <ImageBrush.Transform>
                        <TranslateTransform
                            x:Name="tr"
                            X="0" Y="0"></TranslateTransform>
                    </ImageBrush.Transform>
                </ImageBrush>
            </Ellipse.Fill>
        </Ellipse>
    </Grid>
</UserControl>

そんな訳で、コントロールを移動しても、元の画像を移動せずにクリッピングする、というイベントが作れます。原点からのコントロールの位置は、TransformPoint を使って取っていますが、実際は画像の原点から合わせたほうがよいので、もうちょっと違う書き方が必要かも。

private void UserControl_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var pt = this.TransformToVisual(null).TransformPoint(new Point(0, 0));
    marX = (_imageSize.Width - this.Width) / 2.0;
    marY = (_imageSize.Height - this.Height) / 2.0;
    tr.X = -pt.X + marX;
    tr.Y = -pt.Y + marY;
}

そんな訳で、戦略ボードのようにコントロールを追加して、円を移動させたり、拡大縮小させたりすることができたのですが、移動させているときに画像がずれるのが気に食わないですよね。ええ、このあたりは、ClipとImageBrushによるFillの違いかと。たぶん、WPFでClipを使ったほうがスムースに動くと思います。

20130915_03

■サンプルコード

サンプルコードは、以下からどうぞ。
http://sdrv.ms/1eAP7yp

 

カテゴリー: 開発, C#, WinRT | [WinRT] 画像を丸く切り抜いて表示する方法 はコメントを受け付けていません

艦これ 諜報員 ver.0.5

SWFファイルから画像を抽出する – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/5136

の機能を組み込んで、諜報員の艦娘を秘書艦に合わせます。

image

途中で立ち上げると、うまく切り替わらないので、あからじめ諜報員を立ち上げて置くか、艦これのほうで秘書艦を変えてみてください。このあたりは、のちのち修正しましょう。秘書官を立ち上げたときに、前の艦娘を表示したほうがよさそうだし。

バイナリのほうは 艦これ 諜報員 – Moonmile Solutions Blog からダウンロードしてください。

■Fiddler のフック時に HttpClinet は使えない。

考えてみれば、当たり前なのですが、Fiddler でフックしている中で HttpClient などを呼び出すと、元の Fiddler にフックしてしまうために再入が発生します。FiddlerCore の中身は static のものが多いらしく、なんか url などが途中で消えてしまったうまく動きませんでした。

なので、どうせならば、Task を使って遅延させれば OK。

void UpdateKanmusu(string url , int shipId)
{
    // TODO: WebClient も Fiddler にフックされるので再入を防ぐために非同期にすること。
    // swf をダウンロード
    Task.Factory.StartNew(async() =>
    {
        var cl = new HttpClient();
        var data = await cl.GetByteArrayAsync(new Uri(url));

        // swf から画像を取り出す
        var reader = new SwfReader(data);
        var swf = new SwfCompilationUnit(reader);
        var lst = swf.Tags.FindAll(x =>
            x.TagType == TagType.DefineBitsJPEG3 &&
            ((DefineBitsTag)x).CharacterId == 13);
        var tag = lst[0] as DefineBitsTag;
        Bitmap bmp = tag.ToBitmap();
        // 描画
        UpdateForm(bmp);
    });
}

実は、Fiddler でフック中で重たい処理をすると、ブラウザのほうのレスポンスが悪くなります。なので、処理をしないメッセージであればできるだけ早く解放してやるのがベターです。

そんな訳でサンプルソースは http://sdrv.ms/18ZCDbe から。

カテゴリー: C#, 艦これ | 1件のコメント

Windows 8.1 RTM に Media Center Pack を引き継ぐ

Windows 8 Media Center Pack を2台目に入れるときの注意点 – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/5009

ビデオの閲覧とTVの閲覧のためにある、うちの「Media Center Pack」なのですが、無事、Windows 8 から Windows 8.1 RTM に引き継げました、という話です。

前回の Media Center Pack の購入で変なことになってしまった、家庭用PCなのですが、Windows Update をすると OS ごと立ち上がらなくなってしまう状態でした。そのうち、8.1 が出るだろうから、それにアップデートしてから考えよう、ってことで、放置状態だったのですが、本日 8.1 RTM を入れてみました。

■Win 8 から 8.1 にアップグレードすると、一時的に Media Center が使えなくなる

最初、Windows 8 から Windows 8.1 にアップデートした後に、Media Center が使えなくなります。あたかもアンインストールした状態になってしまい、録画一覧フォルダにあるアイコンも「不明アイコン」のままになります。どうやら、「with Media Center」の部分がアップグレードした時に消えてしまうんですよね。

こうなると録画した番組も見れなくなるし、ええー???と思って、元に戻そうかと色々考えた挙句…

■購入済みの Media Center Pack の認証IDを入れる

仕方がないので、購入済みの認証IDを入れ直します。Windows 8 の時に買った Media Center Pack は 8.1 でも有効なのかどうかが不明なのと、いやいや以前の話だと PC 1台に付きひとつという紐づけだそうだから、Windows 8 の時に購入した Media Center Pack は、同じ PC であれば Windows 8.1 でも有効ではないか…ということで。

引っ張り出してきた認証 ID を入れると、最初は「認証に失敗しました」というエラーがでるのですが、結果的には認証できました。

image

無事「Windows 8.1 Pro with Media Center」になっていますし、以前に録画していた映像も見れるようになります。

途中エラーのようなものが出るのは、うちのPCのせいなのか、RTM の不具合なのかわからないのですが、ひとまず。

■ちなみに、8.1 から Media center Pack を買おうとすると…

こんな感じにエラーになります。なので、実質、8.1 RTM から Media Center は購入できません。これは不具合なのか、MS のサーバーがまだ準備をしていないのかは不明です。多分、準備ができていないだけのように思えるけど。

image

カテゴリー: トラブルシューティング | Windows 8.1 RTM に Media Center Pack を引き継ぐ はコメントを受け付けていません

[Win8.1] Windows 8.1インストール時にローカルアカウントを利用する方法

急遽、Windows 8.1 RTM が MSDNで配布されることになったので、早速「仕事として」チェック。同時に Visual Studio 2013 RC も使えるのですが、これは、

  • Windows 8.1 RTM + Visual Studio 2013 RC で、ストアアプリ開発ができる。
  • Windows 8 + Visual Studio 2013 RC で、ストアアプリ開発以外はできる。
  • Windows 8.1 Preview に Visual Studio 2013 RC はインストールできない。

という違いがあります。8.1 のストアアプリを試したい場合には、Windows 8.1 RTM が必須になるので必然的に MSDN/TeckNet の加入が必須になりますね(正確には、ベンダーへの RTM も含むけど)。 逆に、デスクトップアプリとか、WPFアプリとか、ASP.NET MVC をやりたい場合には、Windows 8 と Visual Studio 2013 RC の組み合わせでも OK な訳です。

■Windows 8 から Windows 8.1 へのアップデート

Windows 8.1 Preview からのアップデートではアプリの引き継ぎはできませんが、Windows 8 からのアップデートではアプリの引き継ぎができます。大抵は使い込んでいるPCだと思うので、「Windows の設定、個人用ファイル、アプリを引き継ぐ」を選択すれば OK です。

image

「何も引き継がない」場合は、どうなるんでしょうね?元のデータ自体は、Windows.old というフォルダにバックアップが取られるので、マイドキュメントの中とか、Program Files の中身は後からファイルを拾うことができます。多分、ディスクの容量があれば、このあたりは問題ないでしょう。マイドキュメント以外は、そのままなので、Dドライブとかにファイルを置いてあれば、そのままで。

■Microsoft アカウントを使わず、ローカルアカウントを使う

会社でActive Directoryを使っていたり、複数のPCをうまく共有したい場合には、インストールの指示に従って「Microsoft アカウント」を作ればいいのですが、個人で使っている場合には「ローカルアカウント」のままにしておきたい場合があります。私の場合、VMWare を使って色々なバージョンを作るのと、共有は SkyDrive ぐらいで十分なので、ローカルアカウント(そのPCだけで有効なアカウント)を使っています。リモートデスクトップでつなげる時が多いので、ユーザー名、パスワードも同じにしてあるというダダ漏れ状態なのですが…まあ、家庭内LANだし。逆に言えば、出先で不意な同期が走らないので安心というのもありますね。

手元のPCをどれでも同じように使いたい場合は、Microsoft アカウントを使う。個別に用途ごとに使いたい場合は、ローカルアカウントを使う、という区別です。

推奨される Microsoft アカウントですが、アップデートの時に利用するように強制的に勧められます。いままでの PC ではローカルアカウント(Windows 7も含めて)で使っていたので、いままでどおりローカルアカウント(ログイン名とパスワード)を使いたい場合、

image

この画面で、一度「Microsoft アカウント」をわざと失敗させます。例えば、ユーザー名を「xxxxx@xxx.com」のような感じで入力し、パスワードを適当に入れます。そうすると、一度 Microsoft アカウントをチェックしようとしますが、当然ならがアカウントがないのでエラーになって戻ってきます。

image

ここで、推奨してないけど、ローカルアカウントを使いますか?のボタン(「既存のアカウントの使用」ボタン)が出てくるのでクリックすると、もともと使っていたローカルアカウントを使うことができます。Windows 8.1 RTM をクリーンインストールした場合は、パスワードの脇に「ローカルアカウントを使いますか?」のようなリンクが出てくるので、これをクリックします。すると、由緒正しいローカルアカウントの登録画面になります(そういえば、デフォルトの Administrator ユーザーはなくなったんですかね?)

ちなみに、インストールしてからローカルアカウントを作成することもできます。チャームの検索で、「アカウントの作成」を設定から検索すれば、コントロールパネルが開きます。

image

PC の最初のアカウントが、Microsoft アカウント(ネットワーク上のアカウント)というのが、ちょっと難点があるかなと。最初のアカウントは必然的に管理者権限(Administrators)になってしまうので、元の Microsoft アカウントが乗っ取られてしまうと、芋づる式に関連する PC のアカウントが乗っ取られてしまいそうな気が。どうなんでしょう?

なので、最初のアカウントはローカルの捨てアカウントにしておいて、Microsoft カウントはちょっと制限のあるアカウントにするのがベターかなと。まあ、会社の場合は、Active Directory を使うので、逆にローカルアカウントは一切作らずに、AD の管理者権限を使って強制的に、ってのがセオリーですが。

カテゴリー: windows 8.1 | 1件のコメント

SWFファイルから画像を抽出する

Flashのswfファイルから画像を抽出する…わけは艦これ諜報員用なのですが、完全に実装する前にメモ程度に残しておきます。いくつか、MFCのライブラリとかJava,PHPのライブラリはあるのですが、やっぱりC#のほうがいいですよね。

neue cc – C#でFlash Liteなswfをバイナリ編集して置換する
http://neue.cc/2013/01/10_393.html
debreuil/Swf2XNA
https://github.com/debreuil/Swf2XNA
SWFバイナリ編集のススメ第三回 (JPEG) | GREE Engineers’ Blog
http://labs.gree.jp/blog/2010/09/782/

github に Swf2XNA というプロジェクトがあります。neue さん言う通り、完全に使うには XNA のライブラリが必要なのですが、そこまで面倒ではなくて SwfFormat プロジェクトだけが必要です。

※一応、但し書きを書いておきますが、画像の著作権は艦これ自体に帰属するので、悪用しないように。

■手っ取り早く画像ファイルを抽出したい

用途としては、艦これでダウンロードしている *.swf ファイルから画像を抜き出したいだけです。画像の取り出し自体は HugFlash – 窓の杜ライブラリ  というツールがあるので、特に問題はありません。艦これ諜報員の榛名の画像は、これを使って取り出したものですが。が、どうせならば実行時に画像を取り出したいですよね。そうすると、秘書艦を変えたときに、同期して諜報員の画像も変えることがでいそうです。

さて、先の記事を読むと Flash の中身は Tag に分かれています。この中で画像に関係するところの

  • TagType.DefineBits
  • TagType.DefineBitsJPEG2
  • TagType.DefineBitsJPEG3

の3つを取り出せばよさそうです。実は、TagType.DefineBits のときの JPEGTables+DefineBits の組み合わせは真面目にやらないといけないのですが、幸いにして(?)、艦これで扱っている swf は JPEGTables の長さが 0 のために、そのまま DefineBits 内のバイナリデータが使えます。これでいいのかどうかは不明なのですが、まあ、用途に足りるので。

private void button1_Click(object sender, EventArgs e)
{
    var reader = new SwfReader(File.ReadAllBytes(@"d:work14.swf"));
    var swf = new SwfCompilationUnit(reader);
    var lst = swf.Tags.FindAll(x =>
        x.TagType == TagType.DefineBits ||
        x.TagType == TagType.DefineBitsJPEG3 ||
        x.TagType == TagType.DefineBitsJPEG2);
    int i = 1;
    foreach (var it in lst)
    {
        var tag = it as DefineBitsTag;
        var path = string.Format(@"d:work14-{0}.png",tag.CharacterId);
        Bitmap bmp = tag.ToBitmap();
        if (bmp != null)
        {
            bmp.Save(path, ImageFormat.Png);
        }
        i++;
    }
}

File.ReadAllBytes メソッドで byte[] として取り出しデータを SwfReader を喰わせた後、SwfCompilationUnit クラスでパースします。そのあと、Tags コレクションの中身を覗けばOKです。
DefineBitsTag クラスの ToBitmap メソッドは自作の拡張メソッドです。

public static class DefineBitsTagExtention
{
    public static Bitmap ToBitmap(this DefineBitsTag tag)
    {
        Bitmap png = null;
        if (tag.TagType == TagType.DefineBitsJPEG2 ||
            tag.TagType == TagType.DefineBitsJPEG3)
        {
            if (tag.HasAlphaData == false)
            {
                var mem = new MemoryStream(tag.JpegData);
                png = new Bitmap(mem);
                //mem.Close();
            }
            else
            {
                var mem = new MemoryStream(tag.JpegData);
                Bitmap bmp = new Bitmap(mem);
                png = new Bitmap(bmp.Width, bmp.Height);
                Graphics g = Graphics.FromImage(png);
                g.DrawImage(bmp, 0, 0);
                var alpha = SwfReader.Decompress(tag.CompressedAlphaData,
					(uint)(bmp.Width * bmp.Height));
                int j = 0;
                for (int y = 0; y < bmp.Height; y++)
                {
                    for (int x = 0; x < bmp.Width; x++)
                    {
                        int a = alpha[j];
                        var c = bmp.GetPixel(x, y);
                        var col = Color.FromArgb(a, c);
                        png.SetPixel(x, y, col);
                        j++;
                    }
                }
            }
        }
        else if (tag.TagType == TagType.DefineBits)
        {
            int size = tag.JpegData.Count() + 4;
            byte[] buf = new byte[size];

            for (int i = 0; i < tag.JpegData.Count(); i++)
            {
                buf[i + 2] = tag.JpegData[i];
            }
            buf[0] = 0xFF;
            buf[1] = 0xD8;
            buf[2+tag.JpegData.Count()] = 0xFF;
            buf[2+tag.JpegData.Count()+1] = 0xD9;
            var mem = new MemoryStream(buf);
            png = new Bitmap(mem);
            // mem.Close();
        }
        return png;
    }
}

DefineBitsJPEG2 と DefineBitsJPEG3 の場合は、画像ファイルそのもの(jpeg,png,gif)が JpegData 配列に入っているので、Bitmap に落とせます。アルファチャンネルがある場合は、HasAlphaData をチェックして透過データを追加してやります。いちいち GetPixel/SetPixel とやるとスピードが遅いので、適当に unsafe してバイナリを直接扱うとかすると良いでしょう。実際 SwfFormat の中ではそういうコードがあります。

JPEGTables + DefineBits の組み合わせのときは、本当はきちんとやらないと駄目なのですが、そもそもが JPEGTables のサイズが 0 なので、ここでは決め打ちで書いています。前後に SOI と EOI を追加してやれば JPENG として読み込みます。

こんな風に全ての画像ファイルを抽出できます…と思ったのですが、HugFlash の出力と見比べると、ひとつだけファイルが足りないんですよね。どうやら、TagType.DefineBitsLossless か TagType.DefineBitsLossless2 に入っている画像データを忘れていた模様。これはちょっと後で。

■秘書艦の切り替えメモ

こんな訳で、リアルタイムに画像抽出ができそうです。ちなみに SwfFormat を使うと「画像改変」もできるので、Faddler と組み合わせれば、あんな画像やこんな画像ですり替えることは可能ですね。ただし、Faddler 自体がプロキシなので差し替えは可能だし、しかし、304 でローカルキャッシュから持ってきている問題もあるので簡単には差し替えできないという要因もありますが。

ひとまず、13番と15番決め打ちで画像を取り出して、諜報員の画像を切りかえることは可能そうです。

カテゴリー: 開発, C# | SWFファイルから画像を抽出する はコメントを受け付けていません