При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Microsoft.Xna.Framework.Graphics;
namespace Application1 { public partial class MainForm : Form { // Объявим поле графического устройства для видимости в методах GraphicsDevice device;
public MainForm() { InitializeComponent();
// Подпишемся на событие Load формы this.Load += new EventHandler(MainForm_Load);
// Попишемся на событие FormClosed формы this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed); }
void MainForm_FormClosed(object sender, FormClosedEventArgs e) { // Удаляем (освобождаем) устройство device.Dispose(); // На всякий случай присваиваем ссылке на устройство значение null device = null; }
void MainForm_Load(object sender, EventArgs e) { // Создаем объект представления для настройки графического устройства PresentationParameters presentParams = new PresentationParameters(); // Настраиваем объект представления через его свойства presentParams.IsFullScreen = false; // Включаем оконный режим presentParams.BackBufferCount = 1; // Включаем задний буфер // для двойной буферизации // Переключение переднего и заднего буферов // должно осуществляться с максимальной эффективностью presentParams.SwapEffect = SwapEffect.Discard; // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height;
// Создадим графическое устройство с заданными настройками device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle, presentParams); }
protected override void OnPaint(PaintEventArgs e) { device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);
base.OnPaint(e); } } } Выбрасывается исключение: Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл. Делаю все пунктуально. В чем может быть проблема? |
События и команды в WPF
Добавление жестов
Когда мы создавали источники - элементы меню, то бодро прописали вместе с названиями задач и жесты клавиатурного ввода. Но ни один из них в настоящий момент не работает, поскольку не подкреплен соответствующим механизмом. Далее мы этот же пример повторим с применением команд, а там, мы говорили, эта функциональность уже встроена. Но на данном этапе нам такую возможность придется реализовывать вручную, и мы это сейчас выполним - для тренировки!
- Добавьте к узлу текущего проекта пустой файл KeyGestures.cs
- Заполните файл KeyGestures.cs следующим кодом
using System; using System.Windows; using System.Windows.Input; using System.Collections.Generic;// Для Dictionary<TKey, TValue> namespace Notepad1 { partial class Window1 { // Еще один вариант в Петцольд, WPF, с.316 !!! // Определяем ассоциативный словарь Dictionary<KeyGesture, RoutedEventHandler> gests = new Dictionary<KeyGesture, RoutedEventHandler>(); void CreateGestures() { // File gests.Add(new KeyGesture(Key.N, ModifierKeys.Control), NewOnExecute);//_New gests.Add(new KeyGesture(Key.O, ModifierKeys.Control), OpenOnExecute);//_Open... gests.Add(new KeyGesture(Key.S, ModifierKeys.Control), SaveOnExecute);//_Save gests.Add(new KeyGesture(Key.F2, ModifierKeys.Control), PrintPreviewOnExecute);//P_rint Preview gests.Add(new KeyGesture(Key.P, ModifierKeys.Control), PrintOnExecute);//_Print... // Edit gests.Add(new KeyGesture(Key.Z, ModifierKeys.Control), UndoOnExecute);//_Undo gests.Add(new KeyGesture(Key.Y, ModifierKeys.Control), RedoOnExecute);//_Redo gests.Add(new KeyGesture(Key.X, ModifierKeys.Control), CutOnExecute);//Cu_t gests.Add(new KeyGesture(Key.C, ModifierKeys.Control), CopyOnExecute);//_Copy gests.Add(new KeyGesture(Key.V, ModifierKeys.Control), PasteOnExecute);//_Paste gests.Add(new KeyGesture(Key.Delete, ModifierKeys.None), DeleteOnExecute);//De_lete gests.Add(new KeyGesture(Key.F, ModifierKeys.Control), FindOnExecute);//_Find... gests.Add(new KeyGesture(Key.F3, ModifierKeys.None), FindNextOnExecute);//Find _Next gests.Add(new KeyGesture(Key.H, ModifierKeys.Control), ReplaceOnExecute);//_Replace... gests.Add(new KeyGesture(Key.G, ModifierKeys.Control), GoToOnExecute);//_Go To... gests.Add(new KeyGesture(Key.A, ModifierKeys.Control), SelectAllOnExecute);//Select _All // Format gests.Add(new KeyGesture(Key.W, ModifierKeys.Control), WordWrapOnExecute);//_Word Wrap } // Перекрываем стандартный обработчик protected override void OnPreviewKeyDown(KeyEventArgs e) { base.OnPreviewKeyDown(e); // Ищем жест, останавливаем событие и исполняем обработчик foreach (KeyGesture gest in gests.Keys) if (gest.Matches(null, e)) // Сравниваем перехваченный жест с заданным в объекте { gests[gest](this, e); // Вызываем обработчик через словарь e.Handled = true; // Останавливаем событие break; // Прерываем цикл } } } }
- Добавьте в конструктор класса Window1 файла Window1.xaml.cs вызов нашей функции создания жестов
public Window1() { InitializeComponent(); // Создание жестов this.CreateGestures(); }
- Запустите приложение и убедитесь, что клавиатурные жесты работают
Логика отключения источников задач
Одной из важных задач управления пользовательским интерфейсом является своевременное отключение источников команд, когда выполнение команды может противоречить логике работы приложения. Например, если в редакторе текста буфер обмена пуст, команду вставки следует сделать недоступной, или, если в загруженном файле не было изменений, то кнопку сохранения тоже нужно отключить.
Для нашего приложения проведем ревизию правил, по которым элементы интерфейса должны менять свое состояние в зависимости от возможности выполнения тех или иных задач на текущий момент.
Раздел 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 механизм команд.
Упражнение 7. Разработка простого блокнота с использованием механизма команд
Мы уже столько потрудились над этим блокнотом, который так наивно назвали простым, что неразумно будет начинать все заново. Проще скопировать полученный проект в новый и там проводить все необходимые изменения. Но трудились мы не для сего блокнота, а ради будущих наших профессиональных успехов (Se La Vi - такова жизнь).