KatsuYuzuのブログ

.NET の開発をメインとした日記です。

SilverlightのTheme適用について その3「テーマを選択して切り替える」

はじめに

前回は1つのThemeをアプリケーションの開始時に設定していました。今回は複数のThemeの中から1つをユーザーに選択してもらいます。また、それだけでは前回のコードを利用して簡単にできるので、今回はMVVMパターンを適用してみたいと思います。
今回のサンプルプロジェクトは記事の最後に置いてあります。説明のコメントを書き込んでいますのでプロジェクトだけでもご覧いただければと思います。

MVVMって何?って方はMVVM星人の尾上さんの資料をご覧ください。

全体像

プロジェクトの全体像は下記のようになっています。

名前空間を上から順に……

  • Infrastructures

インフラです。本来はLivetやMVVM Light Toolkit、Prismなどのライブラリを使うと楽だと思います。
(Livet、Prismはソースコードも見れるのでとても勉強になります。ちなみに、Toolkitもソースコード見れますよ!)

  • Models

MVVMのM(Model)にあたる層です。Themeのコレクションの生成や切り替えなど、ロジック部分を担当しています。

  • Themes

前回と同様にThemeのXAMLを格納しています。また、各Themeの名前を持った列挙型をintで定義しています。

  • ViewModels

MVVMのVM(ViewModel)にあたる層です。ViewとModelの橋渡しを行います。意識するのはインプットとアウトプットです。

  • Views

MVVMのV(View)にあたる層です。XAMLで画面を構成します。が、プロジェクトにありません。MainPageを移動するの忘れてました……orz

Theme列挙型からコレクションを生成

Themesに定義した列挙型からコレクションを生成します。Silverlightの軽量フレームワークにはEnum.GetValuesがなかったので、ReflectionとLinqを利用しました。

        private IEnumerable<Theme> CreateThemes()
        {
            return typeof(Themes.ThemeType).GetMembers(BindingFlags.Public | BindingFlags.Static)
                .Select(m =>
                {
                    Themes.ThemeType value;
                    Enum.TryParse<Themes.ThemeType>(m.Name, out value);
                    return value;
                })
                .Where(t => Enum.IsDefined(typeof(Themes.ThemeType), t))
                .Select(t => new Theme() { Name = t.ToString(), Value = t });
        }

コレクションをViewにバインド

先ほど生成したコレクションはModelで作られています。なので、それをViewModelがViewへ橋渡しします。変化を捕捉してViewの状態として保持していきます。

            // Model の 変化を捕捉
            _model.ThemeCollection.CollectionChanged += SyncToCollectionChanged;
        public void SyncToCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                var themeModel = e.NewItems[0] as Models.Theme;
                if (themeModel != null)
                {
                    // UI スレッド以外から UI の変化が発生すると例外になります。
                    // 今回は非同期操作がないので問題ありませんが、Collection の操作は非同期で行われることが多々ありますので注意してください。
                    // 注意とは、具体的には System.Windows.Threading.Dispatcher を使います。
                    ThemeCollection.Add(new ThemeViewModel() { Name = themeModel.Name, Value = themeModel.Value });
                }
            }
        }

次にViewにListBoxを配置してItemsSourceにバインドします。また、選択したアイテムがPageViewModelのプロパティに流れるようにSelectedItemをTwoWayモードでバインドします。

<ListBox DisplayMemberPath="Name" ItemsSource="{Binding ThemeCollection, Mode=OneWay}" SelectedItem="{Binding SelectedTheme, Mode=TwoWay}"/>

最後にModelへインプットしてあげると、Modelが切り替えロジックを実行します。

        private ThemeViewModel _selectedTheme;
        public ThemeViewModel SelectedTheme
        {
            get
            {
                return _selectedTheme;
            }
            set
            {
                _selectedTheme = value;
                _model.SetApplicationTheme(value.Value);
            }
        }


SetApplicationThemeでは前回と同様にToolkitのThemeクラスのSetApplicationThemeUriを行っています。ちなみに、SetApplicationThemeUriではApp.Current.ResourcesのMergedDictionariesにテーマを追加することでテーマを切り替えています。そのとき、追加したテーマのインスタンスを保持しておき、新しいテーマを設定されたときに前回のテーマを削除しています。

まとめ

MVVMしてみたり、テーマ切り替えてみたり、ラジバンダリ!僕は考え方が甘くてVMが厚くなりすぎてVVMになっちゃうことが多いです……ただ、ソースコードを変えずに画面(View)変更の要求を耐え切ったりした経験があるので、MVVMはやめられないですね。

次回

未定です。このサンプルでいくならば、分離ストレージにテーマを保存して次回起動時に自動でテーマを適用とかすると面白いかなぁ。