При выполнении в лабораторной работе упражнения №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
Размещение вариантов заголовков окна в ресурсах приложения
В коде вспомогательных функций используется словарь ресурсов приложения, которые мы сейчас определим.
- Откройте файл разметки App.xaml и дополните его определением ресурсов для хранения неизменяемой части заголовка окна. Областью видимости этих ресурсов будет все приложение
<Application x:Class="Notepad1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="Window1.xaml"> <Application.Resources> <String xmlns="clr-namespace:System;assembly=Mscorlib" x:Key="ApplicationTitle1"> Title="Window1: Управление состоянием источников команд" </String> <String xmlns="clr-namespace:System;assembly=Mscorlib" x:Key="ApplicationTitle2"> Title="Window2: Управление состоянием источников команд" </String> </Application.Resources> </Application>
Создание заготовок обработчиков
Будем мы использовать механизм команд WPF или нет, но без событий и их обработчиков никак не обойтись. Начнем с того факта, что для решения наших задач можно создать обработчики с одинаковой сигнатурой, определяемой делегатом пространства имен System.Windows:
public delegate void RoutedEventHandler(object sender, RoutedEventArgs e)
Имена обработчикам будем присваивать в соответствии с решаемой ими задачей. Обработчики создадим вручную и их необязательно сразу присоединять к событиям источников, поскольку неприсоединенные обработчики будут считаться обычными методами класса. Наиболее полно все задачи представлены в главном меню приложения, поэтому для каждой из них нужно создать свою группу обработчиков. Выполним все это по порядку, размещая группы обработчиков в соответствующих частях класса Window1.
- Добавьте в часть класса Window1, расположенную в файле File.cs, следующие пустые обработчики по количеству задач меню в разделе File
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; namespace Notepad1 { partial class Window1 { //------------------------------------------------------ // // Обработчики источников задач File // //------------------------------------------------------ private void NewOnExecute(object sender, RoutedEventArgs e) { } private void OpenOnExecute(object sender, RoutedEventArgs e) { } private void SaveOnExecute(object sender, RoutedEventArgs e) { } private void SaveAsOnExecute(object sender, RoutedEventArgs e) { } private void PageSetupOnExecute(object sender, RoutedEventArgs e) { } private void PrintPreviewOnExecute(object sender, RoutedEventArgs e) { } private void PrintOnExecute(object sender, RoutedEventArgs e) { } private void ExitOnExecute(object sender, RoutedEventArgs e) { } } }
- Добавьте в часть класса Window1, расположенную в файле Edit.cs, следующие пустые обработчики по количеству задач меню в разделе Edit
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; namespace Notepad1 { partial class Window1 { //------------------------------------------------------ // // Обработчики источников задач Edit // //------------------------------------------------------ private void UndoOnExecute(object sender, RoutedEventArgs e) { } private void RedoOnExecute(object sender, RoutedEventArgs e) { } private void CutOnExecute(object sender, RoutedEventArgs e) { } private void CopyOnExecute(object sender, RoutedEventArgs e) { } private void PasteOnExecute(object sender, RoutedEventArgs e) { } private void DeleteOnExecute(object sender, RoutedEventArgs e) { } private void FindOnExecute(object sender, RoutedEventArgs e) { } private void FindNextOnExecute(object sender, RoutedEventArgs e) { } private void ReplaceOnExecute(object sender, RoutedEventArgs e) { } private void GoToOnExecute(object sender, RoutedEventArgs e) { } private void SelectAllOnExecute(object sender, RoutedEventArgs e) { } } }
- Добавьте в часть класса Window1, расположенную в файле Other.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; namespace Notepad1 { partial class Window1 { //------------------------------------------------------ // // Прочие обработчики // //------------------------------------------------------ private void FontOnExecute(object sender, RoutedEventArgs e) { } private void WordWrapOnExecute(object sender, RoutedEventArgs e) { } private void AboutOnExecute(object sender, RoutedEventArgs e) { } } }
- Запустите приложение и убедитесь, что ошибок компиляции нет, но функциональности от выполненной нами работы в нем пока не прибавилось
Регистрация обработчиков в разметке
Мы подготовили интерфейс окна Window1 и дальше наступает этап кодирования функциональности. В окне Window1 мы откажемся от услуг механизма команд, а будем использовать обычную технологию, основанную на обработчиках событий. Начнем постепенно подключать обработчики к источникам задач, используя событие Click.
- Подключите декларативно в файле разметки Window1.xaml к каждому источнику события Click соответствующий обработчик и назначьте имена элементам для последующего управления ими из кода
Обратите внимание на некоторую некорректность работы подсказчика кода IntelliSense, который не предлагает нам список уже созданных обработчиков. Он ищет их в застраничном файле Window1.xaml.cs, в то время как обработчики находятся в других файлах частичного класса. В данном случае это не слишком большое неудобство, поскольку мнемоника составления имен у нас строго соблюдена и мы вряд ли ошибемся. Но в больших практических проектах это следует иметь ввиду, прежде чем разбивать класс окна по отдельным файлам.
Разметка с подключенными обработчиками и присвоенными именами будет выглядеть так (файл приводится целиком)
<Window x:Class="Notepad1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1: Управление состоянием источников команд" Width="500" Height="375" MinWidth="500" MinHeight="375" WindowStartupLocation="CenterScreen" ResizeMode="CanResizeWithGrip" Loaded="Window_Loaded" Icon="Notepad.ico" > <Window.Resources> <!-- File --> <Image x:Shared="False" x:Key="iconNew" Source="Images/NewDocumentHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconOpen" Source="Images/OpenHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconSave" Source="Images/SaveHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconPageSetup" Source="Images/PrintSetupHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconPrintPreview" Source="Images/PrintPreviewHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconPrint" Source="Images/PrintHS.png" Width="16" Height="16" /> <!-- Edit --> <Image x:Shared="False" x:Key="iconUndo" Source="Images/Edit_UndoHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconRedo" Source="Images/Edit_RedoHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconCut" Source="Images/CutHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconCopy" Source="Images/CopyHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconPaste" Source="Images/PasteHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconDelete" Source="Images/DeleteHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconFind" Source="Images/FindHS.png" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconFont" Source="Images/FontHS.png" Width="16" Height="16" /> </Window.Resources> <DockPanel LastChildFill="True"> <!-- Меню --> <Menu DockPanel.Dock="Top"> <MenuItem Header="_File"> <!-- Сокращенные варианты подключения иконок с использованием статических ресурсов --> <MenuItem Name="itemNew" Click="NewOnExecute" Header="_New" InputGestureText="Ctrl+N" Icon="{StaticResource iconNew}" /> <MenuItem Name="itemOpen" Click="OpenOnExecute" Header="_Open..." InputGestureText="Ctrl+O" Icon="{StaticResource iconOpen}" /> <MenuItem Name="itemSave" Click="SaveOnExecute" Header="_Save" InputGestureText="Ctrl+S" Icon="{StaticResource iconSave}" /> <MenuItem Name="itemSaveAs" Click="SaveAsOnExecute" Header="Save _As..." /> <Separator /> <MenuItem Name="itemPageSetup" Click="PageSetupOnExecute" Header="Page Set_up..." Icon="{StaticResource iconPageSetup}" /> <MenuItem Name="itemPrintPreview" Click="PrintPreviewOnExecute" Header="P_rint Preview" InputGestureText="Ctrl+F2" Icon="{StaticResource iconPrintPreview}" /> <MenuItem Name="itemPrint" Click="PrintOnExecute" Header="_Print..." InputGestureText="Ctrl+P" Icon="{StaticResource iconPrint}" /> <Separator /> <MenuItem Name="itemExit" Click="ExitOnExecute" Header="E_xit" /> </MenuItem> <MenuItem Header="_Edit"> <MenuItem Name="itemUndo" Click="UndoOnExecute" Header="_Undo" InputGestureText="Ctrl+Z" Icon="{StaticResource iconUndo}" /> <MenuItem Name="itemRedo" Click="RedoOnExecute" Header="_Redo" InputGestureText="Ctrl+Y" Icon="{StaticResource iconRedo}" /> <Separator></Separator> <MenuItem Name="itemCut" Click="CutOnExecute" Header="Cu_t" InputGestureText="Ctrl+X" Icon="{StaticResource iconCut}" /> <MenuItem Name="itemCopy" Click="CopyOnExecute" Header="_Copy" InputGestureText="Ctrl+C" Icon="{StaticResource iconCopy}" /> <MenuItem Name="itemPaste" Click="PasteOnExecute" Header="_Paste" InputGestureText="Ctrl+V" Icon="{StaticResource iconPaste}" /> <MenuItem Name="itemDelete" Click="DeleteOnExecute" Header="De_lete" InputGestureText="Del" Icon="{StaticResource iconDelete}" /> <Separator></Separator> <MenuItem Name="itemFind" Click="FindOnExecute" Header="_Find..." InputGestureText="Ctrl+F" Icon="{StaticResource iconFind}" /> <MenuItem Name="itemFindNext" Click="FindNextOnExecute" Header="Find _Next" InputGestureText="F3" /> <MenuItem Name="itemReplace" Click="ReplaceOnExecute" Header="_Replace..." InputGestureText="Ctrl+H" /> <MenuItem Name="itemGoTo" Click="GoToOnExecute" Header="_Go To..." InputGestureText="Ctrl+G" /> <Separator></Separator> <MenuItem Name="itemSelectAll" Click="SelectAllOnExecute" Header="Select _All" InputGestureText="Ctrl+A" /> </MenuItem> <MenuItem Header="F_ormat"> <MenuItem Name="itemFont" Click="FontOnExecute" Header="_Font..." Icon="{StaticResource iconFont}" /> <Separator /> <MenuItem Name="itemWordWrap" Click="WordWrapOnExecute" Header="_Word Wrap" IsCheckable="True" IsChecked="True" InputGestureText="Ctrl+W" /> </MenuItem> <MenuItem Header="_Help"> <MenuItem Name="itemAbout" Click="AboutOnExecute" Header="_About" /> </MenuItem> </Menu> <!-- Панель инструментов --> <ToolBarTray DockPanel.Dock="Top"> <ToolBar> <Button Name="btnNew" Click="NewOnExecute" Width="23" Content="{StaticResource iconNew}" /> <Button Name="btnOpen" Click="OpenOnExecute" Width="23" Content="{StaticResource iconOpen}" /> <Button Name="btnSave" Click="SaveOnExecute" Width="23" Content="{StaticResource iconSave}" /> </ToolBar> <ToolBar> <Button Name="btnUndo" Click="UndoOnExecute" Width="23" Content="{StaticResource iconUndo}" /> <Button Name="btnRedo" Click="RedoOnExecute" Width="23" Content="{StaticResource iconRedo}" /> <Separator /> <Button Name="btnCut" Click="CutOnExecute" Width="23" Content="{StaticResource iconCut}" /> <Button Name="btnCopy" Click="CopyOnExecute" Width="23" Content="{StaticResource iconCopy}" /> <Button Name="btnPaste" Click="PasteOnExecute" Width="23" Content="{StaticResource iconPaste}" /> <Button Name="btnDelete" Click="DeleteOnExecute" Width="23" Content="{StaticResource iconDelete}" /> </ToolBar> <ToolBar Header="Find:"> <TextBox Width="100" /> <Button Name="btnFind" Click="FindOnExecute" Width="23" Content="{StaticResource iconFind}" /> </ToolBar> </ToolBarTray> <!-- Строка состояния --> <StatusBar DockPanel.Dock="Bottom" Height="32" Name="statusBar"> <Label>Simulator Application is Loading</Label> <Separator /> <ProgressBar Height="20" Width="100" IsIndeterminate="True" /> </StatusBar> <!-- Многострочное текстовое поле редактирования --> <TextBox TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" VerticalScrollBarVisibility="Auto" Name="txtBox1" > <TextBox.ContextMenu> <ContextMenu Width="100"> <MenuItem Name="contextCut" Click="CutOnExecute" Header="Cu_t" Icon="{StaticResource iconCut}" /> <MenuItem Name="contextCopy" Click="CopyOnExecute" Header="_Copy" Icon="{StaticResource iconCopy}" /> <MenuItem Name="contextPaste" Click="PasteOnExecute" Header="_Paste" Icon="{StaticResource iconPaste}" /> <MenuItem Name="contextDelete" Click="DeleteOnExecute" Header="De_lete" Icon="{StaticResource iconDelete}" /> </ContextMenu> </TextBox.ContextMenu> </TextBox> </DockPanel> </Window>
Столько имен нам не понадобиться, но для единообразия мы, на всякий случай, промаркировали все источники задач (может пригодится).
- Запустите приложение для проверки отсутствия синтаксических ошибок
Следующим шагом следует наполнить созданные заготовки обработчиков практическим кодом в соответствии с логикой работы приложения. Поскольку наша задача состоит не в создании полнофункционального приложения целиком, а в иллюстрации технологии его создания с применением механизма команд, реализуем намеченные задачи частично. Некоторые функции, ввиду их большого объема и сложности кодирования, мы только обозначим выдачей соответствующих диалоговых окон или сообщений. Управление доступностью элементов-источников задач пользовательского интерфейса пока отложим 'на потом' (или навсегда!).
Реализация обработчиков раздела меню File
- Выделите корень проекта Notepad1 и командой Project/Add Reference подключите сборки System.Windows.Forms.dll и System.Drawing.dll
- Модифицируйте код файла File.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; // Для сборок: System.Windows.Forms.dll, System.Drawing.dll using sdp = System.Drawing.Printing;// Псевдоним пространства имен using swf = System.Windows.Forms; // Псевдоним пространства имен using PageSetupDialog = System.Windows.Forms.PageSetupDialog;// Псевдоним класса namespace Notepad1 { partial class Window1 { //------------------------------------------------------ // // Обработчики источников задач File // //------------------------------------------------------ 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.Focus(); } private void OpenOnExecute(object sender, RoutedEventArgs e) { if(DisplayOpenDialog()) txtBox1.CaretIndex = txtBox1.Text.Length;// Курсор в конец txtBox1.Focus();// Передача фокуса } private void SaveOnExecute(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(strLoadedFile)) DisplaySaveDialog(String.Empty); else SaveFile(strLoadedFile); txtBox1.Focus(); } private void SaveAsOnExecute(object sender, RoutedEventArgs e) { DisplaySaveDialog(strLoadedFile); txtBox1.Focus(); } private void PageSetupOnExecute(object sender, RoutedEventArgs e) { // Ограничемся только показом окна Windows Forms PageSetupDialog dlg = new PageSetupDialog(); // Без настроек не работает. Зададим хотя бы по умолчанию dlg.PageSettings = new sdp.PageSettings(); dlg.PrinterSettings = new sdp.PrinterSettings(); dlg.ShowDialog(); txtBox1.Focus(); } private void PrintPreviewOnExecute(object sender, RoutedEventArgs e) { sdp.PrintDocument document = new sdp.PrintDocument(); document.DocumentName = strLoadedFile; swf.PrintPreviewDialog dlg = new swf.PrintPreviewDialog(); dlg.Document = document; dlg.UseAntiAlias = true;// Включить сглаживание dlg.ShowDialog(); txtBox1.Focus(); } private void PrintOnExecute(object sender, RoutedEventArgs e) { sdp.PrintDocument document = new sdp.PrintDocument(); document.DocumentName = strLoadedFile; swf.PrintDialog dlg = new swf.PrintDialog(); dlg.Document = document; dlg.ShowDialog(); txtBox1.Focus(); } private void ExitOnExecute(object sender, RoutedEventArgs e) { if (!CheckModifiedAndSaveIt()) return; // Пользователь передумал выходить Close(); } } }
Обратите внимание на использование псевдонимов пространств имен добавленных к проекту библиотечных сборок. Еще раз вспомним, что при совместном применении пользовательских интерфейсов WPF и Windows Forms имена типов одной из технологий следует прописывать полностью или использовать псевдонимы. Иначе в коде могут возникнуть конфликты имен, а если компилятор их не обнаружит, то и серьезные ошибки времени выполнения.
- Запустите приложение, испытайте правильность функционирования обработчиков, изучите их код
Обработка системной кнопки
Пока в приложение закрался один существенный недостаток. Когда документ содержит несохраненные изменения и пользователь завершает работу приложения по нашей команде Exit, то все в порядке - приложение извещает о необходимости их сохранить. Но когда окно закрывается по системной кнопке, то извещение отсутствует. Исправим это, для чего воспользуемся событием Closing. В отличии от Closing событие Closed возбудается, когда отменить закрытие окна уже невозможно, а можно только что-то доделать.
- Присоедините к открывающему дескриптору окна Window1 в файле Window1.xaml обработчик события Closing, которое будет возбуждаться при попытке закрытия окна любым способом
<Window x:Class="Notepad1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1: Управление состоянием источников команд" Width="500" Height="375" MinWidth="500" MinHeight="375" WindowStartupLocation="CenterScreen" ResizeMode="CanResizeWithGrip" Loaded="Window_Loaded" Icon="Notepad.ico" Closing="Window_Closing" >
- Заполните обработчик события Closing следующим кодом
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (!CheckModifiedAndSaveIt()) { e.Cancel = true; return; // Пользователь передумал выходить } }
- Запустите приложение - теперь сообщение о несохраненных изменениях выводится и при попытке закрытия окна системной кнопкой
Обратите внимание, что при закрытии окна через наше меню задачей Exit на кнопку отказа от сохранения надо щелкать 2 раза. Это связано с тем, что в обработчике события Closing проверка повторяется. Значит при выходе через меню проверку в обработчике события Closing нужно блокировать с помощью флага.
- Добавьте в класс Window1 поле-флаг _IsExitItem и модифицируйте соответствующим образом обработчики
bool _IsExitItem = false; private void ExitOnExecute(object sender, RoutedEventArgs e) { if (!CheckModifiedAndSaveIt()) return; // Пользователь передумал выходить _IsExitItem = true; Close(); }
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { /* // Эта проверка была бы надежнее if(_IsExitItem) return; */ // !_IsExitItem должен в условии стоять первым if (!_IsExitItem && !CheckModifiedAndSaveIt()) { e.Cancel = true; _IsExitItem = false; return; // Пользователь передумал выходить } }
В последнем обработчике есть один поучительный нюанс: если в условии проверку значения флага поставить последним, то код будет реагировать на наши нововведения. Это происходит потому, что логическое умножение проверяется компилятором слева направо до первого ложного значения. В нашем случае если флаг _IsExitItem==true, то функция CheckModifiedAndSaveIt() выполняться уже не будет. Такой код менее надежен, поскольку зависит от компилятора, да и мы (или сопровождающий программист) можем случайно переставить члены выражения местами. Поэтому лучше заменить этот код на более ясный, как показано в коментариях.
- Запустите приложение и испытайте работу закрытия окна при несохраняемых изменениях