diff --git a/XLAB2/AccountingBookDirectoryWindow.xaml b/XLAB2/AccountingBookDirectoryWindow.xaml
new file mode 100644
index 0000000..5f631cc
--- /dev/null
+++ b/XLAB2/AccountingBookDirectoryWindow.xaml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/XLAB2/AccountingBookDirectoryWindow.xaml.cs b/XLAB2/AccountingBookDirectoryWindow.xaml.cs
new file mode 100644
index 0000000..ce8a687
--- /dev/null
+++ b/XLAB2/AccountingBookDirectoryWindow.xaml.cs
@@ -0,0 +1,35 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace XLAB2
+{
+ public partial class AccountingBookDirectoryWindow : Window
+ {
+ private readonly AccountingBookDirectoryWindowViewModel _viewModel;
+
+ internal AccountingBookDirectoryWindow(DocumentNumberDirectoryService documentNumberDirectoryService)
+ {
+ InitializeComponent();
+ _viewModel = new AccountingBookDirectoryWindowViewModel(
+ documentNumberDirectoryService,
+ new DialogService(this, documentNumberDirectoryService));
+ DataContext = _viewModel;
+ }
+
+ private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ var row = sender as DataGridRow;
+ if (row != null)
+ {
+ row.IsSelected = true;
+ row.Focus();
+ }
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ _viewModel.Initialize();
+ }
+ }
+}
diff --git a/XLAB2/AccountingBookDirectoryWindowViewModel.cs b/XLAB2/AccountingBookDirectoryWindowViewModel.cs
new file mode 100644
index 0000000..829920f
--- /dev/null
+++ b/XLAB2/AccountingBookDirectoryWindowViewModel.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows.Data;
+using System.Windows.Input;
+
+namespace XLAB2
+{
+ internal sealed class AccountingBookDirectoryWindowViewModel : ObservableObject
+ {
+ private readonly IDialogService _dialogService;
+ private readonly DocumentNumberDirectoryService _service;
+ private string _searchText;
+ private GroupOption _selectedItem;
+ private string _statusText;
+
+ public AccountingBookDirectoryWindowViewModel(DocumentNumberDirectoryService service, IDialogService dialogService)
+ {
+ _service = service ?? throw new ArgumentNullException("service");
+ _dialogService = dialogService ?? throw new ArgumentNullException("dialogService");
+
+ Items = new ObservableCollection();
+ ItemsView = CollectionViewSource.GetDefaultView(Items);
+ ItemsView.Filter = FilterItems;
+
+ AddCommand = new RelayCommand(delegate { AddItem(); });
+ DeleteCommand = new RelayCommand(delegate { DeleteSelectedItem(); }, delegate { return SelectedItem != null; });
+ EditCommand = new RelayCommand(delegate { EditSelectedItem(); }, delegate { return SelectedItem != null; });
+ RefreshCommand = new RelayCommand(delegate { RefreshItems(null); });
+
+ UpdateStatus();
+ }
+
+ public ICommand AddCommand { get; private set; }
+
+ public ICommand DeleteCommand { get; private set; }
+
+ public ICommand EditCommand { get; private set; }
+
+ public ObservableCollection Items { get; private set; }
+
+ public ICollectionView ItemsView { get; private set; }
+
+ public ICommand RefreshCommand { get; private set; }
+
+ public string SearchText
+ {
+ get { return _searchText; }
+ set
+ {
+ if (SetProperty(ref _searchText, value))
+ {
+ ItemsView.Refresh();
+ UpdateStatus();
+ }
+ }
+ }
+
+ public GroupOption SelectedItem
+ {
+ get { return _selectedItem; }
+ set
+ {
+ if (SetProperty(ref _selectedItem, value))
+ {
+ RaiseCommandStates();
+ }
+ }
+ }
+
+ public string StatusText
+ {
+ get { return _statusText; }
+ private set { SetProperty(ref _statusText, value); }
+ }
+
+ public void Initialize()
+ {
+ RunOperation(delegate { RefreshItems(null); }, false);
+ }
+
+ private void AddItem()
+ {
+ var result = _dialogService.ShowAccountingBookEditDialog(new GroupOption(), true, Items.ToList());
+ if (result == null)
+ {
+ return;
+ }
+
+ RunOperation(delegate
+ {
+ var items = CloneItems(Items);
+ items.Add(CloneItem(result));
+ _service.SaveAccountingBooks(items);
+ RefreshItems(result.Key);
+ _dialogService.ShowInfo("Запись справочника добавлена.");
+ }, true);
+ }
+
+ private bool Contains(string source, string searchText)
+ {
+ return !string.IsNullOrWhiteSpace(source)
+ && source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
+ }
+
+ private void DeleteSelectedItem()
+ {
+ if (SelectedItem == null)
+ {
+ return;
+ }
+
+ var selectedItem = SelectedItem;
+ if (!_dialogService.Confirm(string.Format("Удалить книгу учета \"{0}\"?", selectedItem.Title)))
+ {
+ return;
+ }
+
+ RunOperation(delegate
+ {
+ var items = CloneItems(Items);
+ items.RemoveAll(delegate(GroupOption item)
+ {
+ return string.Equals(item.Key, selectedItem.Key, StringComparison.OrdinalIgnoreCase);
+ });
+
+ _service.SaveAccountingBooks(items);
+ RefreshItems(null);
+ _dialogService.ShowInfo("Запись справочника удалена.");
+ }, true);
+ }
+
+ private void EditSelectedItem()
+ {
+ if (SelectedItem == null)
+ {
+ return;
+ }
+
+ var seed = CloneItem(SelectedItem);
+ var result = _dialogService.ShowAccountingBookEditDialog(seed, false, Items.ToList());
+ if (result == null)
+ {
+ return;
+ }
+
+ RunOperation(delegate
+ {
+ var items = CloneItems(Items);
+ var index = items.FindIndex(delegate(GroupOption item)
+ {
+ return string.Equals(item.Key, seed.Key, StringComparison.OrdinalIgnoreCase);
+ });
+
+ if (index < 0)
+ {
+ throw new InvalidOperationException("Не удалось найти книгу учета для обновления.");
+ }
+
+ items[index] = CloneItem(result);
+ _service.SaveAccountingBooks(items);
+ RefreshItems(result.Key);
+ _dialogService.ShowInfo("Запись справочника обновлена.");
+ }, true);
+ }
+
+ private bool FilterItems(object item)
+ {
+ var directoryItem = item as GroupOption;
+ if (directoryItem == null)
+ {
+ return false;
+ }
+
+ if (string.IsNullOrWhiteSpace(SearchText))
+ {
+ return true;
+ }
+
+ return Contains(directoryItem.Key, SearchText)
+ || Contains(directoryItem.Title, SearchText);
+ }
+
+ private void RaiseCommandStates()
+ {
+ ((RelayCommand)AddCommand).RaiseCanExecuteChanged();
+ ((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
+ ((RelayCommand)EditCommand).RaiseCanExecuteChanged();
+ ((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
+ }
+
+ private void RefreshItems(string keyToSelect)
+ {
+ var items = _service.LoadAccountingBooks();
+ var currentKey = string.IsNullOrWhiteSpace(keyToSelect)
+ ? (SelectedItem == null ? null : SelectedItem.Key)
+ : keyToSelect;
+
+ Items.Clear();
+ foreach (var item in items)
+ {
+ Items.Add(CloneItem(item));
+ }
+
+ ItemsView.Refresh();
+ SelectedItem = string.IsNullOrWhiteSpace(currentKey)
+ ? Items.FirstOrDefault()
+ : Items.FirstOrDefault(delegate(GroupOption item)
+ {
+ return string.Equals(item.Key, currentKey, StringComparison.OrdinalIgnoreCase);
+ }) ?? Items.FirstOrDefault();
+ UpdateStatus();
+ }
+
+ private void RunOperation(Action action, bool showWarningForInvalidOperation)
+ {
+ try
+ {
+ action();
+ }
+ catch (InvalidOperationException ex)
+ {
+ if (showWarningForInvalidOperation)
+ {
+ _dialogService.ShowWarning(ex.Message);
+ return;
+ }
+
+ _dialogService.ShowError(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ _dialogService.ShowError(ex.Message);
+ }
+ }
+
+ private static GroupOption CloneItem(GroupOption item)
+ {
+ return new GroupOption
+ {
+ Key = item == null ? string.Empty : item.Key,
+ Title = item == null ? string.Empty : item.Title
+ };
+ }
+
+ private static List CloneItems(IEnumerable items)
+ {
+ var result = new List();
+
+ foreach (var item in items ?? Array.Empty())
+ {
+ result.Add(CloneItem(item));
+ }
+
+ return result;
+ }
+
+ private void UpdateStatus()
+ {
+ var visibleCount = ItemsView.Cast