私はWPFとMVVMの問題を学習しようとしていますが、思わぬ障害に見舞われました。この質問は似ていますが、まったく同じではありません この質問(handling-dialogs-in-wpf-with-mvvm)と同じ ...
MVVMパターンを使用して記述された「ログイン」フォームがあります。
このフォームには、通常のデータバインディングを使用してXAMLのビューにバインドされるユーザー名とパスワードを保持するViewModelがあります。また、通常のデータバインディングを使用して、フォームの「ログイン」ボタンにバインドされた「ログイン」コマンドもあります。
「ログイン」コマンドが起動すると、ViewModelの関数が呼び出され、ネットワークからデータを送信してログインします。この関数が完了すると、2つのアクションがあります。
ログインが無効でした-MessageBoxを表示するだけで問題ありません
ログインは有効でした。ログインフォームを閉じて、DialogResult
...としてtrueを返す必要があります。
問題は、ViewModelが実際のビューについて何も知らないため、どのようにしてビューを閉じて特定のDialogResultを返すように指示できるのかということです。 CodeBehindにコードを貼り付けたり、ViewをViewModelに渡したりできますが、MVVMのすべてのポイントを完全に無効にするようです...
最後に、MVVMパターンの「純度」に違反し、ビューでClosed
イベントを発行し、Close
メソッドを公開しました。 ViewModelはview.Close
を呼び出すだけです。ビューはインターフェースを介してのみ認識され、IOCコンテナーを介して接続されるため、テスト容易性や保守性は失われません。
受け入れられた答えが-5票であることはかなりばかげているようです! 「純粋」であると同時に問題を解決することで得られる良い気持ちをよく知っていますが、たった1行の方法を避けるために200行のイベント、コマンド、および動作を考えるのは私だけではありません「パターン」と「純度」の名前は少しばかげています。..
Thejuanの答え に触発されて、よりシンプルな添付プロパティを記述しました。スタイルもトリガーもありません。代わりに、これを行うことができます:
<Window ...
xmlns:xc="clr-namespace:ExCastle.Wpf"
xc:DialogCloser.DialogResult="{Binding DialogResult}">
これは、WPFチームが正しく理解し、DialogResultを最初から依存関係プロパティにした場合とほぼ同じくらいきれいです。 ViewModelにbool? DialogResult
プロパティを配置し、INotifyPropertyChangedを実装するだけで、ViewModelはプロパティを設定するだけでWindowを閉じる(およびDialogResultを設定する)ことができます。 MVVMがあるはずです。
DialogCloserのコードは次のとおりです。
using System.Windows;
namespace ExCastle.Wpf
{
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null)
window.DialogResult = e.NewValue as bool?;
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
}
私もこれを投稿しました 私のブログに 。
私の観点からは、「ログイン」ウィンドウだけでなく、あらゆる種類のウィンドウに同じアプローチが使用されるため、質問はかなり良いです。私は多くの提案を検討しましたが、どれも私にとっては大丈夫です。 MVVMデザインパターンの記事 から引用した私の提案を確認してください。
各ViewModelクラスは、WorkspaceViewModel
イベントおよびRequestClose
タイプのCloseCommand
プロパティを持つICommand
から継承する必要があります。 CloseCommand
プロパティのデフォルト実装は、RequestClose
イベントを発生させます。
ウィンドウを閉じるには、ウィンドウのOnLoaded
メソッドをオーバーライドする必要があります。
void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
DataContext = customer;
customer.RequestClose += () => { Close(); };
}
またはOnStartup
アプリのメソッド:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
var viewModel = new MainWindowViewModel();
viewModel.RequestClose += window.Close;
window.DataContext = viewModel;
window.Show();
}
RequestClose
のCloseCommand
イベントとWorkspaceViewModel
プロパティの実装はかなり明確であると思いますが、一貫性があることを示します。
public abstract class WorkspaceViewModel : ViewModelBase
// There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
RelayCommand _closeCommand;
public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
{
_closeCommand = new RelayCommand(
param => Close(),
param => CanClose()
);
}
return _closeCommand;
}
}
public event Action RequestClose;
public virtual void Close()
{
if ( RequestClose != null )
{
RequestClose();
}
}
public virtual bool CanClose()
{
return true;
}
}
RelayCommand
のソースコード:
public class RelayCommand : ICommand
{
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
}
P.S。それらのソースに対して私を酷く扱わないでください!昨日それらを持っていたら、数時間節約できただろう...
P.P.S。コメントや提案は大歓迎です。
添付のビヘイビアを使用してウィンドウを閉じました。 ViewModelの「signal」プロパティをアタッチされた動作にバインドします(実際にはトリガーを使用します)trueに設定すると、動作によりウィンドウが閉じます。
http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/
ここでMVVMの長所と短所を主張する多くのコメントがあります。私にとっては、Nirに同意します。それはパターンを適切に使用することの問題であり、MVVMは常に適合しません。人々は、MVVMに適合するようにソフトウェア設計の最も重要な原則すべてを犠牲にすることをいとわなくなったようです。
そうは言っても、あなたのケースは少しのリファクタリングでうまく適合すると思います。
ほとんどの場合、WPFを使用すると、複数のWindow
sなしで取得できます。 Windowsの代わりにFrame
sでPage
sとDialogResult
sを使用してみてください。
あなたの場合、私の提案はLoginFormViewModel
がLoginCommand
を処理し、ログインが無効である場合、LoginFormViewModel
のプロパティを適切な値に設定します(false
またはいくつかの列挙値UserAuthenticationStates.FailedAuthentication
など)。ログインに成功した場合も同じことをします(true
またはその他の列挙値)。その後、さまざまなユーザー認証状態に応答するDataTrigger
を使用し、簡単なSetter
を使用してSource
のFrame
プロパティを変更できます。
ログインウィンドウにDialogResult
を返すようにすると、混乱するのではないでしょうか。 DialogResult
は、実際にはViewModelのプロパティです。 WPFでの明らかに制限された経験では、WinFormsで同じことをどのようにしたかという点で考えているために、何かが適切に感じられない場合があります。
お役に立てば幸いです。
ログインダイアログが最初に作成されるウィンドウであると仮定して、LoginViewModelクラス内でこれを試してください。
void OnLoginResponse(bool loginSucceded)
{
if (loginSucceded)
{
Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
window.Show();
App.Current.MainWindow.Close();
App.Current.MainWindow = window;
}
else
{
LoginError = true;
}
}
私がそれを処理する方法は、ViewModelにイベントハンドラーを追加することです。ユーザーが正常にログインすると、イベントが発生します。私のビューでは、このイベントにアタッチし、イベントが発生したらウィンドウを閉じます。
これはシンプルでクリーンなソリューションです。ViewModelにイベントを追加し、そのイベントが発生したときにウィンドウを閉じるように指示します。
詳細については、私のブログ投稿 ViewModelからウィンドウを閉じる を参照してください。
私が最初にやったことはここにありますが、動作しますが、かなり長くてandいようです(グローバルな静的なものは決して良いことではありません)
1:App.xaml.cs
public partial class App : Application
{
// create a new global custom WPF Command
public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}
2:LoginForm.xaml
// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />
3:LoginForm.xaml.cs
// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
DialogResult = true;
Close();
}
4:LoginFormViewModel.cs
// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
App.LoggedIn.Execute(this, null);
}
後でこのコードをすべて削除し、LoginFormViewModel
がそのビューでCloseメソッドを呼び出すようにしました。最終的にはずっと良くなり、フォローしやすくなりました。私見のパターンのポイントは、アプリが何をしているのかを人々が簡単に理解できるようにすることであり、この場合、MVVMは私がそれを使用していなかった場合よりも理解するのがはるかに難しくなり、今ではanti-pattern。
これはおそらく非常に遅いですが、私は同じ問題に遭遇し、私に合った解決策を見つけました。
私はダイアログなしでアプリを作成する方法を理解することはできません(多分それは単なる頭脳ブロックです)。だから私はMVVMに行き詰まり、ダイアログを表示していました。だから私はこのCodeProjectの記事に出会いました:
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
これは、基本的にウィンドウを別のウィンドウのビジュアルツリー内に配置できるようにするUserControlです(xamlでは許可されていません)。また、IsShowingと呼ばれるブール型のDependencyPropertyも公開します。
コントロールのContentプロパティがトリガーを介して!= nullの場合に基本的にダイアログを表示するような、通常はリソース辞書のようなスタイルを設定できます。
<Style TargetType="{x:Type d:Dialog}">
<Style.Triggers>
<Trigger Property="HasContent" Value="True">
<Setter Property="Showing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
ダイアログを表示するビューでは、次のようにします。
<d:Dialog Content="{Binding Path=DialogViewModel}"/>
そして、ViewModelで行う必要があるのは、プロパティを値に設定することだけです(注:ViewModelクラスは、ビューが発生したことを知るためにINotifyPropertyChangedをサポートする必要があります)。
そのようです:
DialogViewModel = new DisplayViewModel();
ViewModelとViewを一致させるには、リソースディクショナリに次のようなものが必要です。
<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
<vw:DisplayView/>
</DataTemplate>
これらすべてで、ダイアログを表示するための1行のコードを取得します。問題は、上記のコードだけではダイアログを本当に閉じることができないことです。そのため、DisplayViewModelが継承するViewModel基本クラスにイベントを入れる必要があり、上記のコードの代わりに、これを記述します。
var vm = new DisplayViewModel();
vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
DialogViewModel = vm;
その後、コールバックを介してダイアログの結果を処理できます。
これは少し複雑に思えるかもしれませんが、基礎を築いた後は非常に簡単です。繰り返しますが、これは私の実装です。他にもあるはずです:)
これがお役に立てば幸いです。
そう、この質問は6歳に近く、ここでそれが正しい答えだと思うものがまだ見つからないので、私の「2セント」を共有させてください...
実際には2つの方法があります。1つ目は簡単な方法です... 2つ目は正しい方法です。したがって、正しい方法を探している場合は、#1をスキップして#2にジャンプしてください =:
小さなプロジェクトがある場合、ViewModelでCloseWindowActionを作成することがあります。
public Action CloseWindow { get; set; } // In MyViewModel.cs
そして、ビューを作成する人、またはビューのコードビハインドで、アクションが呼び出すメソッドを設定するだけです。
(MVVMはビューとViewModelの分離に関することを忘れないでください...ビューのコードビハインドはまだビューであり、適切な分離がある限り、パターンに違反していません)
ViewModelが新しいウィンドウを作成する場合:
private void CreateNewView()
{
MyView window = new MyView();
window.DataContext = new MyViewModel
{
CloseWindow = window.Close,
};
window.ShowDialog();
}
または、メインウィンドウに表示する場合は、ビューのコンストラクターの下に配置します。
public MyView()
{
InitializeComponent();
this.DataContext = new MainViewModel
{
CloseWindow = this.Close
};
}
ウィンドウを閉じたいときは、ViewModelでActionを呼び出すだけです。
これを行う適切な方法は、Prism(IMHO)を使用することです。これについては ここにあります になります。
インタラクションリクエストを作成し、新しいウィンドウで必要なデータを入力し、ランチし、閉じ、さらにデータを受信することもできます。このすべてがカプセル化され、MVVMが承認されました。あなたもウィンドウが閉じられた状態のステータスを取得、たとえば、ユーザーCanceled
またはAccepted
([OK]ボタン)ウィンドウと必要に応じてデータを戻す] it。少し複雑で回答#1ですが、より完全であり、Microsoftの推奨パターンです。
私が提供したリンクにはすべてのコードスニペットと例がありますので、ここにコードを配置することはありません。Prismクイックスタートをダウンロードして実行する記事を読んでください。もう少し冗長に理解するのは本当に簡単です。動作させますが、利点はウィンドウを閉じるだけの場合よりも大きくなります。
public partial class MyWindow: Window
{
public ApplicationSelection()
{
InitializeComponent();
MyViewModel viewModel = new MyViewModel();
DataContext = viewModel;
viewModel.RequestClose += () => { Close(); };
}
}
public class MyViewModel
{
//...Your code...
public event Action RequestClose;
public virtual void Close()
{
if (RequestClose != null)
{
RequestClose();
}
}
public void SomeFunction()
{
//...Do something...
Close();
}
}
参考までに、私はこの同じ問題にぶつかったので、グローバルまたは静的を必要としない回避策を見つけたと思いますが、それは最良の答えではないかもしれません。私はあなた達にそれを自分で決めさせます。
私の場合、表示するWindowをインスタンス化するViewModel(ViewModelMainと呼びます)はLoginFormViewModelについても知っています(上記の状況を例として使用)。
そのため、ICommand型のプロパティをLoginFormViewModelに作成しました(CloseWindowCommandと呼びます)。次に、Windowで.ShowDialog()を呼び出す前に、LoginFormViewModelのCloseWindowCommandプロパティを、インスタンス化したWindowのwindow.Close()メソッドに設定します。その後、LoginFormViewModel内で行う必要があるのは、CloseWindowCommand.Execute()を呼び出してウィンドウを閉じることだけです。
これはちょっとした回避策/ハックですが、MVVMパターンを実際に壊すことなく機能します。
このプロセスを好きなだけ批判してください、私はそれを取ることができます! :)
ViewModelに、Viewが登録するイベントを公開させることができます。次に、ViewModelがビューを閉じる時間を決定すると、ビューを閉じるイベントが発生します。特定の結果値を返したい場合は、そのためのViewModelにプロパティがあります。
膨大な数の回答に追加するために、以下を追加します。 ViewModelにICommandがあり、そのコマンドでそのウィンドウ(またはその他のアクション)を閉じたい場合、次のようなものを使用できます。
var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
if (windows[i].DataContext == this)
windows[i].Close();
完全ではなく、テストするのは難しいかもしれません(静的なモック/スタブが難しいため)が、他のソリューションよりもクリーン(IMHO)です。
エリック
別の解決策は、DialogResultのようなビューモデルでINotifyPropertyChangedを使用してプロパティを作成し、次にコードビハインドでこれを記述します。
public class SomeWindow: ChildWindow
{
private SomeViewModel _someViewModel;
public SomeWindow()
{
InitializeComponent();
this.Loaded += SomeWindow_Loaded;
this.Closed += SomeWindow_Closed;
}
void SomeWindow_Loaded(object sender, RoutedEventArgs e)
{
_someViewModel = this.DataContext as SomeViewModel;
_someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
}
void SomeWindow_Closed(object sender, System.EventArgs e)
{
_someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
this.Loaded -= SomeWindow_Loaded;
this.Closed -= SomeWindow_Closed;
}
void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
{
this.DialogResult = _someViewModel.DialogResult;
}
}
}
最も重要なフラグメントは_someViewModel_PropertyChanged
です。 DialogResultPropertyName
は、SomeViewModel
のパブリックconst文字列にすることができます。
この種のトリックを使用して、ViewModelでこれを行うのが難しい場合に備えて、View Controlでいくつかの変更を行います。 ViewModelのOnPropertyChangedを使用すると、Viewで任意の操作を実行できます。 ViewModelは依然として「ユニットテスト可能」であり、コードビハインドのいくつかの小さなコード行は違いを生じません。
私はすべての答えを読みましたが、私は言わなければなりません、それらのほとんどはちょうど十分に良くないか、さらに悪いです。
ダイアログウィンドウを表示してダイアログ結果を返す責任があるDialogServiceクラスを使用すると、これを美しく処理できます。 create sample project の実装と使用法を示しています。
最も重要な部分は次のとおりです。
//we will call this interface in our viewmodels
public interface IDialogService
{
bool? ShowDialog(object dialogViewModel, string caption);
}
//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
public string Message {get; set;}
public void ShowLoginCommandExecute()
{
var loginViewModel = new LoginViewModel();
var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");
//after dialog is closed, do someting
if (dialogResult == true && loginViewModel.IsLoginSuccessful)
{
this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
}
}
}
public class DialogService : IDialogService
{
public bool? ShowDialog(object dialogViewModel, string caption)
{
var contentView = ViewLocator.GetView(dialogViewModel);
var dlg = new DialogWindow
{
Title = caption
};
dlg.PART_ContentControl.Content = contentView;
return dlg.ShowDialog();
}
}
これは単純ではありませんか? EventAggregatorまたは他の同様のソリューションよりも、より簡単で読みやすく、最後になりますが、デバッグが簡単ですか?
あなたが見ることができるように、私のビューモデルでは、私の投稿で説明されているViewModelの最初のアプローチを使用しています: WPFでViewModelからViewを呼び出すためのベストプラクティス
もちろん、実際には、DialogService.ShowDialog
にはダイアログを設定するためのオプションがさらに必要です。実行するボタンとコマンド。異なる方法がありますが、範囲外です:)
Dependency Property
をView
// UserControl
(または閉じたいWindow
)に作成します。以下のように:
public bool CloseTrigger
{
get { return (bool)GetValue(CloseTriggerProperty); }
set { SetValue(CloseTriggerProperty, value); }
}
public static readonly DependencyProperty CloseTriggerProperty =
DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged)));
private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
//write Window Exit Code
}
そして、あなたのViewModelのプロパティからバインドします:
<Window x:Class="WpfStackOverflowTempProject.MainWindow"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="525"
CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"
プロパティ入力VeiwModel
:
private bool closeWindow;
public bool CloseWindow
{
get { return closeWindow; }
set
{
closeWindow = value;
RaiseChane("CloseWindow");
}
}
次に、ViewModelでCloseWindow
値を変更して、クローズ操作をトリガーします。 :)
これは、ビューモデルを介してこれを行う方法の質問には答えませんが、XAML +ブレンドSDKのみを使用して行う方法を示しています。
Blend SDKから2つのファイルをダウンロードして使用することにしました。どちらもMicrosoftからNuGetを介してパッケージとして使用できます。ファイルは次のとおりです。
System.Windows.Interactivity.dllおよびMicrosoft.Expression.Interactions.dll
Microsoft.Expression.Interactions.dllは、ビューモデルや他のターゲットでプロパティを設定したり、メソッドを呼び出したり、他のウィジェットも内部に持つなどの素晴らしい機能を提供します。
一部のXAML:
<Window x:Class="Blah.Blah.MyWindow"
...
xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.Microsoft.com/expression/2010/interactions"
...>
<StackPanel>
<Button x:Name="OKButton" Content="OK">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction
TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
PropertyName="DialogResult"
Value="True"
IsEnabled="{Binding SomeBoolOnTheVM}" />
</i:EventTrigger>
</Button>
<Button x:Name="CancelButton" Content="Cancel">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction
TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
PropertyName="DialogResult"
Value="False" />
</i:EventTrigger>
</Button>
<Button x:Name="CloseButton" Content="Close">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<!-- method being invoked should be void w/ no args -->
<ei:CallMethodAction
TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
MethodName="Close" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<StackPanel>
</Window>
単純なOK/Cancel動作だけを行う場合は、WindowがWindow.ShowDialog()で表示されている限り、IsDefaultプロパティとIsCancelプロパティを使用して逃げることができます。
個人的にIsDefaultプロパティがtrueに設定されたボタンで問題が発生しましたが、ページが読み込まれると非表示になりました。それが表示された後はうまく再生したくないようでしたので、代わりに上記のようにWindow.DialogResultプロパティを設定しているだけで、私にとってはうまくいきます。
プログラムで作成されたウィンドウにユーザーコントロールを表示する必要があるため、最終的に Joe Whiteの回答 と Adam Millsの回答 のコードをブレンドしました。したがって、DialogCloserはウィンドウ上にある必要はなく、ユーザーコントロール自体にあることができます
<UserControl ...
xmlns:xw="clr-namespace:Wpf"
xw:DialogCloser.DialogResult="{Binding DialogResult}">
また、DialogCloserは、ウィンドウ自体にアタッチされていない場合、ユーザーコントロールのウィンドウを見つけます。
namespace Wpf
{
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d.GetWindow();
if (window != null)
window.DialogResult = e.NewValue as bool?;
}
public static void SetDialogResult(DependencyObject target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
public static class Extensions
{
public static Window GetWindow(this DependencyObject sender_)
{
Window window = sender_ as Window;
return window ?? Window.GetWindow( sender_ );
}
}
}
私はこのように行きます:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
// View
public partial class TestCloseWindow : Window
{
public TestCloseWindow() {
InitializeComponent();
Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
}
}
// View Model
public class MainViewModel: ViewModelBase
{
ICommand _closeChildWindowCommand;
public ICommand CloseChildWindowCommand {
get {
return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
Messenger.Default.Send(new CloseWindowMsg());
}));
}
}
}
public class CloseWindowMsg
{
}
ここでは、動作が最も便利な方法です。
一方から、指定されたビューモデルにバインドできます(「フォームを閉じる!」
別の手からは、フォーム自体にアクセスできるため、必要なフォーム固有のイベントをサブスクライブしたり、確認ダイアログなどを表示したりできます。
必要な振る舞いを書くことは、非常に初めての退屈なものです。ただし、今後は、正確な1行のXAMLスニペットによって、必要なすべてのフォームで再利用できます。また、必要に応じて、別のアセンブリとして抽出して、必要な次のプロジェクトに含めることができます。
ウィンドウをコマンドパラメーターとして渡すだけではどうですか?
C#:
private void Cancel( Window window )
{
window.Close();
}
private ICommand _cancelCommand;
public ICommand CancelCommand
{
get
{
return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
( window ) => Cancel( window ),
( window ) => ( true ) ) );
}
}
XAML:
<Window x:Class="WPFRunApp.MainWindow"
x:Name="_runWindow"
...
<Button Content="Cancel"
Command="{Binding Path=CancelCommand}"
CommandParameter="{Binding ElementName=_runWindow}" />
Joe Whiteのソリューションを実装しましたが、「DialogResultはWindowの作成後にのみ設定でき、dialog」エラーとして表示される問題が発生しました。
Viewが閉じられた後もViewModelを保持していましたが、ときどき同じVMを使用して新しいViewを開きました。古いビューがガベージコレクションされる前に新しいビューを閉じると、DialogResultChanged閉じたウィンドウでDialogResultプロパティを設定しようとしてエラーが発生したようです。
私の解決策は、DialogResultChangedを変更して、ウィンドウのIsLoadedプロパティをチェックすることでした。
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null && window.IsLoaded)
window.DialogResult = e.NewValue as bool?;
}
この変更を行った後、閉じたダイアログへの添付ファイルは無視されます。
これは単純なバグのないソリューション(ソースコード付き)です。
INotifyPropertyChanged
からViewModelを派生します
観察可能なプロパティを作成CloseDialog ViewModel
public void Execute()
{
// Do your task here
// if task successful, assign true to CloseDialog
CloseDialog = true;
}
private bool _closeDialog;
public bool CloseDialog
{
get { return _closeDialog; }
set { _closeDialog = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string property = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
このプロパティの変更について、Viewでハンドラーをアタッチします
_loginDialogViewModel = new LoginDialogViewModel();
loginPanel.DataContext = _loginDialogViewModel;
_loginDialogViewModel.PropertyChanged += OnPropertyChanged;
これでほぼ完了です。イベントハンドラーでDialogResult = true
を作成します
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "CloseDialog")
{
DialogResult = true;
}
}