Home > Net >  WPF: VM-first, close Window in a MVVM-conform way
WPF: VM-first, close Window in a MVVM-conform way

Time:11-23

in my application I'm opening a Window for an Input form. In my App.xaml I have defined the following:

            <DataTemplate DataType="{x:Type ViewModels:EditTicketViewModel}">
                <Frame>
                    <Frame.Content>
                        <Views:EditTicketView></Views:EditTicketView>
                    </Frame.Content>
                </Frame>
            </DataTemplate>

My application also has a Window service for opening windows:

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

namespace DevPortal.Interfaces
{
    public interface IWindowService
    {
        public void ShowWindow(object viewModel, bool showDialog);
    }
}

the implementation:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using DevPortal.Interfaces;
using Syncfusion.Windows.Shared;
using Syncfusion.SfSkinManager;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace DevPortal.Services
{
    public class WindowService : IWindowService
    {
        public void ShowWindow(object viewModel, bool showDialog)
        {
            var window = new ChromelessWindow();
            window.ResizeMode = ResizeMode.NoResize;
            SfSkinManager.SetTheme(window, new Theme("FluentDark"));
            window.Content = viewModel;
            window.SizeToContent = SizeToContent.WidthAndHeight;
            window.Title = viewModel.GetType().GetProperty("Title").GetValue(viewModel).ToString();
            window.ShowIcon = false;

            if (showDialog)
            {
                window.ShowDialog();
            } else
            {
                window.Show();
            }

        }
    }
}

How I open the window (from a viewmodel in the MainView)

    [RelayCommand]
    private void CreateTicket()
    {
        App.Current.ServiceProvider.GetService<IWindowService>().ShowWindow(new EditTicketViewModel(), true);
    }

What would be the best way to close this window from the ViewModel? Previously i was used to directly create the view, and in the constructor of the view i would subscribe to a close-event in the viewmodel, but that's not really the MVVM-way I guess. Do I need to implement some kind of service? Thanks!

EDIT: I forgott to mention that the View is a page. So i Am creating a window with the viewmodel as content, and the datatemplate of the viewmodel is a Frame containing the page.

CodePudding user response:

You could for example return an IWindow from your window service:

public class WindowService : IWindowService
{
    public IWindow ShowWindow(object viewModel, bool showDialog)
    {
        var window = new ChromelessWindow();
        ...
        return window;
    }
}

...and then simply call Close() on this one in the view model.

The interface would be as simple as this:

public interface IWindow
{
    void Close();
}

Your ChromelessWindow implements the interface:

public partial class ChromelessWindow : Window, IWindow { ... }

...and the view model only has a dependency on an interface. It still doesn't know anything about a view or actual window. IWindow is just a name. I can be called anything.

CodePudding user response:

The cleanest way of closing a Window from it's ViewModel is using an attached property.

public static class perWindowHelper
{
    public static readonly DependencyProperty CloseWindowProperty = DependencyProperty.RegisterAttached(
        "CloseWindow",
        typeof(bool?),
        typeof(perWindowHelper),
        new PropertyMetadata(null, OnCloseWindowChanged));

    private static void OnCloseWindowChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
    {
        if (!(target is Window view))
        {
            return;
        }

        if (view.IsModal())
        {
            view.DialogResult = args.NewValue as bool?;
        }
        else
        {
            view.Close();
        }
    }

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

    public static bool IsModal(this Window window)
    {
        var fieldInfo = typeof(Window).GetField("_showingAsDialog", BindingFlags.Instance | BindingFlags.NonPublic);
        return fieldInfo != null && (bool)fieldInfo.GetValue(window);
    }
}

In the ViewModel create a ViewClosed property

private bool? _viewClosed;

public bool? ViewClosed
{
    get => _viewClosed;
    set => Set(nameof(ViewClosed), ref _viewClosed, value);
}

then in the View, bind to it using the attached property

<Window
    ...
    vhelp:perWindowHelper.CloseWindow="{Binding ViewClosed}" >
  • Related