Опубликован: 05.08.2010 | Доступ: свободный | Студентов: 2010 / 47 | Оценка: 4.50 / 4.40 | Длительность: 60:26:00
Самостоятельная работа 12:

События и команды в 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:

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

Упражнение 7. Разработка простого блокнота с использованием механизма команд

Мы уже столько потрудились над этим блокнотом, который так наивно назвали простым, что неразумно будет начинать все заново. Проще скопировать полученный проект в новый и там проводить все необходимые изменения. Но трудились мы не для сего блокнота, а ради будущих наших профессиональных успехов (Se La Vi - такова жизнь).

Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №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" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?