5
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[WPF/C#]Prism(6.3.0)のRegionで画面遷移をする

Last updated at Posted at 2019-05-18

Prism関連
https://qiita.com/tera1707/items/4fda73d86eded283ec4f#prism%E9%96%A2%E9%80%A3wpfxaml

やりたいこと

Prismを使って、画面遷移(とMVVM)の基本部分のひな型を作っておけば、今後WPFで簡単なアプリなど作ろうとしたときに役に立ちそうと思ったので、ひな型を作ってみる。それをする上でのメモを残しておく。

前提

下記の、かずきさんの教育用githubをベースに実験して、自分用にまとめ。
https://github.com/runceel/PrismEdu/tree/master/06.Navigation
https://blog.okazuki.jp/entry/2014/09/11/224645

作業手順

PrismをNugetからインストール

prism.core(今回は6.3.0)
image.png

prism.Unity(今回は6.3.0)
image.png

Bootstrapper.cs作成

アプリ起動時に、Prism.Unityがしかるべき処理をできるように、起動処理を作ってやる。
(一旦どうしてこういう作りになるかは置いといて、こういうものとして「おまじない」扱いに一旦しとく)

Bootstrapper.cs
using Microsoft.Practices.Unity;
using PrismSample.Views;
using Prism.Unity;
using System.Linq;
using System.Windows;
using System;

namespace PrismSample
{
    class Bootstrapper : UnityBootstrapper
    {
        protected override DependencyObject CreateShell()
        {
            return this.Container.Resolve<Shell>();
        }

        protected override void InitializeShell()
        {
            ((Window)this.Shell).Show();
        }

        protected override void ConfigureContainer()
        {
            base.ConfigureContainer();

            // ViewをDIコンテナに登録(!!!すべて「Object型」で登録すること!!!(なぜかは不明))
            this.Container.RegisterTypes(
                AllClasses.FromLoadedAssemblies()
                    .Where(x => x.Namespace.EndsWith(".Views")),
                getFromTypes: _ => new[] { typeof(object) },
                getName: WithName.TypeName);
        }
    }
}

App.xamlをメンテ

起動時に上で作ったブートストラップを実施してくれるよう、StartupUriの行を、Startup="Application_Startup"にする。

App.xaml
<Application x:Class="NavigationSampleApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:NavigationSampleApp"
             Startup="Application_Startup"> <!-- ★←ココ -->
    <Application.Resources>
         
    </Application.Resources>
</Application>

App.xaml.csをメンテ

APp.xamlでStartup時処理に指定したApplication_Startupを、App.xaml.csに記述する。

App.xaml.cs
using System.Windows;

namespace PrismSample
{
    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            // Bootstrapperを起動する
            new Bootstrapper().Run();
        }
    }
}

親画面(Shell.xaml)を作成

ここに、画面遷移のための「ContentControl」を作成し、RegionNameを記述する。
ここで作ったContentControlに、後で作成する画面用UserControlが表示されることになる。

shell.xaml
<Window x:Class="PrismSample.Views.Shell"
        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"
        xmlns:local="clr-namespace:PrismSample.Views"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:ViewModels="clr-namespace:PrismSample.ViewModels"
        mc:Ignorable="d"
        Title="Shell"
        Height="300"
        Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
        <ContentControl Grid.Row="0" prism:RegionManager.RegionName="MainRegion" />
        <Button Grid.Row="1" Content="ボタン" Command="{Binding ButtonCommand}"/>
    </Grid>
</Window>

下で作るViewModelと自動で連携するために、下記をWindowに付け加えること。
※後でつくる、UserControlも同様!忘れるとViewModelがViewと紐づかない。

xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"

###親画面のViewModelを作成
ボタンをつけて、それを押したらUserControlで作った画面に遷移するようにした。

※本当は、UserControl1の画面を初期表示させたくてコンストラクタでRequestNavigateしたかったが、コンストラクタを通った時点ではRegionManagerがまだ注入されておらずnullのため、RegionManagerで遷移処理ができなかった。どうすればよいか、要調査。

→画面起動時に遷移させてやる方法があった。この記事の下の方に追記した。

ShellViewModel.cs
using PrismSample.Views;
namespace PrismSample.ViewModels
{
    class ShellViewModel : BindableBase
    {
        [Dependency]
        public IRegionManager RegionManager { get; set; }

        public DelegateCommand ButtonCommand { get; }
        
        public ShellViewModel()
        {
            this.ButtonCommand = new DelegateCommand(() =>
            {
                this.RegionManager.RequestNavigate("MainRegion", nameof(UserControl1));
            });
        }

    }
}

画面のUserControlを作る

Shell.xamlに配置したContentControlに出したい画面を作る。
まずは、Viewを作る。
image.png

UserControl1.xaml
<UserControl x:Class="PrismSample.Views.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:PrismSample.Views"
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="Beige">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="60"/>
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="0" Text="画面1" FontSize="60"/>
        <Button Grid.Row="1" Content="画面1のボタン" Command="{Binding ButtonCommand}"/>
    </Grid>
</UserControl>

画面のUserControlに対応したViewModelを作成する

★注意点

  • 画面のViewに対応するViewModelを作る。prismに自動でViewと関連付けてもらうために、名前は「Viewの名前ViewModel」にすること(今回の場合はUserControl1ViewModel
  • ViewModelなので画面にバインドするのに便利なようにBindableBaseを継承すること
  • PrismのRegionを使えるように、INavigationAwareインターフェースを実装すること。
    • IsNavigationTargetメソッドは、画面のインスタンスを使いまわすかどうか制御するためのもの(src内コメント参照)
    • OnNavigatedFromメソッドは、この画面から他の画面に遷移するときの処理
    • OnNavigatedToメソッドは、他の画面からこの画面に遷移したときの処理。ここで遷移元からのパラメータを受け取れる。
  • [Dependency]をつけて、RegionManagerプロパティを作成しておくこと。これは、Unityコンテナから注入されてくる画面遷移処理に使用するマネージャー。
UserControl1ViewModel.cs
using Microsoft.Practices.Unity;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using PrismSample.Views;

namespace PrismSample.ViewModels
{
    class UserControl1ViewModel : BindableBase, INavigationAware
    {
        [Dependency]
        public IRegionManager RegionManager { get; set; }

        public DelegateCommand ButtonCommand { get; }

        public UserControl1ViewModel()
        {
            this.ButtonCommand = new DelegateCommand(() =>
            {
                // Shell.xaml.csで作成したリージョンの名前と、画面のUserControlクラス名を指定して、画面遷移させる。
                // (パラメータを渡すこともできる)
                this.RegionManager.RequestNavigate("MainRegion", nameof(UserControl2), new NavigationParameters($"id=1"));
            });
        }

        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            // このメソッドの返す値により、画面のインスタンスを使いまわすかどうか制御できる。
            // true :インスタンスを使いまわす(画面遷移してもコンストラクタ呼ばれない)
            // false:インスタンスを使いまわさない(画面遷移するとコンストラクタ呼ばれる)
            // メソッド実装なし:trueになる
            // ※コンストラクタは呼ばれないが、Loadedイベントは起きる
            return false;
        }

        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
            // この画面から他の画面に遷移するときの処理
        }

        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            // 他の画面からこの画面に遷移したときの処理

            // 画面遷移元から、この画面に遷移したときにパラメータを受け取れる。
            string Id = navigationContext.Parameters["id"] as string;
        }
    }
}

画面遷移について

上のUserControl1内でほかの画面(今回の場合はUserControl2)に遷移させたい場合は、下記のようにする。
NavigationParametersを使って、遷移先にパラメータを渡すこともできる。(遷移先画面のOnNavigatedToメソッドで受け取る(src参照))

this.RegionManager.RequestNavigate("MainRegion", nameof(UserControl2), new NavigationParameters($"id=1"));

ほかの画面のViewとViewModelを作る

今回は、画面を2つ作ってお互いを遷移させるようなことをするので、UserControl2'というビューをつくり、それに対応するUserControl2ViewModelを作る。内容は、UserCOntrol1`とほぼ同じのため、内容は割愛。

不要なものを削除する

プロジェクト作成時にできていたMainWindow.xamlは使用しないため、削除しておく。

できあがり

一旦これで出来上がり。ファイル/フォルダ構成はこのようにした。
image.png

動作としては、下記のような画面遷移をする。

image.png

image.png

image.png

190521追記

最初に一回ボタンを押さないと初期画面(UserControl1)を出せていなかったが、ウインドウのActivatedイベント時にRegionManagerを使うことで、初期画面を表示することができた。(本当はViewModel内でやりたかったが...)
※Loadedの方が良いかも。

→ViewModel内でやる方法があった。この記事の下の方に追記した。

Shell.xaml
<Window x:Class="PrismSample.Views.Shell"
        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"
        xmlns:local="clr-namespace:PrismSample.Views"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:ViewModels="clr-namespace:PrismSample.ViewModels"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        mc:Ignorable="d"
        Title="Shell"
        Height="300"
        Width="300"
        Activated="Window_Activated">   <!-- ★追記 -->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
        <ContentControl Grid.Row="0" prism:RegionManager.RegionName="MainRegion" />
        <!--<Button Grid.Row="1" Content="ボタン" Command="{Binding ButtonCommand}"/>-->
    </Grid>
</Window>
Shell.xaml.cs
using Microsoft.Practices.Unity;
using Prism.Regions;
using System;
using System.Windows;

namespace PrismSample.Views
{
    /// <summary>
    /// Shell.xaml の相互作用ロジック
    /// </summary>
    public partial class Shell : Window
    {
        [Dependency]
        public IRegionManager RegionManager { get; set; }

        public Shell()
        {
            InitializeComponent();
        }

        private void Window_Activated(object sender, EventArgs e)
        {
            this.RegionManager.RequestNavigate("MainRegion", nameof(UserControl1));
        }
    }
}

20/09/03追記

こちらの記事でやったように、EventTriggerを使ってLoadedイベントにViewModelのコマンドを結び付けてあげれば、コードビハインドを使わず、ViewModelに画面遷移の処理を集めることができそう。

※その記事にも書いているが、下記の参照追加などが必要なので注意。

shell.xaml
<Window x:Class="PrismSample.Views.Shell"
        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"
        xmlns:local="clr-namespace:PrismSample.Views"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:ViewModels="clr-namespace:PrismSample.ViewModels"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" ★ここ追加
        mc:Ignorable="d"
        Title="Shell"
        Height="300"
        Width="300">
    <i:Interaction.Triggers>   ★この辺も追加
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding LoadedCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
        <ContentControl Grid.Row="0" prism:RegionManager.RegionName="MainRegion" />
    </Grid>
</Window>
ShellViewModel.cs
using Microsoft.Practices.Unity;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using PrismSample.Views;
namespace PrismSample.ViewModels
{
    class ShellViewModel : BindableBase
    {
        [Dependency]
        public IRegionManager RegionManager { get; set; }
        public DelegateCommand LoadedCommand { get; }     //★ボタンのコマンドをやめて、Loaded時のコマンドにした
        
        public ShellViewModel()
        {
            this.LoadedCommand = new DelegateCommand(() =>
            {
                this.RegionManager.RequestNavigate("MainRegion", nameof(UserControl1));
            });
        }

    }
}

コード

参考

かずきさんPrism教育github
https://github.com/runceel/PrismEdu

かずきさんブログ
https://blog.okazuki.jp/entry/2014/09/11/224645

5
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?