Реализация логики доступности источников команд

  • В соответствии с логикой работы подобных приложений заполните заготовки обработчиков события CanExecute в файле EnabledControls.cs следующим образом (комментарии в коде)
// Обработчики события CanExecute команд private void SaveCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Позволить, если есть несохраненные изменения e.CanExecute = IsModified; } /* private void PageSetupCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Источник не регулируется } */ private void UndoCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Позволить, если есть что откатывать e.CanExecute = IsModified; } private void RedoCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Позволить, если очередь отмены есть и стоим не самые первые e.CanExecute = txtBox1.CanRedo; } private void CutCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Позволить, если есть выделенный текст e.CanExecute = txtBox1.SelectionLength > 0; } private void CopyCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Позволить, если есть выделенный текст e.CanExecute = txtBox1.SelectionLength > 0; } private void PasteCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Позволить, если буфер обмена непустой и там содержится текстовый формат e.CanExecute = Clipboard.ContainsText(); } private void DeleteCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Позволить, если есть выделенный текст e.CanExecute = txtBox1.SelectionLength > 0; } private void FindNextCanExecute(object sender, CanExecuteRoutedEventArgs e) { //!!!!! От фонаря - не хочется думать !!!!! e.CanExecute = txtBox1.CaretIndex < txtBox1.Text.Length; } private void ReplaceCanExecute(object sender, CanExecuteRoutedEventArgs e) { //!!!!! От фонаря - не хочется думать !!!!! e.CanExecute = txtBox1.CaretIndex < txtBox1.Text.Length; } /* private void GoToCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Источник не регулируется } */ private void SelectAllCanExecute(object sender, CanExecuteRoutedEventArgs e) { // Заблокировать, если нечего выделять или уже все выделено if (txtBox1.Text == String.Empty || txtBox1.SelectionLength == txtBox1.Text.Length) e.CanExecute = false; else e.CanExecute = true; }
  • Запустите приложение и убедитесь... - что все работает не так как надо!!!

Исправим это. Начнем с журнала откатов, встроенного в элемент TextBox. По логике, очередь изменений должна очищаться при создании нового документа или открытии существующего. А эта задача у нас выполняется в обработчиках NewOnExecute() и OpenOnExecute().

  • Откройте файл File.cs и добавьте в упомянутые обработчики код очистки журнала изменений объекта txtBox1 следующим образом
private void NewOnExecute(object sender, RoutedEventArgs e) { // Пользователь передумал или была ошибка записи изменений if (!CheckModifiedAndSaveIt()) return; // Изменений нет или они успешно сохранены //txtBox1.Text = String.Empty; // Вариант I //txtBox1.Text = ""; // Вариант II txtBox1.Clear(); // Вариант III strLoadedFile = null; IsModified = false; UpdateTitle(); txtBox1.UndoLimit = 0; // Очистка очереди отмены txtBox1.UndoLimit = -1; // Размер по умолчанию ограничен памятью txtBox1.Focus(); // Надо отключить Cut, Copy и Delete } private void OpenOnExecute(object sender, RoutedEventArgs e) { if (DisplayOpenDialog()) { txtBox1.CaretIndex = txtBox1.Text.Length;// Курсор в конец txtBox1.UndoLimit = 0; // Очистка очереди отмены txtBox1.UndoLimit = -1; // Размер по умолчанию ограничен памятью } txtBox1.Focus();// Передача фокуса }
  • Откройте файл Edit.cs и добавьте в обработчик UndoOnExecute() следующий код
private void UndoOnExecute(object sender, RoutedEventArgs e) { txtBox1.Undo(); if (!txtBox1.CanUndo) IsModified = false; }
  • Запустите приложение и убедитесь... - что опять все работает не так как надо!!!

Вроде бы все предусмотрели, в чем здесь дело?

  • Проставьте в файле Edit.cs вдоль левого поля в теле обработчиков UndoOnExecute(), RedoOnExecute(), CutOnExecute(), CopyOnExecute(), PasteOnExecute(), DeleteOnExecute(), SelectAllOnExecute() напротив исполнимого кода точки останова (Breakpoin), как показано на снимке


увеличить изображение

>

  • Запустите приложение и поэксперименируйте с источниками команд - ни один из отмеченных обработчиков (кроме DeleteOnExecute, да и тот на жест Del не реагирует) не срабатывает, хотя текстовый элемент свои функции выполняет исправно

Это происходит из-за того, что для соответствующих задач мы использовали библиотечные команды, которые напрямую работают с текстовыми элементами, имеющими фокус ввода. Ну работают и пусть себе работают, поскольку в наших обработчиках предусмотрена та же самая функциональность, кроме UndoOnExecute().

  • Удалите все точки останова командой оболочки Debug/Delete All Breakpoints
  • Теперь в файле EnabledControls.cs проставляйте поодиночке точки останова и каждый раз запускайте приложение для обработчиков UndoCanExecute(), RedoCanExecute(), CutCanExecute(), CopyCanExecute(), PasteCanExecute(), DeleteCanExecute(), SelectAllCanExecute(). Для остановки процесса пользуйтесь командой Stop Debugging меню Debug оболочки или одноименной кнопкой панели инструментов!!!

Мы видим, что все эти обработчики события CanExecute срабатывают исправно для всех команд: и библиотечных, и пользовательских. Причем срабатывают перед тем, как элемент показывается при раскрытии меню, начальной отрисовке или наведении курсора. То есть срабатывает именно тогда, когда нужно принять решение, с какой доступностью этот элемент отобразить.

Итак, для правильной работы логики доступности источников команд нам нужно заставить выполняться именно наши обработчики для команд Undo, Redo и SelectAll.

  • Добавьте в статический конструктор файла EnabledControls.cs следующий код переопределения библиотечных команд на пользовательские
// Статический конструктор static Window1() { // Определяем с добавлением жестов InputGestureCollection coll = new InputGestureCollection(); coll.Add(new KeyGesture(Key.F3, ModifierKeys.None, "F3")); FindNextCommand = new RoutedCommand("FindNext", typeof(Window1), coll); coll = new InputGestureCollection(); coll.Add(new KeyGesture(Key.G, ModifierKeys.Control, "Ctrl+G")); GoToCommand = new RoutedCommand("GoTo", typeof(Window1), coll); // Заменяем библиотечные команды на свои для правильной работы логики coll = new InputGestureCollection(); coll.Add(new KeyGesture(Key.Z, ModifierKeys.Control, "Ctrl+Z")); UndoCommand = new RoutedCommand("Undo", typeof(Window1), coll); coll = new InputGestureCollection(); coll.Add(new KeyGesture(Key.Y, ModifierKeys.Control, "Ctrl+Y")); RedoCommand = new RoutedCommand("Redo", typeof(Window1), coll); coll = new InputGestureCollection(); coll.Add(new KeyGesture(Key.A, ModifierKeys.Control, "Ctrl+A")); SelectAllCommand = new RoutedCommand("SelectAll", typeof(Window1), coll); }

Обратите внимание на то, что мы не удалили в полях инициализацию переопределенных ссылок библиотечными командами. Мы понадеялись на порядок инициализации полей и срабатывания статического конструктора. Вначале вынесенные в поля класса ссылки на команды объявляются и инициализируются, а затем в статическом конструкторе эти же ссылки получают адреса новых команд. Это малоприметный, но очень важный нюанс, проигнорировав который можно получить неуловимую ошибку выполнения.

Это плохая практика программирования, поскольку завтра могут выпустить другой транслятор или среду исполнения CLR, где этот порядок будет изменен, и наша программа станет работать неверно. Microsoft не дремлет (!!!) и регулярно посылает по сети обновления и исправления. А что они завтра пришлют, кто его знает. Поэтому лучше было бы в полях ссылки только объявить, а уже в конструкторе они бы наверняка получили адреса наших (пользовательских) команд. Можете так и сделать, но я у себя оставляю как есть.

  • Запустите приложение - теперь все работает как надо!!!

Мы спроектировали более-менее сносный текстовый редактор, для этого спукались на относительно низкий (подробный) уровень программирования. Конечно, в получившемся продукте еще много недоработок, да и такую задачу можно было бы решить в два счета на C++Builder. Но мы-то не блокнот проектировали, а знакомились с новым механизмом команд WPF на примере блокнота.

Вначале мы попытались проектировать без привлечения команд. Создали основу блокнота, но управление доступностью источников реализовали только для одной задачи Save, и то с большим трудом. Приходилось пускаться на всякие ухищрения. Затем часть задач переключили на команды. И сразу удалось гораздо легче и понятнее реализовать доступность всех источников пользовательского интерфейса. Выгода от применения механизма команд очевидна.








Дата добавления: 2015-04-15; просмотров: 779;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

Поделитесь с друзьями:

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.005 сек.