かずきのBlog@hatena

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

複数のXAPに分割されたアプリケーション

Silverlight4からお手軽に作れるようになってます。MEFの中にあるDeploymentCatalogクラスのお陰です。
例えば、SilverlightApplication5という名前でプロジェクトをつくり、MEF関連のアセンブリを2つ参照に追加します。

MainPageがあるので、適当にViewModelを作ります。ViewModelでは、UserControlをMEFからImportしてもらうようにしてます。

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

namespace SilverlightApplication5
{
    [Export]
    public class MainPageViewModel : INotifyPropertyChanged
    {
        private IEnumerable<UserControl> _menus;

        // Menuという名前でExportされてるUserControlを全て入れてもらう
        [ImportMany("Menu", AllowRecomposition = true)]
        public IEnumerable<UserControl> Menus
        {
            get { return _menus; }
            set
            {
                _menus = value;
                OnPropertyChanged("Menus");
            }
        }

        #region 定番コード
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            var h = PropertyChanged;
            if (h == null) return;
            h(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

    }
}

Viewのコードビハインド側でMEF使ってDataContextを設定します。

using System.ComponentModel.Composition;
using System.Windows.Controls;

namespace SilverlightApplication5
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            // Importしてもらう
            CompositionInitializer.SatisfyImports(this);
        }

        // このプロパティ経由でViewModelを代入してもらう
        [Import]
        public MainPageViewModel Model
        {
            get { return DataContext as MainPageViewModel; }
            set { DataContext = value; }
        }
    }
}

ここまで出来たら、MainPage.xamlで、ItemsControlをおいてMenusプロパティをバインドして中身をそのまま表示するように設定します。

<UserControl x:Class="SilverlightApplication5.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <ItemsControl ItemsSource="{Binding Path=Menus}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <!-- とりあえず横並びで -->
                    <StackPanel Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</UserControl>

とりあえず、これで土台が出来上がりです。このまま起動しても白いだけの画面が出るだけで面白くもなんともありません。SilverlightApplicationを1つ追加します。名前はデフォのままでSilverlightApplication1にしました。
そこのMainPageをMenuという名前でExportするようにしました。

using System.ComponentModel.Composition;
using System.Windows.Controls;

namespace SilverlightApplication1
{
    // Menuって名前のUserControl
    [Export("Menu", typeof(UserControl))]
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }
    }
}

XAML側には、適当に何か置いておきます。(ここではボタン1つおきました。

<UserControl x:Class="SilverlightApplication1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="Button" Margin="12" Name="button1" />
    </Grid>
</UserControl>

これで粒がそろいました。最後につなげて生きます。SilverlightApplication5のApp.xaml.csを開いてApplication_Startupイベントハンドラあたりに以下のコードを追加します。

private void Application_Startup(object sender, StartupEventArgs e)
{
    // 自分のアセンブリとSilverlightApplication1.xapを読み込む
    CompositionHost.Initialize(
        new AssemblyCatalog(typeof(App).Assembly),
        CreateCatalog("SilverlightApplication1.xap"));

    this.RootVisual = new MainPage();
}
private DeploymentCatalog CreateCatalog(string path)
{
    // DeploymentCatalog作ってダウンロード開始する
    var catalog = new DeploymentCatalog(path);
    catalog.DownloadAsync();
    return catalog;
}

これで実行すると、SilverlightAPplication5のMainPageにSilverlightApplication1のMainPageがはまり込んで表示されます。このように別のXAPを用意してExport("Menu", typeof(UserControl))をつけてやることで、どんどん追加することが出来ます。

業務単位でXAPを作っておいて、メニュー画面にこの方式を使ってメニュー用画面だけを読み込んで並べるというのも手じゃないでしょうか。