14-UsingEventAggregator
Description には Using the IEventAggregator とあります。 まんま過すぎですね。
動かしてみると、TextBox に入力した内容を「Send Message」で送ると、 ListBox に追加されるというサンプルのようです。
UsingEventAggregator 内の App.xaml.cs
using System.Windows;
using UsingEventAggregator.Views;
namespace UsingEventAggregator
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry) { }
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ModuleA.ModuleAModule>();
moduleCatalog.AddModule<ModuleB.ModuleBModule>();
}
}
}
UsingEventAggregator 内の Views\MainWindow.xaml
<Window
x:Class="UsingEventAggregator.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="{Binding Title}"
Width="525"
Height="350"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentControl prism:RegionManager.RegionName="LeftRegion" />
<ContentControl Grid.Column="1" prism:RegionManagerRegionName="RightRegion" />
</Grid>
</Window>
UsingEventAggregator 内の ViewsModels\MainWindowViewModel.cs
namespace UsingEventAggregator.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public MainWindowViewModel() { }
}
}
ModuleA プロジェクトと ModuleB プロジェクトを ContentControl
で読み込んでいるようです。
個人的には、ここまでプロジェクト細切れにする必要が本当にあるのか、と疑問ですけどね。
ModuleA 内の ModuleAModule.cs
using ModuleA.Views;
namespace ModuleA
{
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("LeftRegion", typeof(MessageView));
}
public void RegisterTypes(IContainerRegistry containerRegistry) { }
}
}
ModuleA 内の Views\MessageView.xaml
<UserControl
x:Class="ModuleA.Views.MessageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Padding="25"
prism:ViewModelLocator.AutoWireViewModel="True">
<StackPanel>
<TextBox Margin="5" Text="{Binding Message}" />
<Button
Margin="5"
Command="{Binding SendMessageCommand}"
Content="Send Message" />
</StackPanel>
</UserControl>
ModuleA 内の ViewsModels\MessageViewModel.cs
using UsingEventAggregator.Core;
namespace ModuleA.ViewModels
{
public class MessageViewModel : BindableBase
{
private readonly IEventAggregator _ea;
private string _message = "Message to Send";
public string Message
{
get { return _message; }
set { SetProperty(ref _message, value); }
}
public DelegateCommand SendMessageCommand { get; }
public MessageViewModel(IEventAggregator ea)
{
_ea = ea;
SendMessageCommand = new DelegateCommand(SendMessage);
}
private void SendMessage()
{
_ea.GetEvent<MessageSentEvent>().Publish(Message);
}
}
}
どうやら、コンストラクタでコマンドを登録して、SendMessage()
でメッセージングを行っているようです。
ModuleB 内の ModuleBModule.cs
using ModuleB.Views;
namespace ModuleB
{
public class ModuleBModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("RightRegion", typeof(MessageList));
}
public void RegisterTypes(IContainerRegistry containerRegistry) { }
}
}
ModuleB 内の Views\MessageList.xaml
<UserControl
x:Class="ModuleB.Views.MessageList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Padding="25"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<ListBox ItemsSource="{Binding Messages}" />
</Grid>
</UserControl>
ModuleB 内の ViewModels\MessageListViewModel.cs
using System.Collections.ObjectModel;
using UsingEventAggregator.Core;
namespace ModuleB.ViewModels
{
public class MessageListViewModel : BindableBase
{
private readonly IEventAggregator _ea;
private ObservableCollection<string> _messages;
public ObservableCollection<string> Messages
{
get { return _messages; }
set { SetProperty(ref _messages, value); }
}
public MessageListViewModel(IEventAggregator ea)
{
_ea = ea;
Messages = new ObservableCollection<string>();
_ea.GetEvent<MessageSentEvent>().Subscribe(MessageReceived);
}
private void MessageReceived(string message)
{
Messages.Add(message);
}
}
}
どうやら、コンストラクタでメッセージ購読処理をして
MessageReceived
で ListBox に追加しているようですね。
このサンプルについて
正直、あまり言及することがありません。
メッセージングを使うならこういう形になるよな、だけど CommunityToolkit.Mvvm の方がスマートに書けるな、それだけです。
15-FilteringEvents
Description には Filtering events when subscribing to events とあります。
これ、何でしょうね?TextBox にテキスト入力しても、ListBox に追加されません。コードを見ていきましょう。
UsingEventAggregator.Core 内の MessageSentEvent.cs
namespace UsingEventAggregator.Core
{
public class MessageSentEvent : PubSubEvent<string> { }
}
UsingEventAggregator 内の Views\MainWindow.xaml
<Window
x:Class="UsingEventAggregator.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="{Binding Title}"
Width="525"
Height="350"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentControl prism:RegionManager.RegionName="LeftRegion" />
<ContentControl Grid.Column="1" prism:RegionManager.RegionName="RightRegion" />
</Grid>
</Window>
UsingEventAggregator 内の ViewModels\MainWindowViewModel.cs
namespace UsingEventAggregator.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public MainWindowViewModel() { }
}
}
ModuleA 内の ModuleAModule.cs
using ModuleA.Views;
namespace ModuleA
{
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("LeftRegion", typeof(MessageView));
}
public void RegisterTypes(IContainerRegistry containerRegistry) { }
}
}
ModuleA 内の Views\MessageView.xaml
<UserControl
x:Class="ModuleA.Views.MessageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Padding="25"
prism:ViewModelLocator.AutoWireViewModel="True">
<StackPanel>
<TextBox Margin="5" Text="{Binding Message}" />
<Button
Margin="5"
Command="{Binding SendMessageCommand}"
Content="Send Message" />
</StackPanel>
</UserControl>
ModuleA 内の ViewModels\MessageViewModel.cs
using UsingEventAggregator.Core;
namespace ModuleA.ViewModels
{
public class MessageViewModel : BindableBase
{
private readonly IEventAggregator _ea;
private string _message = "Message to Send";
public string Message
{
get { return _message; }
set { SetProperty(ref _message, value); }
}
public DelegateCommand SendMessageCommand { get; }
public MessageViewModel(IEventAggregator ea)
{
_ea = ea;
SendMessageCommand = new DelegateCommand(SendMessage);
}
private void SendMessage()
{
_ea.GetEvent<MessageSentEvent>().Publish(Message);
}
}
}
ModuleB 内の ModuleBModule.cs
using ModuleB.Views;
namespace ModuleB
{
public class ModuleBModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("RightRegion", typeof(MessageList));
}
public void RegisterTypes(IContainerRegistry containerRegistry) { }
}
}
ModuleB 内の Views\MessageList.xaml
<UserControl
x:Class="ModuleB.Views.MessageList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Padding="25"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<ListBox ItemsSource="{Binding Messages}" />
</Grid>
</UserControl>
ModuleB 内の ViewModels\MessageListViewModel.cs
using System.Collections.ObjectModel;
using UsingEventAggregator.Core;
namespace ModuleB.ViewModels
{
public class MessageListViewModel : BindableBase
{
private readonly IEventAggregator _ea;
private ObservableCollection<string> _messages;
public ObservableCollection<string> Messages
{
get { return _messages; }
set { SetProperty(ref _messages, value); }
}
public MessageListViewModel(IEventAggregator ea)
{
_ea = ea;
Messages = new ObservableCollection<string>();
_ea.GetEvent<MessageSentEvent>().Subscribe(MessageReceived, ThreadOption.PublisherThread, false, (filter) => filter.Contains("Brian"));
}
private void MessageReceived(string message)
{
Messages.Add(message);
}
}
}
はい、やっとネタがわかりました!「Brian」を含むメッセージだけを処理していたんですね!
っていうか、これ購読で処理する必要ある?MessageReceived
で弾けばいいだけの話に見えるんだけど…
16-RegionContext
Descriptionには Pass data to nested regions using the RegionContext とあります。
実際動作させると…リストボックスの内容を下の View に内容を解析して反映させている…?
RegionContext 内の App.xaml.cs
using System.Windows;
using ModuleA;
using RegionContext.Views;
namespace RegionContext
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry) { }
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ModuleAModule>();
}
}
}
RegionContext 内の Views\MainWindow.xaml
<Window
x:Class="RegionContext.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="{Binding Title}"
Width="525"
Height="350"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<ContentControl prism:RegionManager.RegionName="ContentRegion" />
</Grid>
</Window>
RegionContext 内の ViewModels\MainWindowViewModel.cs
namespace RegionContext.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public MainWindowViewModel() { }
}
}
ModuleA 内の ModuleAModule.cs
using ModuleA.Views;
namespace ModuleA
{
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ContentRegion", typeof(PersonList));
regionManager.RegisterViewWithRegion("PersonDetailsRegion", typeof(PersonDetail));
}
public void RegisterTypes(IContainerRegistry containerRegistry) { }
}
}
ModuleA 内の Views\PersonList.xaml
<UserControl
x:Class="ModuleA.Views.PersonList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid
x:Name="LayoutRoot"
Margin="10"
Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="_listOfPeople" ItemsSource="{Binding People}" />
<ContentControl
Grid.Row="1"
Margin="10"
prism:RegionManager.RegionContext="{Binding SelectedItem, ElementName=_listOfPeople}"
prism:RegionManager.RegionName="PersonDetailsRegion" />
</Grid>
</UserControl>
ModuleA 内の Views\PersonDetail.xaml
<UserControl
x:Class="ModuleA.Views.PersonDetail"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- First Name -->
<TextBlock Margin="5" Text="First Name:" />
<TextBlock
Grid.Column="1"
Margin="5"
Text="{Binding SelectedPerson.FirstName}" />
<!-- Last Name -->
<TextBlock
Grid.Row="1"
Margin="5"
Text="Last Name:" />
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="5"
Text="{Binding SelectedPerson.LastName}" />
<!-- Age -->
<TextBlock
Grid.Row="2"
Margin="5"
Text="Age:" />
<TextBlock
Grid.Row="2"
Grid.Column="1"
Margin="5"
Text="{Binding SelectedPerson.Age}" />
</Grid>
</UserControl>
ModuleA 内の ViewModels\PersonListViewModel.cs
using System.Collections.ObjectModel;
using ModuleA.Business;
namespace ModuleA.ViewModels
{
public class PersonListViewModel : BindableBase
{
private ObservableCollection<Person> _people;
public ObservableCollection<Person> People
{
get { return _people; }
set { SetProperty(ref _people, value); }
}
public PersonListViewModel()
{
CreatePeople();
}
private void CreatePeople()
{
var people = new ObservableCollection<Person>();
for (int i = 0; i < 10; i++)
{
people.Add(new Person()
{
FirstName = String.Format("First {0}", i),
LastName = String.Format("Last {0}", i),
Age = i
});
}
People = people;
}
}
}
ModuleA 内の ViewModels\PersonDetailViewModel.cs
using ModuleA.Business;
namespace ModuleA.ViewModels
{
public class PersonDetailViewModel : BindableBase
{
private Person _selectedPerson;
public Person SelectedPerson
{
get { return _selectedPerson; }
set { SetProperty(ref _selectedPerson, value); }
}
public PersonDetailViewModel() { }
}
}
ModuleA 内の Business\Person.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ModuleA.Business
{
public class Person : INotifyPropertyChanged
{
#region Properties
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged();
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
OnPropertyChanged();
}
}
private int _age;
public int Age
{
get { return _age; }
set
{
_age = value;
OnPropertyChanged();
}
}
private DateTime? _lastUpdated;
public DateTime? LastUpdated
{
get { return _lastUpdated; }
set
{
_lastUpdated = value;
OnPropertyChanged();
}
}
#endregion //Properties
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
#endregion //INotifyPropertyChanged
public override string ToString()
{
return String.Format("{0}, {1}", LastName, FirstName);
}
}
}
このコードについて
まず思ったのは「え?Businessフォルダって、ViewModel?Model?」という混乱ですね。
ViewModel が Model を知っているけど、Model は ViewModel を知らない、これに逸脱してないから Model だとは思う。 だったら、せめて Models フォルダに入れようよ…
噛み砕くと、ListBox で選択されたら、ViewModel から選択アイテムを Business に伝えて、 イベント発火で PersonDetailViewModel に投げている…のだとは思う。
いや、SelectedItem
どこ行ったよ!イベントを受け取る所どこだよ!
っていうか Person クラスが INotifyPropertyChanged
を継承しているから、やっぱり ViewMode なのか?
率直に言って、こんなうんコード(もうこう言わざるを得ない)は触りたくもないですね。
触るとしたら、CommunityToolkit.Mvvm を使ったリファクタリングでだけ!
もうね、Prism 開発者陣も MVVM の設計思想を理解していなかった疑惑まで出てきてます。
ええ、次回はこれを CommunityToolkit.Mvvm で書いてみます。 言い出しっぺの法則ですね…
17-BasicRegionNavigation
Descriptionには See how to implement basic region navigation とあります。
動かして見た所、単純にリージョンを切り替えるだけみたいです。
ModuleA 内の ModuleAModle.cs
using ModuleA.Views;
namespace ModuleA
{
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider) { }
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>();
containerRegistry.RegisterForNavigation<ViewB>();
}
}
}
ViewA と ViewB をナビゲーターに登録してるようですね。
BasicRegionNavigation 内の App.xaml.cs
using System.Windows;
using BasicRegionNavigation.Views;
namespace BasicRegionNavigation
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry) { }
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ModuleA.ModuleAModule>();
}
}
}
BasicRegionNavigation 内の Views\MainWindow.xaml
<Window
x:Class="BasicRegionNavigation.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="{Binding Title}"
Width="525"
Height="350"
prism:ViewModelLocator.AutoWireViewModel="True">
<DockPanel LastChildFill="True">
<StackPanel
Margin="5"
DockPanel.Dock="Top"
Orientation="Horizontal">
<Button
Margin="5"
Command="{Binding NavigateCommand}"
CommandParameter="ViewA">
Navigate to View A
</Button>
<Button
Margin="5"
Command="{Binding NavigateCommand}"
CommandParameter="ViewB">
Navigate to View B
</Button>
</StackPanel>
<ContentControl Margin="5" prism:RegionManager.RegionName="ContentRegion" />
</DockPanel>
</Window>
BasicRegionNavigation 内の ViewModels\MainWindowViewModel.cs
namespace BasicRegionNavigation.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public DelegateCommand<string> NavigateCommand { get; }
public MainWindowViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
}
private void Navigate(string navigatePath)
{
if (navigatePath != null)
_regionManager.RequestNavigate("ContentRegion", navigatePath);
}
}
}
重要なのはこれだけですね。
単に View をリージョン使って切り替えるだけです。
18-NavigationCallback
Descriptionには Get notifications when navigation has completed とあります。
実際に動かしてみましょう。リージョン切り替えボタンをクリックすると、メッセージボックスが表示されます。
BasicRegionNavigation 内の ViewModels\MainWindowViewModel.cs
namespace BasicRegionNavigation.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public DelegateCommand<string> NavigateCommand { get; }
public MainWindowViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
}
private void Navigate(string navigatePath)
{
if (navigatePath != null)
_regionManager.RequestNavigate("ContentRegion", navigatePath, NavigationComplete);
}
private void NavigationComplete(NavigationResult result)
{
System.Windows.MessageBox.Show(String.Format("Navigation to {0} complete. ", result.Context.Uri));
}
}
}
これ以外のファイルは前節と同じです。
結局、ナビゲートしたらメッセージボックスを出す処理が追加されただけですね。
19-NavigationParticipation
Descripionには Learn about View and ViewModel navigation participation with INavigationAware とあります。
今までとは少し毛色が違い、ViewA と ViewB を切り替えたらタブ表示で、クリック回数が表示されます。
NavigationParticipation 内の App.xaml.cs
前節と同じです。
NavigationParticipation 内の Views\MainWindow.xaml
<Window
x:Class="NavigationParticipation.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="{Binding Title}"
Width="525"
Height="350"
prism:ViewModelLocator.AutoWireViewModel="True">
<Window.Resources>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding DataContext.Title}" />
</Style>
</Window.Resources>
<DockPanel LastChildFill="True">
<StackPanel
Margin="5"
DockPanel.Dock="Top"
Orientation="Horizontal">
<Button
Margin="5"
Command="{Binding NavigateCommand}"
CommandParameter="ViewA">
Navigate to View A
</Button>
<Button
Margin="5"
Command="{Binding NavigateCommand}"
CommandParameter="ViewB">
Navigate to View B
</Button>
</StackPanel>
<TabControl Margin="5" prism:RegionManager.RegionName="ContentRegion" />
</DockPanel>
</Window>
NavigationParticipation 内の ViewModels\MainWindowViewModel.cs
前節と同じです。
ModuleA 内の ModuleAModule.cs
前節と同じです。
ModuleA 内の Views\ViewA.xaml
<UserControl
x:Class="ModuleA.Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<StackPanel>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="48"
Text="{Binding Title}" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Text="{Binding PageViews}" />
</StackPanel>
</UserControl>
この Text="{Binding PageViews}"
がボタンクリック数を反映しているのでしょう
ViewB.xaml は、ほぼ ViewA.xaml と同一なので省略します。
ModuleA 内の ViewModels\ViewAViewModel.cs
namespace ModuleA.ViewModels
{
public class ViewAViewModel : BindableBase, INavigationAware
{
private string _title = "ViewA";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private int _pageViews;
public int PageViews
{
get { return _pageViews; }
set { SetProperty(ref _pageViews, value); }
}
public ViewAViewModel() { }
public void OnNavigatedTo(NavigationContext navigationContext)
{
PageViews++;
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext) { }
}
}
ここの肝は INavigationAware
インターフェースのようですね。
OnNavigatedTo
と IsNavigationTarget
そして OnNavigatedFrom
を実装する必要があるようです。
そして、ナビゲートされたら PageView カウンタのインクリメント…と。
こちらも ViewModels\ViewBViewModel.cs は、ほぼ同一なので省略します。
…しかし、ナビゲートしたら、ナビゲートされた側が受け取るって設計は、 そこはかとなく筋の悪さを感じます。
20-NavigateToExistingViews
Descriptionには Control view instances during navigation とあります。
動きは 19-NavigationParticipation とほぼ同じに見えますね。 だけどボタン連打すると、ViewA は3の倍数の次になると新タブを作ります。 ViewB の場合は6の倍数の次になると…ですね。
ModuleA の ViewAViewModel
namespace ModuleA.ViewModels
{
public class ViewAViewModel : BindableBase, INavigationAware
{
private string _title = "ViewA";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private int _pageViews;
public int PageViews
{
get { return _pageViews; }
set { SetProperty(ref _pageViews, value); }
}
public ViewAViewModel() { }
public void OnNavigatedTo(NavigationContext navigationContext)
{
PageViews++;
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return PageViews / 3 != 1;
}
public void OnNavigatedFrom(NavigationContext navigationContext) { }
}
}
ViewBViewModel.cs はほとんど同一なので省略します。
他のコードは前節とほぼ同じなのでバッサリ省略します。
…って言うか、IsNavigationTarget
が false になったらタブが追加される処理はどこ?
またブラックボックスかよ…
21-PassingParameters
Descriptionには Pass parameters from View/ViewModel to another View/ViewModel とあります。
16-RegionContext のタブ版のようなサンプルですね。
もう面倒なので ViewModel の肝要なところだけ引用します。
ModuleA 内の ViewModels\PersonListViewModel.cs
using System.Collections.ObjectModel;
using ModuleA.Business;
namespace ModuleA.ViewModels
{
public class PersonListViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
private ObservableCollection<Person> _people;
public ObservableCollection<Person> People
{
get { return _people; }
set { SetProperty(ref _people, value); }
}
public DelegateCommand<Person> PersonSelectedCommand { get; }
public PersonListViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
PersonSelectedCommand = new DelegateCommand<Person>(PersonSelected);
CreatePeople();
}
private void PersonSelected(Person person)
{
var parameters = new NavigationParameters
{
{ "person", person }
};
if (person != null)
_regionManager.RequestNavigate("PersonDetailsRegion", "PersonDetail", parameters);
}
private void CreatePeople()
{
var people = new ObservableCollection<Person>();
for (int i = 0; i < 10; i++)
{
people.Add(new Person()
{
FirstName = String.Format("First {0}", i),
LastName = String.Format("Last {0}", i),
Age = i
});
}
People = people;
}
}
}
ModuleA 内の ViewModels\PersonDetailViewModel.cs
using ModuleA.Business;
namespace ModuleA.ViewModels
{
public class PersonDetailViewModel : BindableBase, INavigationAware
{
private Person _selectedPerson;
public Person SelectedPerson
{
get { return _selectedPerson; }
set { SetProperty(ref _selectedPerson, value); }
}
public PersonDetailViewModel() { }
public void OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.Parameters["person"] is Person person)
SelectedPerson = person;
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
if (navigationContext.Parameters["person"] is Person person)
return SelectedPerson != null && SelectedPerson.LastName == person.LastName;
else
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext) { }
}
}
どうやら IsNavigationTarget
が false を返したら、新しいタブが作成されるようですね。
本当に投げやりでごめんなさい。
一旦締めますね
こう…ほとんど肝となるコードを列挙するだけになって、本当に申し訳ありません。
次回は、先ほども書いた通り WPFにおけるPrism集中講座番外編:16-RegionContextをCommunityToolkit.Mvvmでリファクタリングする となります。