ページ

2011年5月30日月曜日

[WP7] ScrollViewer のスクロールに反応する

Windows Phone 7 の ScrollViewer (確認してませんが、Silverlight や WPF でも同じかも) にはスクロールしたことを伝えるイベントがありません。
けど、スクロール量を表す HorizontalOffset、VerticalOffset プロパテイがあります。
ならば、これらにデータバインディングしてやればスクロールしたときにあわせて処理をできるんじゃないかと。

public static readonly DependencyProperty MyVerticalOffsetProperty =
    DependencyProperty.Register("MyVerticalOffset", typeof(double), typeof(MyView), new PropertyMetadata(0.0, MyVerticalOffset_PropertyChanged));

private static void MyVerticalOffset_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    // ScrollViewer.VerticalOffset が変わったときにここが呼ばれる
}

こんなのを用意しておいて、あとは

XAML で書くなら
<ScrollViewer VerticalOffset="{Binding MyVerticalOffset, Mode=TwoWay}" />

コードで書くなら
var binding = new Binding("MyVerticalOffset") { Source = this, Mode = BindingMode.TwoWay };
this.ScrollViewer.SetBinding(ScrollViewer.VerticalOffsetProperty, binding);

こんな感じでいいのかなぁ?と。

がっ!これはできないんですね。
ScrollViewer.VerticalOffset プロパティなんかは readonly なので、実行時に例外が発生してしまいます。
BindingMode.OneWayToSource にしてやればよさそうにも思えますが、Silverlight には OneWayToSource がありません。
というわけで、どうもこのアプローチはダメみたいです。

しかし、添付プロパテイであれば相手が readonly でも添付できるようです。

private static int verticalOffsetAttachedPropertyCounter = 0;

// コンストラクタ
public MyView()
{
    InitializeComponent();

    // ScrollViewer.VerticalOffset に添付プロパティをアタッチして変化を検出する
    var property = DependencyProperty.RegisterAttached("MyVerticalOffset" + verticalOffsetAttachedPropertyCounter, typeof(double), typeof(MyView), new PropertyMetadata(0.0, MyVerticalOffset_PropertyChanged));
    ++verticalOffsetAttachedPropertyCounter;
    var binding = new Binding("VerticalOffset") { Source = this.ScrollViewer };
    this.ScrollViewer.SetBinding(property, binding);
}

private void MyVerticalOffset_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    // ScrollViewer.VerticalOffset が変わったときにここが呼ばれる
}

こんな感じでうまくいきました。
上記ではインスタンス上で RegisterAttached してますが、もちろん、普段の依存関係プロパティのように static に RegisterAtached しても構いません。
その場合、当然 MyVerticalOffset_PropertyChanged() メソッドも static にすることになります。
今回、static ではなくインスタンスでやっているのは MyVerticalOffset_PropertyChanged() メソッドに渡される DependencyObject が添付先(今回の場合だと ScrollViewer)になっちゃうからです。依存関係プロパティのときはここに自分が渡されるのでキャストすれば自分のインスタンス変数にアクセスできますが、添付プロパティだと自分にアクセスするすべがなくなっちゃいます。そんなわけで static ではなくインスタンスにしています。

ただ、これ、インスタンスが作られるたびに RegisterAttached してるわけですが、いいのかな?まぁ、UnregisterAttached みたいな解除するための API が無いのでどうしようも無いんですが。

2011年5月28日土曜日

[Silverlight] 明日(いや、もう今日か)、「Silverlight を囲む会 in 大阪 #18」 に参加してきます

明日(いや、もう今日か)、 Silverlight を囲む会 in 大阪#18 に参加してきます。
テーマは LightSwitch だそうです。
Visual Studio 風のデザイナーで、Access みたいにちょこちょこと作れちゃう、しかも作ったものは Silverlight アプリに自動生成されちゃう、という 「ちょっとしたビジネスアプリはこれで作ればあっという間にできちゃうんじゃね?」 というものらしいです。いや、私自身はまったく触ってないので、間違ってるかもしれませんが(笑)

余裕のある会場みたいなので当日受付も OK みたいですよ。
(行ってみようという方は上記ページにある主催者連絡先メールなり Twitter なりでコンタクトを取ってみてください)

2011年5月27日金曜日

[Silverlight] OOB のブラウザーコントロールでローカルファイルをブラウズできない?

Silverlight のブラウザー外実行 (Out Of Browser) では WebBrowser コントロールが使えますが、これってどうやらローカルにあるファイルは表示できないんですね。

Silverlight 自身は OOB のときは分離ストレージ (IsolatedStorage)、「昇格された信頼」 な OOB のときはそれに加えて MyDocuments、MyMusic、MyPictures、MyVideos フォルダーにアクセスできます。なので WebBrowser コントロールも少なくともそれらの場所にあるファイルにはアクセス出来るもんだと思ってました。
しかし、”file:///c:/.../index.htm” とか “c:\...\index.htm” とかいろいろ試してみましたがどうやってもアクセスできません。

うーむ、これってできないんですかねぇ?

検索してみると、ファイルを String に読み込んで WebBrowser.NavigateToString() で表示することなら出来る、という記事はみつかるんですが、やっぱりみんな普通には表示できてないみたいな感じ。
完全にオフラインで動かすために、最初に HTML やらなんやらの必要なものをローカルにコピーしておいて以後はそれを参照する、ってな需要ってあると思うんだけどなぁ。(というか、それをやろうとして出来ないことに気付いたんだけど)

Silverlight 5 で何か変わってるんだろうか?(まだ 5 のノーチェック)

2011年5月25日水曜日

[WP7] ListBox の SelectionChanged イベントって、、、選択項目が変わらないと発生しないのね。いや、そりゃそうなのはわかるけど

Windows Phone 7 で ListBox で項目を選択すると、それに対応するページに遷移する。こういう動きってよくありますよね?
で、戻るボタンで戻ってくる。
そして、同じ項目をクリックする。。。と、うんともすんとも言いません。ListBox の SelectionChanged イベントが発生して欲しいんですが発生しません。

なぜ?と思いつつ検索してみるとすぐ見つかりました。
Reselect same item in listbox - did not fire "SelectionChanged" event
Microsoft の Peter Torr さんから 「選択項目が変わってないんだから SelectionChanged イベントは発生しないよ」
えぇぇぇぇぇ。いや、そりゃ確かにそうなんですが。。。
で、「SelectionChanged イベントが来たときに SelectedIndex に –1 を入れれば次もイベントが発生するよ」

なるほどぉ

私はこれで解決できました。
スレでは 「-1 入れるとハイライト表示が消えちゃう。選択されていることを示すハイライトを出したいときはどうすりゃいい?」 と言う感じでもうちょい続いてます。これといった結論は無いような感じですが。
まじめにやるなら、ListBox の DataTemplete に Button を仕込めばいいんじゃないかな?試してないからうまくいくかわからんけど。
ちょっと強引な方法としては、SelectedIndex = –1 にしたあと、Dispatcher.BeginInvoke で元の SelectedIndex に戻しちゃうとか?(この場合、たぶん SelectionChanged イベントが来ちゃうからそれは無視するとか結構強引な感じになると思うけど)

スレの最後に 「ListBox にも Click イベントがあればいいんじゃね?」 とかあるけど、確かにそうだな。

[WP7] LongListSelector でちょっとはまった

Silverlight for Windows Phone Toolkit に入っている LongListSelector はお手軽にグループ化したリストが作れて便利ですが、ちょっとはまったので記録として。

もっとも単純な形だと、

public class Book
{
    public string Category { get; set; }
    public string Name { get; set; }
}

こんなクラスがあったとすると、

var books = new Book[] {
    new Book() { Category = "Cat1", Name = "Book1" },
    new Book() { Category = "Cat1", Name = "Book2" },
    new Book() { Category = "Cat2", Name = "Book3" },
};

this.LongListSelector1.ItemsSource = from book in books
                                     group book by book.Category into c
                                     select c;

こんな風にすればグループ化して表示できます。

と思ってたんですが、これじゃダメなんですね。これだとグループヘッダーのところに何も表示されません。
この場合、ItemsSource には System.Linq.IGouping<TKey, TElement> のコレクションを渡していて、Key がグループヘッダー、グループの中身は GetEnumerator() で取得することになります。実際に group by で返ってくるのは IGrouping を実装した System.Linq.Lookup.Grouping クラス(リファレンスには載ってない)なんですが、どうやらこれの Key プロパティとうまくデータバインドできない様子。

Toolkit のサンプルを見ると

public class PublicGrouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    private readonly IGrouping<TKey, TElement> _internalGrouping;

    public PublicGrouping(IGrouping<TKey, TElement> internalGrouping)
    {
        _internalGrouping = internalGrouping;
    }

    public override bool Equals(object obj)
    {
        PublicGrouping<TKey, TElement> that = obj as PublicGrouping<TKey, TElement>;

        return (that != null) && (this.Key.Equals(that.Key));
    }

    public override int GetHashCode()
    {
        return Key.GetHashCode();
    }

    #region IGrouping<TKey,TElement> Members

    public TKey Key
    {
        get { return _internalGrouping.Key; }
    }

    #endregion

    #region IEnumerable<TElement> Members

    public IEnumerator<TElement> GetEnumerator()
    {
        return _internalGrouping.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _internalGrouping.GetEnumerator();
    }

    #endregion
}

こんなクラスを自前で用意して

this.LongListSelector1.ItemsSource = from book in books
                                     group book by book.Category into c
                                     select new PublicGrouping<string, Book>(c);

と言う風に包んであげています。
なぜそんな事に?

var q = from book in books
        group book by book.Category into c
        select c;
var group = q.First();
var key = group.Key;                                    // ちゃんと Key の内容を取り出せる
var propertyInfo = group.GetType().GetProperty("Key");    // null が返る!

こんな風に試してみました。
System.Linq.Lookup.Grouping クラスは DependectyObject ではありませんから、LongListSelector は内部でリフレクションを使ってデータバインドしてると思います。なので、GetProperty(“Key”) で PropertyInfo が取れないとバインドしようが無いと思いますが、実際に試してみると null が返ってきちゃいます。どうやら Grouping クラスは internal なのでリフレクションでアクセスできない様子。
ちなみに、まったく同じコードを .NET Framework 4 のコンソールアプリケーションと Silverlight 4 で試すとまったく問題なく PropertyInfo が取得できます。Silverlight と WP7 は null になるのかと思ったら、WP7 だけが動作が違うんですね。

分かってしまえばどうということは無いんですけど、ちょっとはまりました。
(というか、最初から public に Grouping クラスを用意しておいてくれたらよかったのに)

2011年5月24日火曜日

[WP7] App Hub のアカウントを作ってみた

HTC 7 Trophy を入手したので App Hub アカウントを作ってみました。
(会社の法人アカウントを作ってみようかと思ってたんですが、とりあえず個人にしました)

最近、公式で手順書が公開されたそうですが、私は今のところ特に問題なく進めることができました。

公式の手順書はこちら
App Hub のアカウント作成手順と注意事項

一部、手順書とは違う(メールが日本語だったり)ところもあるので私の場合を書いておきます。

  1. App Hub でアカウントを作成。私は以前から使っていた Live ID で登録。聞いたところによると、Live ID 作成時の国が「日本」になってないとダメとか、誕生日が 18才以上でないとダメとか、注意点があるそうです(あとから変更してもダメで作成時の情報がチェックされるらしい?)。Live ID 作成時のことなんて覚えてませんでしたが、私の場合は特に問題なく作成できました。
  2. ちょっとしたら App Hub から確認のメールが来ました。これはリンクをクリックしてメールアドレスの確認をするだけ。
  3. それから 1日半後くらいの土曜日の早朝に GeoTrust より「Windows Marketplace for Mobile ID XXXXXX」のメールが到着。私のところには日本語のメールが来ました。身分証明書確認のシートもちゃんと日本語です。

    確認シートの部分をプリントアウト
    ・身分証明書には運転免許証のコピーを使用
    ・「ID 番号」欄には運転免許証の番号を記入。
    ・「有効期限」欄には運転免許証の期限を「201X年X月X日」形式で記入。(文面が日本語になってるんだから日本語表記にしてみた)
    ・「発行場所」欄には「日本」と記入。
    ・「署名」欄には氏名、「日付」欄には当日の日付を「2011年X月X日」形式で記入。
    (以上手書き)

    これをスキャンしてメールに添付して送信。送ったのは GeoTrust からメールが来てから数時間後の土曜日の昼くらいです。
  4. 月曜日の昼に GeoTrust より認証手続き完了のメールが来ました。これもちゃんと日本語です。(締めの言葉は Thank you でしたがw)
  5. その翌日、Microsoft から「Windows Marketplace account notification」のメールが来て無事 App Hub アカウントが作れたようです。

結局、6日間でアカウントの作成はできました。途中に土日を挟まなければもっと早くできるのかもしれませんね。

さっそく、HTC 7 Trophy を繋いで Windows Phone Developer Registration を起動。
App Hub アカウントで Register すると無事登録されました。
Visual Studio から実機に繋いでのデバッグも問題なし!

ところで、今のところ有料アプリを作る予定はないんだけど、EIN の取得と W-8BEN の申請はしておいたほうがいいのかな?