業務のためのC#・C言語・C++学習

主にC#の文法やWPF周りのアウトプットに利用してます。

【C#-WPF】MVVMパターンとContentControlで画面遷移する方法

WPFで画面遷移しようとしてWEB検索をかけるとPageとFrameを使った画面遷移が上位に表示されます。これらは開発者が実現したい機能がない場合があるため、MVVMパターンとContentControlの利用を推奨します。

コンストラクタで初期画面を反映させる場合

大まかな流れ

AView.xamlをMainWindow.xaml上に表示します。

プロジェクトのファイル構成

大まかな流れ
①表示したいUIをUserControlとして作成
②MainWindow.xamlにResources、DataTemplate、ContentControlを設定 ③ContentControlで設定した変数ActiveContentに表示したいUIのViewModelを設定

①表示したいUIをUserControlとして作成

AView.xaml
AViewまず作成する。
WindowではなくUserControlを追加する。Windowだとうまくいかない。UserControlを以下のように選んでください。
中身の実装は、ここではTextboxを表示するようにしています。

<UserControl x:Class="ScreenTransition1.Views.AView"
             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:ScreenTransition1.Views"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <DockPanel>
        <DockPanel DockPanel.Dock="Bottom"/>
        <DockPanel DockPanel.Dock="Top">
            <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="10">
                <TextBox Text="AAAAAAAAAAAAAA"/>
            </StackPanel>
        </DockPanel>
        <Grid>
            <TextBox Text="AAAAAAAAAA"/>
        </Grid>
    </DockPanel>
</UserControl>

AViewModel.cs
AViewの ViewModelを作成する。
後の利便性を考慮しMainContentを継承

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ScreenTransition1.ViewModels
{
    public class AViewModel:MainContent
    {
    }
}

MainContent
INotifyPropertyChangedを継承したクラスである。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace ScreenTransition1.ViewModels
{
    public class MainContent : INotifyPropertyChanged
    {
        //UIに自動更新を行うためのイベント
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

②MainWindow.xamlにResources、DataTemplate、ContentControlを設定

MainWindow.xaml
DataTemplateとContentControlを用いMainWindow.xamlとAView.xamlを関連付ける。

<Window x:Class="ScreenTransition1.MainWindow"
        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:ScreenTransition1"
        xmlns:vm="clr-namespace:ScreenTransition1.ViewModels"
        xmlns:view="clr-namespace:ScreenTransition1.Views"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="800">
    <DockPanel>
        <DockPanel DockPanel.Dock="Top"/>
        <DockPanel DockPanel.Dock="Bottom"/>
        <DockPanel Margin="5">
            <GroupBox Padding="5">
                <GroupBox.Header>
                    <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
                        <ComboBox Width="300"/>
                    </StackPanel>
                </GroupBox.Header>
                <Grid>
                    <Grid.Resources>
                        <DataTemplate DataType="{x:Type vm:AViewModel}">
                            <view:AView/>
                        </DataTemplate>
                    </Grid.Resources>
                    <ContentControl Content="{Binding ActiveContent}"/>
                </Grid>
            </GroupBox>
        </DockPanel>
    </DockPanel>
</Window>

DockPanelを用い外観の整列を行っている。DockPanelのTopとButtomを設定しているが今回はコントールを設定しない。
重要なのはDockPanelのCenter部分にあるGridである。

Gridのリソース内でDataTemplateを用い表示したいUIであるAViewのViewModelとViewを関連付ける。
表示したいUIはContentControlで管理する。

MainWindow.xaml.cs

namespace ScreenTransition1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }
}

③ContentControlで設定した変数ActiveContentに表示したいUIのViewModelを設定

MainViewModel.cs
ContentControlで設定した変数ActiveContentに表示したいUIのViewModelを設定。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ScreenTransition1
{
    using ViewModels;
    public class MainViewModel:MainContent
    {
        public MainViewModel()
        {
            AViewModel = new AViewModel();
            ActiveContent = AViewModel;
        }
        private MainContent _activeContent;
        public MainContent ActiveContent
        {
            get { return _activeContent; }
            set
            {
                if (_activeContent != value)
                {
                    _activeContent = value;
                    RaisePropertyChanged("ActiveContent");
                }
            }
        }

        AViewModel _aViewModel;
        public AViewModel AViewModel
        {
            get { return _aViewModel; }
            set
            {
                if (_aViewModel != value)
                {
                    _aViewModel = value;
                    RaisePropertyChanged("AViewModel");
                }
            }

        }

    }
}

出力結果

コンボボックスで複数画面の画面遷移を行う場合

OverView

この例ではMainWindow.xamlをUIの受け皿とし、AView.xamlとBView.xamlを受け皿に入れるUIとする。UIの変更=画面選択はコンボボックスで行う

プロジェクト階層

プロジェクトはMVVMパターンを採用しており、Viewに関するXAMLやクラス、ViewModelに関するクラスがある。
各プログラムの関係図を示す。引き続きでViewとViewModelでContentControlを利用し画面遷移をする。

各コードやXAMLを解説していく。

MainWindow.xaml
<Window x:Class="ScreenTransition1.MainWindow"
        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:ScreenTransition1"
        xmlns:vm="clr-namespace:ScreenTransition1.ViewModels"
        xmlns:view="clr-namespace:ScreenTransition1.Views"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="800">
    <DockPanel>
        <DockPanel DockPanel.Dock="Top"/>
        <DockPanel DockPanel.Dock="Bottom"/>
        <DockPanel Margin="5">
            <GroupBox Padding="5">
                <GroupBox.Header>
                    <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
                        <ComboBox ItemsSource="{Binding Contents}" SelectedItem="{Binding ActiveContent}" Width="300">
                            <ComboBox.ItemTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding Title}"/>
                                </DataTemplate>
                            </ComboBox.ItemTemplate>
                        </ComboBox>
                    </StackPanel>
                </GroupBox.Header>
                <Grid>
                    <Grid.Resources>
                        <DataTemplate DataType="{x:Type vm:AViewModel}">
                            <view:AView/>
                        </DataTemplate>
                        <DataTemplate DataType="{x:Type vm:BViewModel}">
                            <view:BView/>
                        </DataTemplate>
                    </Grid.Resources>
                    <ContentControl Content="{Binding ActiveContent}"/>
                </Grid>
            </GroupBox>
        </DockPanel>
    </DockPanel>
</Window>

BViewも画面遷移させたいのでDataTemplate にBViewのViewとViewModelを追加している。

AView.xaml,BView.xaml,MainWindow.xaml.cs

AView.xamlとBView.xamlは前節とコンテンツの配置や数は変わっていない。テキストがAAAAになっているかBBBBBBになっているかの違いだけである。
MainWindow.xaml.csは変更点なし。
ComboBoxで画面の選択を行うためにContentControlのバインディン変数であるActiveContentをSelectedItemにも使用している。また、ComboBoxに表示するテキストも追加している。

MainContent.cs

コンボボックスに表示するテキストをバインディングするためのプロパティをコンストラクタで使用する。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace ScreenTransition1.ViewModels
{
    public class MainContent : INotifyPropertyChanged
    {
        //UIに自動更新を行うためのイベント
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        public MainContent(string title)
        {
            Title = title;
        }
        public string Title { get; private set; }
    }
}
MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ScreenTransition1
{
    using ViewModels;
    public class MainViewModel:MainContent
    {
        public MainViewModel() : base("MainView")
        {

        }

        private IList<MainContent> _contents;
        public IList<MainContent> Contents
        {
            get { return _contents ?? (_contents = CreateContents().ToArray()); }
        }
        private IEnumerable<MainContent> CreateContents()
        {
            yield return new AViewModel();
            yield return new BViewModel();
        }
        private MainContent _activeContent;
        public MainContent ActiveContent
        {
            get { return _activeContent ?? (_activeContent = AViewModel); }
            set
            {
                if (_activeContent != value)
                {
                    _activeContent = value;
                    RaisePropertyChanged("ActiveContent");
                }
            }
        }
        public AViewModel AViewModel { get { return Contents.First() as AViewModel; } }
    }
}
出力結果