かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

画面遷移のための仕掛け

Navigation Frameworkという便利なものがあるけれど、URIで指定するしか画面の指定方法がない上に、動的に読み込まれたDLLやXAPファイルに含まれるクラスへの画面遷移がことごとく失敗してしまうために、何かしら自分で準備したほうがいいと思う。
Navigation Frameworkで素敵なのは、SEO対策がされてることだけど、業務アプリではむしろ邪魔なような気がする。

とりあえずこんなの作ってみた。

まずは、画面上の遷移対象となる領域を表すインターフェースとクラス。

using System.Windows.Controls;

namespace SL4.Commons.Regions
{
    // 領域
    public interface IRegion
    {
        void AddView(object view);
        void RemoveView(object view);
    }

    // ContentControlを領域としてラッピング
    public class ContentControlRegion : IRegion
    {
        private ContentControl _target;
        public ContentControlRegion(ContentControl target)
        {
            _target = target;
        }

        #region IRegion Members

        public void AddView(object view)
        {
            _target.Content = view;
        }

        public void RemoveView(object view)
        {
            if (_target.Content == view)
            {
                _target.Content = null;
            }
        }

        #endregion
    }

    // ItemsControl1を領域としてラッピング
    public class ItemsControlRegion : IRegion
    {
        private ItemsControl _target;
        public ItemsControlRegion(ItemsControl target)
        {
            _target = target;
        }

        #region IRegion Members

        public void AddView(object view)
        {
            _target.Items.Add(view);
        }

        public void RemoveView(object view)
        {
            _target.Items.Remove(view);
        }

        #endregion
    }

}

続いて、これを管理するクラス。

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Windows.Controls;

namespace SL4.Commons.Regions
{
    [InheritedExport]
    public interface IRegionManager
    {
        // 領域を名前で取得する
        IRegion GetRegion(string name);
    }

    public class RegionManager : IRegionManager
    {
        // 型とIRegion作成処理のマップ
        private static Dictionary<Type, Func<object, IRegion>> _regionFactories = new Dictionary<Type, Func<object, IRegion>>();

        public static void AddRegionFactory(Type regionType, Func<object, IRegion> factory)
        {
            _regionFactories[regionType] = factory;
        }

        static RegionManager()
        {
            // デフォルトでContentControlとItemsControlを領域として使用可能
            _regionFactories.Add(
                typeof(ContentControl), 
                arg => new ContentControlRegion((ContentControl)arg));
            _regionFactories.Add(
                typeof(ItemsControl),
                arg => new ItemsControlRegion((ItemsControl)arg));
        }

        // 領域は、MEFで管理してるものをごそっといただく
        [ImportMany(RegionExportAttribute.ContractName, AllowRecomposition=true)]
        public IEnumerable<Lazy<object, IRegionExportMetadata>> Regions { get; set; }

        #region IRegionManager Members
        public IRegion GetRegion(string name)
        {
            // 名前で探して返す
            var value = Regions.Single(lazy => lazy.Metadata.Name == name).Value;
            if (_regionFactories.ContainsKey(value.GetType()))
            {
                return _regionFactories[value.GetType()](value);
            }
            return null;
        }
        #endregion
    }
}

領域をMEFへExportするための属性

using System;
using System.ComponentModel.Composition;

namespace SL4.Commons.Regions
{
    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Property)]
    public class RegionExportAttribute : ExportAttribute
    {
        public const string ContractName = "##Region##";

        public string Name { get; set; }
        public RegionExportAttribute(string name) : base(ContractName)
        {
            this.Name = name;
        }
    }

    public interface IRegionExportMetadata
    {
        string Name { get; }
    }
}

使い方は、どこかの画面に置いたContentControlをプロパティとして公開する際に、RegionExport属性をつける

[RegionExport("PlaceHolder")]
public ContentControl PlaceHolder { get { return myContentControl; } }

後はIRegionManagerをImportして、以下のようなコードを書けば画面遷移ちっくな動きをする。

RegionManager.GetRegion("PlaceHolder").AddView(new MyUserControl());

ということで、自分用コードの断片メモでした。人が読んでも解説が少ないからよくわからないと思う…。