- •Лабораторная работа: События и команды в wpf
- •Обзор библиотечных событий
- •Упражнение 1. Обработка событий клавиатуры
- •События мыши
- •Упражнение 2. Прослушивание событий мыши
- •Упражнение 3. Создание и прослушивание пользовательского события
- •Обработчики уровня класса
- •Добавление информации в объект аргумента события
- •Задание для Упражнения 3
- •Модель команд
- •Объекты команд
- •Библиотечные классы команд
- •Присоединение команды к источнику
- •Привязка команды к прослушивающему элементу
- •Упражнение 4. Привязка команд в разметке
- •Перекрытие функций диспетчеризации событий
- •Прямой вызов команд
- •Упражнение 5. Привязка команд в процедурном коде
- •Жесты как источники команд
- •Добавление жестов в команду
- •Способ 1
- •Способ 2
- •Способ 3
- •Добавление жестов в прослушивающий элемент
- •Упражнение 6. Разработка простого блокнота без механизма команд
- •Создание главного меню
- •Добавление иконок
- •Создание логических ресурсов
- •Создание панели инструментов, строки состояния и рабочей области
- •Замена встроенного контекстного меню
- •Назначение ресурсов неразделяемыми
- •Замена иконок на прозрачные
- •Отключение встроенных команд
- •Подключение иконки приложения
- •Распределение класса по нескольким файлам и создание вспомогательных функций
- •Размещение вариантов заголовков окна в ресурсах приложения
- •Создание заготовок обработчиков
- •Регистрация обработчиков в разметке
- •Реализация обработчиков раздела меню File
- •Обработка системной кнопки
- •Реализация части обработчиков раздела меню Edit
- •Разработка и кодирование диалогового окна Find and Replace
- •Кодирование функциональности Find and Replace в основном окне
- •Подключение функциональности Find and Replace к источникам задач
- •Разработка диалогового окна Go To
- •Применение вложенных ресурсов
- •Подключение функциональности Go To к основному окну
- •Прочие задачи
- •Принудительная перерисовка окна
- •Добавление жестов
- •Логика отключения источников задач
- •Реализация логики отключения источников задачи Save
- •Упражнение 7. Разработка простого блокнота с использованием механизма команд
- •Создание нового проекта из копии существующего
- •Краткий анализ задачи
- •Создание и привязка команд
- •Реализация логики доступности источников команд
- •Отображение позиции курсора в строке состояния
Логика отключения источников задач
Одной из важных задач управления пользовательским интерфейсом является своевременное отключение источников команд, когда выполнение команды может противоречить логике работы приложения. Например, если в редакторе текста буфер обмена пуст, команду вставки следует сделать недоступной, или, если в загруженном файле не было изменений, то кнопку сохранения тоже нужно отключить.
Для нашего приложения проведем ревизию правил, по которым элементы интерфейса должны менять свое состояние в зависимости от возможности выполнения тех или иных задач на текущий момент.
Раздел File:
Задача New: должна быть доступна всегда, поскольку пользователь может захотеть в любой момент создать новый документ. Если в текущем документе есть несохраненные изменения, то нужно вывести диалоговое окно с предложением их сохранить, проигнорировать или отменить задачу. При утвердительном ответе пользователя следует проверить, если новый документ сохраняется впервые, то нужно предоставить диалоговое окно записи. Реализована!
Задача Open: аналогична задаче New, только после решения о сохранении текущих изменений следует предоставить диалог открытия файла. Реализована!
Задача Save: при новом документе предоставить диалог записи. Если документ уже сохранялся и имеет имя, но текущих изменений нет, то задачу следует сделать недоступной, отключив соответствующие источники. При первом же изменении нужно немедленно освободить источники выполнения этой задачи. Нереализована!
Задача Save As: должна быть доступна всегда. При запросе выполнения этой задачи сразу предоставить пользователю диалог сохранения файла. Реализована!
Задачи Page Setup, Print Preview, Print: должны быть доступны всегда
Задача Exit: должна быть доступна всегда. Если есть несохраненные изменения, частично выполнить задачу New и завершить приложение (возможно, с сохранением текущего состояния приложения в ресурсном файле). Реализована частично!
Раздел Edit:
Задачи Undo, Redo: делать недоступными, когда восстанавливать нечего. Нереализована!
Задачи Cut, Copy: делать недоступными, если нет выделения текста. Нереализована!
Задача Paste: делать недоступной, если буфер обмена пуст или в нем сохранена нетекстовая информация. Нереализована!
Задача Delete: делать недоступной, если нет выделения текста. Нереализована!
Задачи Find, Find Next, Replace, Go To: доступны всегда
Задача Select All: недоступна в случае, если текстовое поле пустое. Нереализована!
Раздел Format:
Задачи Font, Word Wrap: доступны всегда
Раздел Help:
Задача About: доступна всегда
Реализация логики отключения источников задачи Save
Чтобы продемонстрировать трудность реализации логики отключения источников, ограничимся только одной задачей Save. В последующем упражнении, где будет использован механизм команд, все решится гораздо проще. А пока только одна задача - Save, чтобы зря не тратить силы.
Не хочется вмешиваться в ранее разработанный код, поскольку логику отключения мы оставили на потом и сейчас это может повлечь ошибки. Поэтому, наиболее разумно, добавить автономный код, не меняя прежнего, и разместить его в отдельном файле.
Выделите узел текущего проекта и командой Project/Add Class добавьте новый файл с именем EnabledControls.cs, который заполните так
using System;
using System.Collections.Generic;
using System.Text;
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.Windows.Controls.Primitives;// Для ButtonBase
namespace Notepad1
{
partial class Window1
{
}
}
Первое, что приходит на ум - использовать событие texBox1.TextChanged, в котором проверять состояние флага IsModified и принимать решение о недоступности или доступности источников задачи Save.
Добавьте в файл EnabledControls.cs следующий код, регистрирующий еще один обработчик события texBox1.TextChanged
namespace Notepad1
{
partial class Window1
{
// Вызов размещен в конструкторе класса
void AdditionalHandlers()
{
// Еще один обработчик
// обычного события TextChanged
txtBox1.TextChanged += EnabledControls_TextChanged;
}
private void EnabledControls_TextChanged(object sender,
System.Windows.Controls.TextChangedEventArgs e)
{
// Изменяем состояние интерфейсных элементов _Save
itemSave.IsEnabled = btnSave.IsEnabled = IsModified;
}
}
}
В файле Window1.xaml.cs добавьте в конструктор класса Window1 последней строкой вызов функции AdditionalHandlers() так
public Window1()
{
InitializeComponent();
// Создание жестов
this.CreateGestures();
// Дополнительные обработчики в файле EnabledControls.cs
AdditionalHandlers();
}
Запустите приложение - до первого изменения текста кнопки источники Save блокированы, а потом все работает не так. И жест Ctrl+S тоже доступен.
Дело здесь в том, что событие TextChanged срабатывает раньше, чем будет установлен флаг IsModified. Поэтому нужно обрабатывать не событие изменения текста, а событие изменения флага IsModified. Следующим шагом мы преобразуем поле IsModified в свойство на базе нового логического поля modified и создадим свое событие, в обработчике которого и решим управление доступностью источников задачи Save.
В файле Window1.xaml.cs найдите объявление поля IsModified и переименуйте его в modified
Было bool IsModified = false; // Флаг изменений содержимого
Стало bool modified = false; // Флаг изменений содержимого
Поле modified будет базовым для свойства IsModified. Это все изменения, которые мы вынуждены были провести в прежнем коде. Остальные изменения будем вносить в файл EnabledControls.cs.
В файле EnabledControls.cs удалите весь код, связанный с событием texBox1.TextChanged и его обработчиком
namespace Notepad1
{
partial class Window1
{
// Вызов размещен в конструкторе класса
void AdditionalHandlers()
{
// Еще один обработчик
// обычного события TextChanged
txtBox1.TextChanged += EnabledControls_TextChanged;
}
private void EnabledControls_TextChanged(object sender,
System.Windows.Controls.TextChangedEventArgs e)
{
// Изменяем состояние интерфейсных элементов _Save
itemSave.IsEnabled = btnSave.IsEnabled = IsModified;
}
}
}
Добавьте в файл EnabledControls.cs новый код, чтобы файл стал таким
using System;
using System.Collections.Generic;
using System.Text;
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.Windows.Controls.Primitives;// Для ButtonBase
namespace Notepad1
{
partial class Window1
{
// Объявляем внутреннее событие
private event EventHandler ChangeModifiedEvent;
// Упаковываем базовое поле modified в свойство
private bool IsModified
{
get { return modified; }
set
{
if (modified != value)
{
modified = !modified;
// Инициируем событие, если есть обработчик
if (ChangeModifiedEvent != null)
ChangeModifiedEvent(this, EventArgs.Empty);
}
}
}
// Вызов размещен в конструкторе класса
void AdditionalHandlers()
{
// Начальные запрещения для _Save
itemSave.IsEnabled = btnSave.IsEnabled = false;
// Удаляем созданный в CreateGestures() жест _Save
foreach (KeyGesture gest in gests.Keys)
if (gests[gest] == SaveOnExecute)
{
gests.Remove(gest);
break;
}
// Регистрируем обработчик изменения свойства
this.ChangeModifiedEvent += Window1_ChangeModifiedEvent;
}
void Window1_ChangeModifiedEvent(object sender, EventArgs e)
{
//MessageBox.Show("Modify");
// Проверяем состояние любого из источников _Save
if (btnSave.IsEnabled == false)
// Добавляем жест _Save
gests.Add(new KeyGesture(Key.S, ModifierKeys.Control),
SaveOnExecute);//_Save
else
// Удаляем жест _Save
foreach (KeyGesture gest in gests.Keys)
if (gests[gest] == SaveOnExecute)
{
gests.Remove(gest);
break;
}
// Изменяем состояние интерфейсных элементов _Save
itemSave.IsEnabled = btnSave.IsEnabled = IsModified;
}
}
}
Запустите приложение и убедитесь, что управление источниками задачи Save, включая жесты, во всех режимах работает как и положено. Разберитесь с кодом!!!
Для управления доступностью других задач приложения нужно построить что-то подобное. Мы этого здесь делать не будем, однако и сейчас уже ясно, что это непростая задача. Для желающих продолжить управление отключениями источников можно посоветовать дополнить файл EnabledControls.cs новыми заготовками так
using System;
using System.Collections.Generic;
using System.Text;
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.Windows.Controls.Primitives;// Для ButtonBase
namespace Notepad1
{
partial class Window1
{
// Объявляем внутреннее событие
private event EventHandler ChangeModifiedEvent;
// Упаковываем базовое поле modified в свойство
private bool IsModified
{
get { return modified; }
set
{
if (modified != value)
{
modified = !modified;
// Инициируем событие, если есть обработчик
if (ChangeModifiedEvent != null)
ChangeModifiedEvent(this, EventArgs.Empty);
}
}
}
// Вызов размещен в конструкторе класса
void AdditionalHandlers()
{
// Регистрируем один и тот же обработчик
// всплывающих событий кнопок, элементов меню,
// клавиатурных жестов для окна
this.AddHandler(ButtonBase.ClickEvent,
new RoutedEventHandler(this.Window1_ButtonClick));
this.AddHandler(MenuItem.ClickEvent,
new RoutedEventHandler(this.Window1_ItemClick));
this.AddHandler(Keyboard.KeyDownEvent,
new RoutedEventHandler(this.Window1_Gesture));
// Дополнительный общий обработчик элементов контекстного меню
contextCut.Click += new RoutedEventHandler(item_Context);
contextCopy.Click += new RoutedEventHandler(item_Context);
contextPaste.Click += item_Context; // Упрощенный синтаксис
contextDelete.Click += item_Context;
// Начальные запрещения для _Save
itemSave.IsEnabled = btnSave.IsEnabled = false;
// Удаляем созданный в CreateGestures() жест _Save
foreach (KeyGesture gest in gests.Keys)
if (gests[gest] == SaveOnExecute)
{
gests.Remove(gest);
break;
}
// Регистрируем обработчик изменения свойства
this.ChangeModifiedEvent += Window1_ChangeModifiedEvent;
}
void Window1_ChangeModifiedEvent(object sender, EventArgs e)
{
//MessageBox.Show("Modify");
// Проверяем состояние любого из источников _Save
if (btnSave.IsEnabled == false)
// Добавляем жест _Save
gests.Add(new KeyGesture(Key.S, ModifierKeys.Control),
SaveOnExecute);//_Save
else
// Удаляем жест _Save
foreach (KeyGesture gest in gests.Keys)
if (gests[gest] == SaveOnExecute)
{
gests.Remove(gest);
break;
}
// Изменяем состояние интерфейсных элементов _Save
itemSave.IsEnabled = btnSave.IsEnabled = IsModified;
}
private void Window1_ButtonClick(object sender, RoutedEventArgs e)
{
//MessageBox.Show("Button");
// Повышаем полномочия ссылки
Button btn = sender as Button;
if (btn == btnSave)
{
;
}
}
private void Window1_ItemClick(object sender, RoutedEventArgs e)
{
//MessageBox.Show("Item");
// Повышаем полномочия ссылки
MenuItem item = sender as MenuItem;
if (item == itemSave)
{
;
}
}
private void Window1_Gesture(object sender, RoutedEventArgs e)
{
//MessageBox.Show("Key");
}
private void item_Context(object sender, RoutedEventArgs e)
{
//MessageBox.Show("Context");
}
}
}
Добавленный код пока ни на что не влияет, но может стать отправной точкой для дальнейших действий по блокированию других задач. В следующим упражнении мы все решим гораздо проще, используя встроенный в WPF механизм команд.
