Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
WPF-практика 5 События и команды.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.21 Mб
Скачать

Логика отключения источников задач

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

Для нашего приложения проведем ревизию правил, по которым элементы интерфейса должны менять свое состояние в зависимости от возможности выполнения тех или иных задач на текущий момент.

Раздел File:

  1. Задача New: должна быть доступна всегда, поскольку пользователь может захотеть в любой момент создать новый документ. Если в текущем документе есть несохраненные изменения, то нужно вывести диалоговое окно с предложением их сохранить, проигнорировать или отменить задачу. При утвердительном ответе пользователя следует проверить, если новый документ сохраняется впервые, то нужно предоставить диалоговое окно записи. Реализована!

  2. Задача Open: аналогична задаче New, только после решения о сохранении текущих изменений следует предоставить диалог открытия файла. Реализована!

  3. Задача Save: при новом документе предоставить диалог записи. Если документ уже сохранялся и имеет имя, но текущих изменений нет, то задачу следует сделать недоступной, отключив соответствующие источники. При первом же изменении нужно немедленно освободить источники выполнения этой задачи. Нереализована!

  4. Задача Save As: должна быть доступна всегда. При запросе выполнения этой задачи сразу предоставить пользователю диалог сохранения файла. Реализована!

  5. Задачи Page Setup, Print Preview, Print: должны быть доступны всегда

  6. Задача Exit: должна быть доступна всегда. Если есть несохраненные изменения, частично выполнить задачу New и завершить приложение (возможно, с сохранением текущего состояния приложения в ресурсном файле). Реализована частично!

Раздел Edit:

  1. Задачи Undo, Redo: делать недоступными, когда восстанавливать нечего. Нереализована!

  2. Задачи Cut, Copy: делать недоступными, если нет выделения текста. Нереализована!

  3. Задача Paste: делать недоступной, если буфер обмена пуст или в нем сохранена нетекстовая информация. Нереализована!

  4. Задача Delete: делать недоступной, если нет выделения текста. Нереализована!

  5. Задачи Find, Find Next, Replace, Go To: доступны всегда

  6. Задача Select All: недоступна в случае, если текстовое поле пустое. Нереализована!

Раздел Format:

  1. Задачи Font, Word Wrap: доступны всегда

Раздел Help:

  1. Задача 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 механизм команд.