ほとんどのビューモデルは、WPFプロジェクトでPrismのEventAggregatorを使用して共通のイベントを購読します。基本的には、ボーカルコマンドがビューでこのイベントをトリガし、レスポンスとしてビューは特定のメッセージを含む別のイベントをテキスト読み上げモジュールに公開します。 しかし、これを実装すると、RegionManagerのRequestNavigateを使用して別のビューに切り替えると、以前のビューモデルがまだ何らかの形でアクティブになっていることがわかりました。最新のビューの共通イベントをトリガーすると、以前のビューでもトリガーされます。WPF/PrismでRequestNavigateでビューを切り替えるとViewModels(と多分Views)がアクティブになる
簡体例:
- スタートビュー1
- トリガー共通のイベントで
- 応答:2
- トリガー共通イベント
- レスポンスを表示するビュー1からのメッセージ
- RequestNavigate:表示2のメッセージ、表示1のメッセージ
- 3は、トリガ共通イベント
- 応答表示する210 RequestNavigate:ビュー3からのメッセージは、2を表示し、その後Iが表示1、表示2にブレークポイントを置い1
- 等
見るとView 3の共通イベントを表示し、ビューからメッセージを取得するたびにブレークポイントにヒットします。
私が好きなのは簡単です:以前のViewModel(とViewも可能)は、Viewsを切り替えるときに何らかの形でアクティブにしたくありません。 View 1、View 2、View 1に再度移動すると、View 1のメッセージが2回送信され(ブレークポイントも2回ヒットした)、いくつかの奇妙なケースがあったため、ガベージコレクションを行う方が良いでしょう。 ViewModelの複数の参照が作成されていると、メモリリークを引き起こす可能性があるかどうかはわかりません。
基本的なことだけで別のプロジェクトを作成してこの動作を再現しようとしたので、ここにコードがあります。私は.NET Framework 4.5.2とNinjectでVisual Studio 2017を使用しています。
Shell.xaml
<Window x:Class="PrismTest.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:prsm="http://prismlibrary.com/"
mc:Ignorable="d">
<Grid>
<ContentControl Name="MainRegion" prsm:RegionManager.RegionName="MainRegion" />
</Grid>
</Window>
NinjectPrismBootstrapper.cs
public class NinjectPrismBootstrapper : NinjectBootstrapper
{
protected override void InitializeModules()
{
base.InitializeModules();
// Text to speech
Kernel.Bind<SpeechSynthesizer>().ToSelf().InSingletonScope();
Kernel.Bind<INarrator>().To<StandardNarrator>().InSingletonScope();
Kernel.Bind<INarratorEventManager>().To<NarratorEventManager>().InSingletonScope();
// View models
Kernel.Bind<MainPageViewModel>().ToSelf();
Kernel.Bind<SecondPageViewModel>().ToSelf();
// Views
Kernel.Bind<object>().To<MainPageView>().InTransientScope().Named(typeof(MainPageView).Name);
Kernel.Bind<object>().To<SecondPageView>().InTransientScope().Named(typeof(SecondPageView).Name);
Kernel.Bind<Shell>().ToSelf();
var narratorEventManager = Kernel.Get<INarratorEventManager>();
var regionManager = Kernel.Get<IRegionManager>();
regionManager.RegisterViewWithRegion("MainRegion", typeof(MainPageView));
}
protected override DependencyObject CreateShell()
{
return (Shell)Kernel.GetService(typeof(Shell));
}
protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.MainWindow = (Shell)this.Shell;
Application.Current.MainWindow.Show();
}
}
MainPageView.xaml(私の開始ページ)
<UserControl x:Class="PrismTest.Views.MainPageView"
namespaces...>
<StackPanel>
<TextBlock Text="Main page"/>
<Button Content="Narrator speaks" Command="{Binding Path=NarratorSpeaksCommand}" />
<Button Content="Next page" Command="{Binding Path=GoToNextPageCommand}"/>
</StackPanel>
</UserControl>
MainPageView.xaml.cs
public partial class MainPageView : UserControl
{
public MainPageView(MainPageViewModel dataContext)
{
InitializeComponent();
this.DataContext = dataContext;
}
}
それはRequestNavigate除いてまったく同じコードだから、私は、SecondPageViewModelとSecondPageViewのためのコードを配置する必要はありませんMainPageViewModel(MainPageViewを見るモデル)
public class MainPageViewModel : BindableBase, IRegionMemberLifetime, INavigationAware
{
private readonly IEventAggregator _eventAggregator;
private readonly IRegionManager _regionManager;
public DelegateCommand GoToNextPageCommand { get; private set; }
public DelegateCommand NarratorSpeaksCommand { get; private set; }
public MainPageViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
{
_eventAggregator = eventAggregator;
_regionManager = regionManager;
ConfigureCommands();
//The original common event triggered by a vocal command is simulated in this project by simply clicking on a button
_eventAggregator.GetEvent<CommonEventToAllViews>().Subscribe(NarratorSpeaks);
}
private void ConfigureCommands()
{
GoToNextPageCommand = new DelegateCommand(GoToNextPage);
NarratorSpeaksCommand = new DelegateCommand(ClickPressed);
}
private void GoToNextPage()
{
_regionManager.RequestNavigate("MainRegion", new Uri("SecondPageView", UriKind.Relative));
}
private void ClickPressed()
{
_eventAggregator.GetEvent<CommonEventToAllViews>().Publish();
}
private void NarratorSpeaks()
{
_eventAggregator.GetEvent<NarratorSpeaksEvent>().Publish("Main page");
}
}
はバックMainPageViewにユーザーを送信し、そのNarratorSpeaks方法は、送信別の文字列。私が試した何
:
1)MainPageViewModelとSecondPageViewModelはIRegionMemberLifetimeを継承作成し、偽
2にキープアライブを設定すること)継承INavigationAwareとIsNavigationTarget方法
3にfalseを返す)OnNavigatedFromメソッドにこれを追加しますINavigationAwareから:
public void OnNavigatedFrom(NavigationContext navigationContext)
{
var region = _regionManager.Regions["MainRegion"];
var view = region.Views.Single(v => v.GetType().Name == "MainPageView");
region.Deactivate(view);
}
ワース注目:withou var region = _regionManager.Regions ["MainRegion"]の後にブレークポイントを置くと、非アクティブ化部分。 region.viewsを確認すると、ビューをどれだけ切り替えても、結果は1つだけです。
何も問題はありませんが、ビューを前後に切り替えると、以前のビューでイベントがトリガされ続けます。 だから、私はここで悲しいです。 ViewやViewModelをNinjectに登録して、これをトリガする方法であるかどうかはわかりませんが、もし誰かが提案をしたら、私は喜んでそれを受け取ります。
ありがとうございます!
はい、私のオリジナルプロジェクトでは、音声合成装置がメッセージを積み重ねて現在のメッセージだけを伝えるのを防ぐために、Unsubscribeを一時的な手段として使用しています。しかし、私が心配しているのは、私がView 1> View 2> View 1に戻って、View 1からのメッセージが繰り返されて、ViewModelsの複数の参照が作成されていて、ゴミで処理されていない可能性がある結果としてメモリリークが発生する可能性があります。私はこのビューの切り替えをして5mn待ってみましたが、私はまだ同じメッセージが処理されていました。 –
私は、未使用の参照を処理するためにガベージコレクタにどれくらいの時間がかかるか分かりません。私はWPFとPrismの初心者ですから、間違っていないことを確認したかったのです。実際には使用されていないViewModelsの通常の動作ですか? –
@Bob_ZombX:GCは決定的ではなく、システムのアイドルまたはメモリ圧で実行できます。あなたがビューへの参照を保持しない限り、それは収集する必要があります。 INavigationAwareがViewインスタンスを再利用できることを考慮してください。 –