Работа с диалоговыми окнами

Последнее обновление: 18.10.2016

Работа с диалоговыми окнами при использовании паттерна MVVM может вызывать некоторые трудности. Например, если мы хотим по клику на кнопку или на пункт меню сохранять или открывать объекты с помощью диалоговых окон, не всегда может быть ясно, как вписать взаимодействие с диалоговыми окнами в MVVM.

Итак, для работы с диалоговыми окнами продолжим работу с проектом из прошлой темы. Вначале добавим в него новый интерфейс IDialogService, который будет определять функционал для работы с диалоговыми окнами:

public interface IDialogService
{
    void ShowMessage(string message);	// показ сообщения
    string FilePath { get; set; }	// путь к выбранному файлу
    bool OpenFileDialog();	// открытие файла
    bool SaveFileDialog();	// сохранение файла
}

Далее добавим в проект реализацию этого интерфейса в виде класса DefaultDialogService:

using Microsoft.Win32;
using System.Windows;

namespace MVVM
{
    public class DefaultDialogService : IDialogService
    {
        public string FilePath { get; set; }

        public bool OpenFileDialog()
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            if (openFileDialog.ShowDialog() == true)
            {
                FilePath = openFileDialog.FileName;
                return true;
            }
            return false;
        }

        public bool SaveFileDialog()
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            if (saveFileDialog.ShowDialog() == true)
            {
                FilePath = saveFileDialog.FileName;
                return true;
            }
            return false;
        }

        public void ShowMessage(string message)
        {
            MessageBox.Show(message);
        }
    }
}

Для получения пути файла для открытия/сохранения данный класс использует стандартные классы OpenFileDialog и SaveFileDialog, которые определены в пространстве имен Microsoft.Win32. Кроме того, для отображения сообщения здесь используется метод MessageBox.Show().

Для работы с файлами одного функционала по открытию/сохранению файла мало. Нам еще надо выполнять сами действия по считыванию информации из файла или ее сохранению в файл. Подобные действия, конечно, можно определить и в ViewModel. Однако для работы с информацией мы можем использовать различные типы файлов - бинарные файлы, xml, json и т.д. Для json в .NET мы можем использовать один функционал, для xml - другой, для текстовых файлов - третий и так далее. Поэтому в этом случае для работы с файлами определим в проекте общий интерфейс IFileService:

using System.Collections.Generic;

namespace MVVM
{
    public interface IFileService
    {
        List<Phone> Open(string filename);
        void Save(string filename, List<Phone> phonesList);
    }
}

Первый метод предназначен для открытия файла. Он принимает путь к файлу и возвращает список объектов. Второй метод сохраняет данные из списка в файле по определенному пути.

В качестве типа файлов мы будем использовать файлы json. Поэтому для работы с ними добавим в проект библиотеку :

Сериализация в JSON в WPF

Затем добавим в проект следующий класс JsonFileService:

using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Json;

namespace MVVM
{
    public class JsonFileService : IFileService
    {
        public List<Phone> Open(string filename)
        {
            List<Phone> phones = new List<Phone>();
            DataContractJsonSerializer jsonFormatter = 
                new DataContractJsonSerializer(typeof(List<Phone>));
            using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate))
            {
                phones = jsonFormatter.ReadObject(fs) as List<Phone>;
            }

            return phones;
        }

        public void Save(string filename, List<Phone> phonesList)
        {
            DataContractJsonSerializer jsonFormatter = 
                new DataContractJsonSerializer(typeof(List<Phone>));
            using (FileStream fs = new FileStream(filename, FileMode.Create))
            {
                jsonFormatter.WriteObject(fs, phonesList);
            }
        }
    }
}

С помощью класса DataContractJsonSerializer здесь производится сериализация/десериализация объектов в файл json в виде набора List<Phone>.

Далее изменим код ApplicationViewModel:

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Collections.ObjectModel;
using System;
using System.Linq;

namespace MVVM
{
    public class ApplicationViewModel : INotifyPropertyChanged
    {
        Phone selectedPhone;

        IFileService fileService;
        IDialogService dialogService;

        public ObservableCollection<Phone> Phones { get; set; }

        // команда сохранения файла
        private RelayCommand saveCommand;
        public RelayCommand SaveCommand
        {
            get
            {
                return saveCommand ??
                  (saveCommand = new RelayCommand(obj =>
                  {
                      try
                      {
                          if (dialogService.SaveFileDialog() == true)
                          {
                              fileService.Save(dialogService.FilePath, Phones.ToList());
                              dialogService.ShowMessage("Файл сохранен");
                          }
                      }
                      catch (Exception ex)
                      {
                          dialogService.ShowMessage(ex.Message);
                      }
                  }));
            }
        }

        // команда открытия файла
        private RelayCommand openCommand;
        public RelayCommand OpenCommand
        {
            get
            {
                return openCommand ??
                  (openCommand = new RelayCommand(obj =>
                  {
                      try
                      {
                          if (dialogService.OpenFileDialog() == true)
                          {
                              var phones = fileService.Open(dialogService.FilePath);
                              Phones.Clear();
                              foreach (var p in phones)
                                  Phones.Add(p);
                              dialogService.ShowMessage("Файл открыт");
                          }
                      }
                      catch (Exception ex)
                      {
                          dialogService.ShowMessage(ex.Message);
                      }
                  }));
            }
        }

        // команда добавления нового объекта
        private RelayCommand addCommand;
        public RelayCommand AddCommand
        {
            get
            {
                return addCommand ??
                  (addCommand = new RelayCommand(obj =>
                  {
                      Phone phone = new Phone();
                      Phones.Insert(0, phone);
                      SelectedPhone = phone;
                  }));
            }
        }

        private RelayCommand removeCommand;
        public RelayCommand RemoveCommand
        {
            get
            {
                return removeCommand ??
                  (removeCommand = new RelayCommand(obj =>
                  {
                      Phone phone = obj as Phone;
                      if (phone != null)
                      {
                          Phones.Remove(phone);
                      }
                  },
                 (obj) => Phones.Count > 0));
            }
        }
        private RelayCommand doubleCommand;
        public RelayCommand DoubleCommand
        {
            get
            {
                return doubleCommand ??
                  (doubleCommand = new RelayCommand(obj =>
                  {
                      Phone phone = obj as Phone;
                      if (phone != null)
                      {
                          Phone phoneCopy = new Phone
                          {
                              Company = phone.Company,
                              Price = phone.Price,
                              Title = phone.Title
                          };
                          Phones.Insert(0, phoneCopy);
                      }
                  }));
            }
        }

        public Phone SelectedPhone
        {
            get { return selectedPhone; }
            set
            {
                selectedPhone = value;
                OnPropertyChanged("SelectedPhone");
            }
        }

        public ApplicationViewModel(IDialogService dialogService, IFileService fileService)
        {
            this.dialogService = dialogService;
            this.fileService = fileService;

            // данные по умлолчанию
            Phones = new ObservableCollection<Phone>
            {
                new Phone { Title="iPhone 7", Company="Apple", Price=56000 },
                new Phone {Title="Galaxy S7 Edge", Company="Samsung", Price =60000 },
                new Phone {Title="Elite x3", Company="HP", Price=56000 },
                new Phone {Title="Mi5S", Company="Xiaomi", Price=35000 }
            };
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName]string prop = "")
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }
}

Для работы с файлами в конструктор ApplicationViewModel передаются объекты IDialogService и IFileService:

public ApplicationViewModel(IDialogService dialogService, IFileService fileService)
{
    this.dialogService = dialogService;
    this.fileService = fileService;
	//............
}

Затем эти объекты используются в командах OpenCommand и SaveCommand.

Также изменим код MainWindow.xaml:

<Window x:Class="MVVM.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:local="clr-namespace:MVVM"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="FontSize" Value="14" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="FontSize" Value="14" />
        </Style>
        <Style TargetType="Button">
            <Setter Property="Width" Value="40" />
            <Setter Property="Margin" Value="5" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="0.8*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="25" />
            <RowDefinition Height="*" />
            <RowDefinition Height="0.2*" />
        </Grid.RowDefinitions>
        <Menu Grid.ColumnSpan="2" >
            <MenuItem Header="Файл">
                <MenuItem Header="Открыть" Command="{Binding OpenCommand}" />
                <MenuItem Header="Сохранить" Command="{Binding SaveCommand}" />
            </MenuItem>
        </Menu>
        <ListBox Grid.Row="1" ItemsSource="{Binding Phones}"
                 SelectedItem="{Binding SelectedPhone}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Margin="5">
                        <TextBlock FontSize="18" Text="{Binding Path=Title}" />
                        <TextBlock Text="{Binding Path=Company}" />
                        <TextBlock Text="{Binding Path=Price}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <StackPanel Grid.Row="2" Orientation="Horizontal">
            <Button Command="{Binding AddCommand}">+</Button>
            <Button Command="{Binding RemoveCommand}" 
                    CommandParameter="{Binding SelectedPhone}">-</Button>
            <Button Content="2x">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseDoubleClick">
                        <i:InvokeCommandAction 
                            Command="{Binding DoubleCommand}" 
                            CommandParameter="{Binding SelectedPhone}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
        </StackPanel>

        <StackPanel Grid.Row="1" Grid.Column="1" DataContext="{Binding SelectedPhone}">
            <TextBlock Text="Выбранный элемент"  />
            <TextBlock Text="Модель" />
            <TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="Производитель" />
            <TextBox Text="{Binding Company, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="Цена" />
            <TextBox Text="{Binding Price, UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>
    </Grid>
</Window>

В отличие от предыдущей темы здесь добавлено меню с двумя пунктами, которые привязаны к командам SaveCommand и OpenCommand:

<Menu Grid.ColumnSpan="2" >
    <MenuItem Header="Файл">
        <MenuItem Header="Открыть" Command="{Binding OpenCommand}" />
        <MenuItem Header="Сохранить" Command="{Binding SaveCommand}" />
    </MenuItem>
</Menu>

И в конце изменим файл связанного кода MainWindow.xaml.cs:

using System.Windows;

namespace MVVM
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = 
                new ApplicationViewModel(new DefaultDialogService(), new JsonFileService());
        }
    }
}

В конструктор ApplicationViewModel для работы с файлами передаются объекты DefaultDialogService и JsonFileService.

Диалоговые окна в MVVM в WPF
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850