MVVM - Wyświetlanie messagebox i zamykanie okna z potwierdzeniem

0

Cześć,
Ponownie mam dwa pytania co do MVVM...
Potrzebuję wywołać messagebox oraz (tu nie wiem jak to się nazywa i jak tego poszukać) okienka wyboru przy kliknięciu na X do zamykania programu, chcę żeby użytkownik zdecydował czy zamknąć okno i zapisać zmiany czy nie zapisywać.
Co do pierwszego czyli messagebox to widziałem różne rozwiązania ale wszędzie są frameworki i teraz pytanie czy to już czas żeby zacząć z nimi się bawić czy da radę jakoś w miarę prosty sposób wyświetlić tego messagebox'a?

Pozdrawiam

0

Wiesz pewnie, że nie należy wywoływać Messageboksów z viewmodeu. To twarda zasada, której należy przestrzegać. Co więc zrobić? Wydelegować wywołanie piętro wyżej czyli do code-behind. Tak, tak... code-behind nie musi być puste, jak to niektórzy puryści uważają. Jeżeli cb zajmuje się jedynie widokiem to można go wykorzystać np. do generowania dynamicznych gridów, gdzie nie masz z góry ustalonej informacji o ilości kolumn albo właśnie do obsługi Messageboksów.

Wewnątrz viewmodelu tworzysz delegata i event do tego:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using System.Windows.Input;
using SimpleWindow.Commands;
using SimpleWindow.Helpers;
using SimpleWindow.Interfaces;

namespace SimpleWindow.Viewmodels
{
    public class MainWindowVM : ViewModelBase
    {
        public ICommand Close
        {
            get { return new RelayCommand(() => this.Closing?.Invoke()); }
        }

        public delegate void ClosingEventHandler();
        public event ClosingEventHandler Closing;
    }
}

W code-behind zajmujesz się obsługą tegoż eventu:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using SimpleWindow.Viewmodels;

namespace SimpleWindow
{
    public partial class MainWindow : Window
    {
        private MainWindowVM viewmodel;

        public MainWindow()
        {
            InitializeComponent();

            this.viewmodel = new MainWindowVM();
            this.viewmodel.Closing += () => this.Close();
            this.DataContext = this.viewmodel;

            this.Closing += (sender, args) =>
            {
                var result = MessageBox.Show("Can I close?", "", MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.No) args.Cancel = true;
            };
        }

    }
}

XAML:

<Window x:Class="SimpleWindow.MainWindow"
        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:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:dp="clr-namespace:SimpleWindow.DPs"
        xmlns:local="clr-namespace:SimpleWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" 
        WindowStartupLocation="CenterScreen">
    <Button Content="Close me!"
            Command="{Binding Close}"/>
</Window>

Oczywiście jest to najprostsze rozwiązanie, ponieważ delegat nie musi być typu void i może zwraca do viewmodelu co Ci się podoba. Może również posiadać argumenty.

0

Dziękuję, nie sądziłem, że podobnie to będzie wyglądać jak zamykanie/otwieranie okna. Ale tak dla ścisłości chciałbym wykorzystać zwykły przycisk do zamykania okna czyli ten w prawym górnym rogu czerwony "X"

0

Ok teraz rozumiem, dziękuję ;) a jeśli chodzi o zwykłego messagebox'a? Jeśli dobrze rozumiem to i tak taki messagebox wyskakuje po kliknięciu czegoś więc muszę zrobić delegata, zdarzenie tylko co w View this.?

Popatrz dobrze: this.viewmodel.

1

Możesz to zrobić trochę bardziej "czysto"

Dodaj nową klasę

using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

public sealed class InvokeDelegateCommandAction : TriggerAction<DependencyObject>
{
    /// <summary>
    ///
    /// </summary>
    public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.Register("CommandParameter", typeof(object), typeof(InvokeDelegateCommandAction), null);

    /// <summary>
    ///
    /// </summary>
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
        "Command",
        typeof(ICommand),
        typeof(InvokeDelegateCommandAction),
        null);

    /// <summary>
    ///
    /// </summary>
    public static readonly DependencyProperty InvokeParameterProperty =
        DependencyProperty.Register("InvokeParameter", typeof(object), typeof(InvokeDelegateCommandAction), null);

    private string commandName;

    /// <summary>
    ///
    /// </summary>
    public object InvokeParameter
    {
        get
        {
            return this.GetValue(InvokeParameterProperty);
        }

        set
        {
            this.SetValue(InvokeParameterProperty, value);
        }
    }

    /// <summary>
    ///
    /// </summary>
    public ICommand Command
    {
        get
        {
            return (ICommand)this.GetValue(CommandProperty);
        }

        set
        {
            this.SetValue(CommandProperty, value);
        }
    }

    /// <summary>
    ///
    /// </summary>
    public string CommandName
    {
        get
        {
            return this.commandName;
        }

        set
        {
            if (this.CommandName != value)
            {
                this.commandName = value;
            }
        }
    }

    /// <summary>
    ///
    /// </summary>
    public object CommandParameter
    {
        get
        {
            return this.GetValue(CommandParameterProperty);
        }

        set
        {
            this.SetValue(CommandParameterProperty, value);
        }
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="parameter"></param>
    protected override void Invoke(object parameter)
    {
        this.InvokeParameter = parameter;

        if (this.AssociatedObject != null)
        {
            ICommand command = this.ResolveCommand();
            if ((command != null) && command.CanExecute(this.CommandParameter))
            {
                command.Execute(this.CommandParameter);
            }
        }
    }

    private ICommand ResolveCommand()
    {
        ICommand command = null;
        if (this.Command != null)
        {
            return this.Command;
        }
        var frameworkElement = this.AssociatedObject as FrameworkElement;
        if (frameworkElement != null)
        {
            object dataContext = frameworkElement.DataContext;
            if (dataContext != null)
            {
                PropertyInfo commandPropertyInfo =
                    dataContext.GetType()
                        .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                        .FirstOrDefault(
                            p =>
                                typeof(ICommand).IsAssignableFrom(p.PropertyType)
                                && string.Equals(p.Name, this.CommandName, StringComparison.Ordinal));

                if (commandPropertyInfo != null)
                {
                    command = (ICommand)commandPropertyInfo.GetValue(dataContext, null);
                }
            }
        }

        return command;
    }
}

teraz w xmla dopisujesz obsługę eventów

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <local:InvokeDelegateCommandAction Command="{Binding WindowClosingCommand, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=InvokeParameter}" />
    </i:EventTrigger>
    <i:EventTrigger EventName="Closed">
        <i:InvokeCommandAction Command="{Binding WindowClosedCommand, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

i teraz z ViewModel masz 2 funkcje

public ICommand WindowClosedCommand
{
    get
    {
        return new DelegateCommand(this.Dispose, () => true);
    }
}

public ICommand WindowClosingCommand
{
    get
    {
        return new DelegateCommand<CancelEventArgs>(
            e =>
            {
                var result = this.dialogService.ShowMessageBox(
                    this,
                    "Do you want to close this window",
                    "Question...",
                    MessageBoxButton.YesNo);
                e.Cancel = result != MessageBoxResult.Yes;
            });
    }
}

DelegateCommand pochodzi z Prism a dialogService z

Pozdrawiam,

mr-owl

0

this.viewmodel.Closing... to rozumiem - ma wyświetlić okienko z wyborem czy zapisać zmiany itd.
jeśli chodzi o to this.viewmodel = new MainWindowVM(); nie rozumiem w jaki sposób to ma wyświetlić messagebox'a chociażby ze zwykłą treścią "hello world" this.viewmodel.ShowMessage += () => this.Message(); ...?

0

Choćby tak:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using SimpleWindow.Viewmodels;

namespace SimpleWindow
{
    public partial class MainWindow : Window
    {
        private MainWindowVM viewmodel;

        public MainWindow()
        {
            InitializeComponent();

            this.viewmodel = new MainWindowVM();

            this.viewmodel.ShowMessageWindow += (message) => MessageBox.Show(message);

            this.DataContext = this.viewmodel;
        }

    }
}

I viewmodel:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using System.Windows.Input;
using SimpleWindow.Commands;
using SimpleWindow.Helpers;
using SimpleWindow.Interfaces;

namespace SimpleWindow.Viewmodels
{
    public class MainWindowVM : ViewModelBase
    {
        public ICommand ShowMessage
        {
            get { return new RelayCommand(() => this.ShowMessageWindow?.Invoke("Hello world!")); }
        }

        public delegate void ShowMessageEventHandler(string message);
        public event ShowMessageEventHandler ShowMessageWindow;
    }
}
0

Dziękuję za odpowiedzi :) może jeszcze nie w pełni zrozumiałem MVVM ale dzięki Wam już jest lepiej ;)

Pozdrawiam

1 użytkowników online, w tym zalogowanych: 0, gości: 1