1

WindowManager次のように、事前に開始されたダイアログビューモデルにバインドされたダイアログウィンドウを開くダイアログサービス(と呼ばれる)を備えたMVVM-LightベースのWPFアプリケーションがあります。

private enum ViewModelKind
{
    PlanningGridVM,
    InputDialogVM,
    TreeViewDialogVM,
    SaveFileDialogVM,
    MessageBoxVM
}

/// <summary>
/// Shows the Window linked to this ViewModel as a dialog window.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model.</typeparam>
/// <returns>Tri-state boolean dialog response.</returns>
public bool? ShowDialog<TViewModel>(string key = null)
{
    ViewModelKind name;

    // Attempt to parse the type-parameter to enum
    Enum.TryParse(typeof(TViewModel).Name, out name);

    Window view = null;
    switch (name)
    {
        // removed some irrelevant cases...
        case ViewModelKind.InputDialogVM:
            view = new InputDialogView();
            System.Diagnostics.Debug.WriteLine(
                view.GetHashCode(), "New Window HashCode");
            view.Height = 200;
            result = view.ShowDialog();

        default:
            return true;
    }
}

ダイアログの XAML は次のように始まります。

<Window
    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:b="clr-namespace:MyCompany.Common.Behaviours"

    x:Class="MyCompany.Common.Views.InputDialogView" mc:Ignorable="d"
    DataContext="{Binding InputDialogVM, Source={StaticResource Locator}}"
    Title="{Binding DisplayName}" MinHeight="200" MinWidth="300" MaxHeight="200"
    b:WindowBehaviours.DialogResult="{Binding DialogResult}"
    WindowStyle="ToolWindow" ShowInTaskbar="False"
    WindowStartupLocation="CenterScreen"
    Height="200" Width="300">

ビューモデルは、コンストラクターで Messenger に適切に登録され、ビューモデルのプロパティをリセットすることで初期化メッセージに応答します。これはすべて意図したとおりに機能します。

「OK/キャンセル」ダイアログを適切に閉じるために、 という添付プロパティがありDialogResult、これも期待どおりに機能します...

/// <summary>
/// DialogResult
/// </summary>
public static readonly DependencyProperty DialogResultProperty = DependencyProperty
    .RegisterAttached(
        "DialogResult",
        typeof(bool?),
        typeof(WindowBehaviours),
        new PropertyMetadata(null, DialogResultChanged));

public static void SetDialogResult(Window target, bool? value)
{
    target.SetValue(DialogResultProperty, value);
}

private static void DialogResultChanged(
    DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    var window = obj as Window;

    System.Diagnostics.Debug.WriteLine(
        window.GetHashCode(), "Attempting to update DialogResult on Hashcode");

    if (window != null && window.IsActive)
    {
        window.DialogResult = e.NewValue as bool?;
    }
}

...ただし、1 つ注意点があります。ウィンドウ インスタンスの HashCode を追跡するために追加したデバッグ出力に気付きましたか? それは私にとって次のことを確認しました:

XAML の DataContext バインディングを介してダイアログ ビューによってアクセスされる 1 つの再利用可能なビュー モデル インスタンスがあり、新しいダイアログを数回連続して開いた場合、これらのダイアログ インスタンスは、OnClosed イベントが発生した後でもopen のままです。ダイアログが表示されなくなっても!

これの正味の効果は、IsActiveウィンドウの null をチェックすることと併せて、ウィンドウのプロパティをチェックしなければならないことです。そうしないと、システムはwindow.DialogResultまだ残っているすべてのダイアログファントムに設定しようとし、System.InvalidOperationException例外が発生します:「DialogResultは、ウィンドウが作成されてダイアログとして表示された後にのみ設定できます」.


デバッグ出力

New Window HashCode: 4378943
Attempting to update DialogResult on Hashcode: 4378943

New Window HashCode: 53142588
Attempting to update DialogResult on Hashcode: 53142588

New Window HashCode: 47653507
Attempting to update DialogResult on Hashcode: 53142588
Attempting to update DialogResult on Hashcode: 47653507

New Window HashCode: 57770831
Attempting to update DialogResult on Hashcode: 53142588
Attempting to update DialogResult on Hashcode: 57770831

New Window HashCode: 49455573
Attempting to update DialogResult on Hashcode: 53142588
Attempting to update DialogResult on Hashcode: 57770831
Attempting to update DialogResult on Hashcode: 49455573

New Window HashCode: 20133242
Attempting to update DialogResult on Hashcode: 53142588
Attempting to update DialogResult on Hashcode: 57770831
Attempting to update DialogResult on Hashcode: 49455573
Attempting to update DialogResult on Hashcode: 20133242

質問

アタッチされたビヘイビアーがインスタンスに固有のプロパティの値を格納するということを何度も見てきました。なぜこれは反対の方法で動作するのですか?

これらの有効期限が切れたダイアログがまだ単一のビューモデル インスタンスの INPC イベントに登録されていることは明らかです。閉じたダイアログが INPC イベントから登録解除されるようにするにはどうすればよいですか?

4

1 に答える 1

1

私を正しい方向に向けてくれたKessに感謝します...問題は、ダイアログインスタンスを破棄することではなく、各ダイアログインスタンスに対して一意のビューモデルインスタンスがあることを確認することでした! Cleanup()次に、組み込みメソッドを使用して Messenger からビューモデルを登録解除するだけです。

トリックは、メソッドを使用GetInstance<T>(string key)ServiceLocatorて、そのキーをに渡すことWindowManagerです。


解決

InputDialogView XAML:

DataContext を ViewModelLocator プロパティに割り当てる行を削除します

DataContext="{Binding InputDialogVM, Source={StaticResource Locator}}"

ウィンドウマネージャー:

ShowDialog に渡された文字列キーを使用し、(キーと共に) ServiceLocator を使用して一意のビューモデルを取得し、view.DataContext を明示的に設定します。

public bool? ShowDialog<TViewModel>(string key = null)
{
    ViewModelKind name;

    Enum.TryParse(typeof(TViewModel).Name, out name);

    Window view = null;
    switch (name)
    {
        case ViewModelKind.InputDialogVM:
            view = new InputDialogView();

            // Added this line
            view.DataContext = ServiceLocator.Current
                .GetInstance<InputDialogVM>(key);

            view.Height = 200;
            result = view.ShowDialog();

        default:
            return true;
    }
}

MainViewModel:

ServiceLocator一意の文字列キーを使用して新しいビュー モデルを作成する

internal void ExecuteChangeState()
{
    string key = Guid.NewGuid().ToString();
    InputDialogVM viewModel = ServiceLocator.Current
        .GetInstance<InputDialogVM>(key);

    // Use a nested tuple class here to send initialization parameters
    var inputDialogParams = new InputDialogVM.InitParams(
        "Please provide a reason", "Deactivation Prompt", true);

    // This message sends some critical values to all registered VM instances
    Messenger.Default.Send(new NotificationMessage<InputDialogVM.InitParams>(
        inputDialogParams, CommonMsg.InitInputDialog));

    if (_windowManager.ShowDialog<InputDialogVM>(key) == true)
    {
        var logEntry = new DealsLogEntry()
        {
            LogDateTime = DateTime.Now,
            LogUsername = this.CurrentUser.Username,
            Reason = viewModel.InputString
        };

        _logRepository.Create(logEntry);
    }

    // Unregister this instance from the Messenger class
    viewModel.Cleanup();
}
于 2014-06-05T12:25:00.633 に答える