При выполнении в лабораторной работе упражнения №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
Добавление иконок
Теперь добавим к пунктам меню иконки, которые можно взять в архиве VS2008ImageLibrary.zip, устанавливаемом на компьютер вместе с VS-2008 и находящимся в каталоге Program Files\Microsoft Visual Studio 9.0\Common7\VS2008ImageLibrary\1033. Мы будем использовать иконки из папки архива VS2008ImageLibrary\Actions\24bitcolor bitmaps. Для удобства они скопированы в каталог Source данной работы.
- В панели Solution Explorer вызовите контекстное меню для корня проекта Notepad1 и добавьте командой Add/New Folder новую папку с именем Images
- Вызовите для папки Images контекстное меню и командой Add/Existing Item скопируйте в нее иконки с расширением .bmp из папки Source:
- Подключите иконку NewDocument.bmp к пункту меню File/New, модифицировав разметку для этого элемента так
Было <MenuItem Header="_New" InputGestureText="Ctrl+N"> </MenuItem> Стало <MenuItem Header="_New" InputGestureText="Ctrl+N"> <MenuItem.Icon> <Image Source="Images/NewDocument.bmp" Width="16" Height="16" /> </MenuItem.Icon> </MenuItem>
- Запустите приложение и убедитесь, что иконка появилась слева от названия пункта меню New
Здесь есть один недостаток, который бросается в глаза - фон иконки имеет цвет Magenta. В Windows Forms фон устраняется установкой параметра ImageTransparentColor в значение цвета, который система будет считать прозрачным. В WPF для нейтрализации фона можно применить другой механизм, задействующий параметр OpacityMask, но пока мы оставим все как есть.
Создание логических ресурсов
Если в каждый элемент MenuItem мы будем встраивать дескриптор <MenuItem.Icon> с элементом Image, то разметка сильно разбухнет и станет нечитабельным. Чтобы сделать код более элегантным, воспользуемся новым механизмом WPF - логическими ресурсами. Ресурсы хороши тем, что их можно использовать многократно в различных местах приложения. В нашем приложении одни и те же иконки понадобятся в меню, панели инструментов и контекстном меню.
Каждый элемент, производный от класса FrameworkElement, включая окно Window, наследует от FrameworkElement коллекцию Resources типа словаря ресурсов System.Windows. ResourceDictionary. В эту коллекцию можно помещать описания пар ключ-объект, ссылки на которые потом можно подключать к элементам управления для встраивания объектов. Такой прием называется расширением разметки интерфейсных элементов.
Обязательным элементом при объявлении и использовании ресурса является ключ x:Key="идентификатор_ключа", которому можно присваивать произвольное имя. В пределах одной коллекции ключи должны быть уникальными. Использование ссылки на ресурс для свойства элемента выполняется по синтаксису
Свойство="{StaticResource идентификатор_ключа}" или Свойство="{DynamicResource идентификатор_ключа}"
Синтаксический анализатор разметки XAML присвоит свойству объявленный в ресурсе объект. Если применяется ссылка на статический ресурс, то объявление ресурса должно предшествовать его использованию. Встретив ссылку на ресурс анализатор начинает поиск объекта с заданным ключом вначале в коллекции Resources того элемента, где расположена ссылка. Не найдя нужную пару анализатор поднимается на уровень выше и просматривает коллекцию родительского элемента и так далее, пока не будет найдена соответствующая ссылке пара. Таким образом, ресурсы можно объявлять в любом элементе, но лучше сосредоточить их в коллекции <Window.Resources> окна или <Application.Resources> приложения.
- Добавьте после открывающего дескриптора окна объявление логического ресурса, а в самозакрывающемся дескрипторе элемена меню - его использование по ключу
<Window x:Class="Notepad1.Window1" ............................................... > <Window.Resources> <!-- File --> <Image x:Key="iconNew" Source="Images/NewDocument.bmp" Width="16" Height="16" /> </Window.Resources> <DockPanel LastChildFill="True"> <!-- Меню --> <Menu DockPanel.Dock="Top"> <MenuItem Header="_File"> <!-- Сокращенные варианты подключения иконок с использованием статических ресурсов --> <MenuItem Header="_New" InputGestureText="Ctrl+N" Icon="{StaticResource iconNew}" /> ............................................... </MenuItem> ............................................... </Menu> </DockPanel> </Window>
- Запустите приложение и убедитесь, что иконка присутствует слева от названия пункта меню New, но разметка элемента меню стала значительно короче
- Расширьте объявление ресурсов на все иконки
<Window.Resources> <!-- File --> <Image x:Key="iconNew" Source="Images/NewDocument.bmp" Width="16" Height="16" /> <Image x:Key="iconOpen" Source="Images/Open.bmp" Width="16" Height="16" /> <Image x:Key="iconSave" Source="Images/Save.bmp" Width="16" Height="16" /> <Image x:Key="iconPageSetup" Source="Images/PrintSetup.bmp" Width="16" Height="16" /> <Image x:Key="iconPrintPreview" Source="Images/PrintPreview.bmp" Width="16" Height="16" /> <Image x:Key="iconPrint" Source="Images/Print.bmp" Width="16" Height="16" /> <!-- Edit --> <Image x:Key="iconUndo" Source="Images/Edit_Undo.bmp" Width="16" Height="16" /> <Image x:Key="iconRedo" Source="Images/Edit_Redo.bmp" Width="16" Height="16" /> <Image x:Key="iconCut" Source="Images/Cut.bmp" Width="16" Height="16" /> <Image x:Key="iconCopy" Source="Images/Copy.bmp" Width="16" Height="16" /> <Image x:Key="iconPaste" Source="Images/Paste.bmp" Width="16" Height="16" /> <Image x:Key="iconDelete" Source="Images/Delete.bmp" Width="16" Height="16" /> <Image x:Key="iconFind" Source="Images/Find.bmp" Width="16" Height="16" /> <Image x:Key="iconFont" Source="Images/Font.bmp" Width="16" Height="16" /> </Window.Resources>
- Дополните разметку создания главного меню ссылками на ресурс объекта рисунка для свойства Icon
<!-- Меню --> <Menu DockPanel.Dock="Top"> <MenuItem Header="_File"> <!-- Сокращенные варианты подключения иконок с использованием статических ресурсов --> <MenuItem Header="_New" InputGestureText="Ctrl+N" Icon="{StaticResource iconNew}" /> <MenuItem Header="_Open..." InputGestureText="Ctrl+O" Icon="{StaticResource iconOpen}" /> <MenuItem Header="_Save" InputGestureText="Ctrl+S" Icon="{StaticResource iconSave}" /> <MenuItem Header="Save _As..." /> <Separator /> <MenuItem Header="Page Set_up..." Icon="{StaticResource iconPageSetup}" /> <MenuItem Header="P_rint Preview" InputGestureText="Ctrl+F2" Icon="{StaticResource iconPrintPreview}" /> <MenuItem Header="_Print..." InputGestureText="Ctrl+P" Icon="{StaticResource iconPrint}" /> <Separator /> <MenuItem Header="E_xit" /> </MenuItem> <MenuItem Header="_Edit"> <MenuItem Header="_Undo" InputGestureText="Ctrl+Z" Icon="{StaticResource iconUndo}" /> <MenuItem Header="_Redo" InputGestureText="Ctrl+Y" Icon="{StaticResource iconRedo}" /> <Separator></Separator> <MenuItem Header="Cu_t" InputGestureText="Ctrl+X" Icon="{StaticResource iconCut}" /> <MenuItem Header="_Copy" InputGestureText="Ctrl+C" Icon="{StaticResource iconCopy}" /> <MenuItem Header="_Paste" InputGestureText="Ctrl+V" Icon="{StaticResource iconPaste}" /> <MenuItem Header="De_lete" InputGestureText="Del" Icon="{StaticResource iconDelete}" /> <Separator></Separator> <MenuItem Header="_Find..." InputGestureText="Ctrl+F" Icon="{StaticResource iconFind}" /> <MenuItem Header="Find _Next" InputGestureText="F3" /> <MenuItem Header="_Replace..." InputGestureText="Ctrl+H" /> <MenuItem Header="_Go To..." InputGestureText="Ctrl+G" /> <Separator></Separator> <MenuItem Header="Select _All" InputGestureText="Ctrl+A" /> </MenuItem> <MenuItem Header="F_ormat"> <MenuItem Header="_Font..." Icon="{StaticResource iconFont}" /> <Separator /> <MenuItem Header="_Word Wrap" IsCheckable="True" IsChecked="True" InputGestureText="Ctrl+W" /> </MenuItem> <MenuItem Header="_Help"> <MenuItem Header="_About" /> </MenuItem> </Menu>
- Запустите приложение и проверьте наличие иконок в элементах главного меню
Создание панели инструментов, строки состояния и рабочей области
Панель инструментов содержит кнопки быстрого доступа к командам меню, наиболее часто употребляемые пользователем. Обычно она размещается в верхней части рабочей области окна после меню. Панель инструментов представляет собой как минимум один контейнер для элементов управления, чаще всего кнопок, определенный классом ToolBar. Но она может состоять и из нескольких групп инструментов, где каждая группа пакуется в отдельный контейнер ToolBar. Для управления совместным стандартным поведением нескольких панелей инструментов все они упаковываются в контейнер ToolBarTray.
- Добавьте в контейнер размещения DockPanel после разметки меню разметку создания панели инструментов
<!-- Панель инструментов --> <ToolBarTray DockPanel.Dock="Top"> <ToolBar> <Button Width="23" Content="{StaticResource iconNew}" /> <Button Width="23" Content="{StaticResource iconOpen}" /> <Button Width="23" Content="{StaticResource iconSave}" /> </ToolBar> <ToolBar> <Button Width="23" Content="{StaticResource iconUndo}" /> <Button Width="23" Content="{StaticResource iconRedo}" /> <Separator /> <Button Width="23" Content="{StaticResource iconCut}" /> <Button Width="23" Content="{StaticResource iconCopy}" /> <Button Width="23" Content="{StaticResource iconPaste}" /> <Button Width="23" Content="{StaticResource iconDelete}" /> </ToolBar> <ToolBar Header="Find:"> <TextBox Width="100" /> <Button 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>
Контейнер StatusBar строки состояния мы привязали к нижней части окна и наполнили тремя элементами, последний из которых будет имитировать ход процесса загрузки приложения за счет свойства IsIndeterminate="True".
- Добавьте после разметки строки состояния многострочное текстовое поле редактирования TextBox, который будет представлять рабочую область окна приложения и занимать все свободное пространство, поскольку в открывающем дескрипторе панели размещения мы указали для последнего элемента соответствующий параметр <DockPanel LastChildFill="True">
<!-- Многострочное текстовое поле редактирования --> <TextBox TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" VerticalScrollBarVisibility="Auto" > </TextBox>
Параметр TextWrapping="Wrap" переносит строку, если она не помещается по ширине в текстовое поле. AcceptsReturn="True" включает действие клавиши для переноса строк (accept - принимать, признавать). AcceptsTab="True" включает действие клавиши табуляции в текстовом поле. VerticalScrollBarVisibility="Auto" включает автоматическое появление линейки скролирования, когда текст выходит за пределы области редактирования по высоте.
Замена встроенного контекстного меню
- Запустите приложение и вызовите контекстное меню для элемента редактирования щелчком правой кнопкой мыши или клавиатурным жестом Shift+F10
Мы видим, что элемент редактирования имеет по умолчанию контекстное меню, которое называется встроенным. Некоторые элементы управления имеют встроенное контекстное меню с готовой поддержкой команд.
- Проверьте, что команды меню и клавиатурные комбинации выполняют операции вырезания и копирования в буфер обмена, а также вставку из буфера. Работает также и встроенная логика отключения источников команд, когда операция невозможна, например, буфер обмена пуст и нечего вставлять
Для тренировки, в дальнейшем мы переопределим встроенное в TextBox контекстное меню и встроенные команды, а заодно и отключим клавиатурные комбинации, инициирующие встроенные команды.
- Добавьте внутрь дескриптора TextBox разметку контекстного меню с подключенными пиктограммами для элементов
<TextBox.ContextMenu> <ContextMenu Width="100"> <MenuItem Header="Cu_t" Icon="{StaticResource iconCut}" /> <MenuItem Header="_Copy" Icon="{StaticResource iconCopy}" /> <MenuItem Header="_Paste" Icon="{StaticResource iconPaste}" /> <MenuItem Header="De_lete" Icon="{StaticResource iconDelete}" /> </ContextMenu> </TextBox.ContextMenu>
- Запустите проект
Обратите внимание, что применение акселератора Shift+F10 всегда вызывает наше контекстное меню в центре элемента редактирования.
Назначение ресурсов неразделяемыми
Отметьте странное поведение иконок: при открытии разделов меню соответствующая группа иконок исчезает из других элементов управления. Это происходит потому, что по умолчанию логический ресурс считается разделяемым. А это значит, что объект, объявленный в ресурсе, при многократном использовании может появляться только в одном месте. Чтобы это устранить, нужно объявить ресурс неразделяемым с помощью атрибута x:Shared="False" (share - разделять, делиться; shared - общий).
- Добавьте в секцию объявления ресурсов атрибут x:Shared="False"
<Window.Resources> <!-- File --> <Image x:Shared="False" x:Key="iconNew" Source="Images/NewDocument.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconOpen" Source="Images/Open.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconSave" Source="Images/Save.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconPageSetup" Source="Images/PrintSetup.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconPrintPreview" Source="Images/PrintPreview.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconPrint" Source="Images/Print.bmp" Width="16" Height="16" /> <!-- Edit --> <Image x:Shared="False" x:Key="iconUndo" Source="Images/Edit_Undo.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconRedo" Source="Images/Edit_Redo.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconCut" Source="Images/Cut.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconCopy" Source="Images/Copy.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconPaste" Source="Images/Paste.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconDelete" Source="Images/Delete.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconFind" Source="Images/Find.bmp" Width="16" Height="16" /> <Image x:Shared="False" x:Key="iconFont" Source="Images/Font.bmp" Width="16" Height="16" /> </Window.Resources>
- Запустите проект - убедитесь, что сам интерфейс функционирует нормально
Замена иконок на прозрачные
И наконец, прежде чем приступить к кодированию логики, все-таки изменим фон иконок, просто заменив их на рисунки с прозрачным фоном. Такой набор иконок можно найти все в том же поставляемом вместе с Visual Studio 2008 архиве VS2008ImageLibrary.zip в папке VS2008ImageLibrary\Actions\pngformat. Для удобства этот набор скопирован в прилагаемую к работе папку Source. Это файлы типа png и их имена имеют приставку HS (Hide or Show), например, вместо Cut.bmp имя нового файла - CutHS.png.
- Добавьте в папку Images проекта из Source 14 рисунков png командой контекстного меню Add/Existing Item
- Командой Find and Replace (жест Ctrl+H) выполните замену в файле Window1.xaml всех вхождений .bmp на HS.png
- Запустите проект и убедитесь, что фон иконок во всех элементах стал прозрачным
Отключение встроенных команд
Прежде, чем приступить к реализации функциональности с помощью подключения команд, потренируемся на отключении встроенных команд элемента TextBox. Обратите внимание, что хоть мы и заменили встроенное меню TextBox на свое и соответствующие опции в этом контекстном меню перестали работать, все-таки продолжают функционировать встроенные жесты, типичные для текстового поля редактирования: Ctrl+Home, Ctrl+End, Ctrl+X, Ctrl+C, Ctrl+V, Ctrl+Z, Ctrl+Y и т.д. Попробуем отключить встроенные жесты на примере команд Cut (Ctrl+X), Copy (Ctrl+C) и Paste (Ctrl+V).
- Добавьте в открывающий дескриптор окна подписку на событие Loaded, чтобы создать обработчик для размещения процедурного кода, который будет выполнен сразу после инициализации элементов окна, определенных в разметке
<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" > ...................................................... </Window>
- Присвойте в разметке имя элементу TextBox для возможности его кодирования в файле Window1.xaml.cs
<TextBox TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" VerticalScrollBarVisibility="Auto" Name="txtBox1" > ................................................. </TextBox>
- Добавьте в обработчик события Loaded следующий код отключения встроенных жестов вырезания, копирования и вставки в элементе текстового поля
private void Window_Loaded(object sender, RoutedEventArgs e) { // Отключаем в TextBox встроенный жест Ctrl+X для команды Cut KeyBinding keyBinding = new KeyBinding( ApplicationCommands.NotACommand, Key.X, ModifierKeys.Control); txtBox1.InputBindings.Add(keyBinding); // Отключаем в TextBox встроенный жест Ctrl+C для команды Copy keyBinding = new KeyBinding( ApplicationCommands.NotACommand, Key.C, ModifierKeys.Control); txtBox1.InputBindings.Add(keyBinding); // Отключаем в TextBox встроенный жест Ctrl+V для команды Paste keyBinding = new KeyBinding( ApplicationCommands.NotACommand, Key.V, ModifierKeys.Control); txtBox1.InputBindings.Add(keyBinding); }
Ключевым элементом в данном способе отключения жестов является команда NotACommand, которая означает пустую команду. Мы отключили только источники возбуждения встроенных команд для конкретного элемента TextBox, но сами встроенные команды продолжают существовать с теми же самыми жестами.
Подключение иконки приложения
- Скопируйте из папки Source в корень проекта Notepad1 командой контекстного меню Add/Existing Item иконку Notepad.ico и подключите ее к окну Window1
<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>
Распределение класса по нескольким файлам и создание вспомогательных функций
Код решения поставленной задачи будет достаточно большим. Чтобы сделать его обозримым, распределим отдельные группы частичного класса по отдельным файлам в соответствии с разделами меню. Все равно компилятор их увидет и соберет в единую сборку.
- В панели Solution Explorer выделите узел проекта Notepad1 и командой Project/Add Class добавьте три файла с именами File.cs, Edit.cs и Other.cs
- Скопируйте из файла Window1.xaml.cs в каждый из этих новых файлов код подключения пространств имен и отредактируйте заготовки частей класса Window1, которые пока будут одинаковые, так
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 { } }
- Откройте файл Window1.xaml.cs и дополните класс Window1 вспомогательным кодом, после чего он должен стать таким
using System; using System.Windows; using System.Windows.Input; using Microsoft.Win32; // Для стандартных диалогов Win32 using System.IO; // Работа с файлами и каталогами namespace Notepad1 { public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { // Отключаем в TextBox встроенный жест Ctrl+X для команды Cut KeyBinding keyBinding = new KeyBinding( ApplicationCommands.NotACommand, Key.X, ModifierKeys.Control); txtBox1.InputBindings.Add(keyBinding); // Отключаем в TextBox встроенный жест Ctrl+C для команды Copy keyBinding = new KeyBinding( ApplicationCommands.NotACommand, Key.C, ModifierKeys.Control); txtBox1.InputBindings.Add(keyBinding); // Отключаем в TextBox встроенный жест Ctrl+V для команды Paste keyBinding = new KeyBinding( ApplicationCommands.NotACommand, Key.V, ModifierKeys.Control); txtBox1.InputBindings.Add(keyBinding); } #region private Fields - локальные поля //------------------------------------------------------ // // private Fields - локальные поля // //------------------------------------------------------ bool IsModified = false;// Флаг изменений содержимого string strLoadedFile; // Полное имя загруженного документа #endregion private Fields #region Auxiliary Methods - вспомогательные методы //------------------------------------------------------ // // Auxiliary Methods - вспомогательные методы // //------------------------------------------------------ // Метод возвращает true, если содержимое // TextBox не требует сохранения bool flag; bool CheckModifiedAndSaveIt() { if (!IsModified) return true; MessageBoxResult result = MessageBox.Show( "Сохранить изменения?", "", // Контекст и заголовок MessageBoxButton.YesNoCancel, // Кнопки диалога MessageBoxImage.Question, // Иконка вопроса MessageBoxResult.Yes // Кнопка с фокусом ); switch (result) { case MessageBoxResult.Yes: if (String.IsNullOrEmpty(strLoadedFile)) flag = DisplaySaveDialog(""); // Запись с диалогом else flag = SaveFile(strLoadedFile); // Просто запись break; case MessageBoxResult.No: flag = true; break; case MessageBoxResult.Cancel: flag = false; break; } return flag; } // Вызывает диалоговое окно записи файла // и возвращает true, если файл был сохранен bool DisplaySaveDialog(string strFileName) { SaveFileDialog dlg = new SaveFileDialog(); dlg.Filter = "Text Documents(*.txt)|*.txt|All Files(*.*)|*.*"; dlg.FileName = strFileName; bool result = (bool)dlg.ShowDialog(this); // Желание пользователя if (result) result = SaveFile(dlg.FileName); // Возможность компьютера return result; } // Сохраняет документ и возвращает true при успехе // Аргумент - полное имя файла bool SaveFile(string strFileName) { try { File.WriteAllText(strFileName, txtBox1.Text, System.Text.Encoding.GetEncoding(1251)); } catch (Exception e) { // Ловим все исключения и выводим диалог MessageBox.Show( "Ошибка записи файла:\n" + e.Message, "", MessageBoxButton.OK, MessageBoxImage.Asterisk ); return false; } strLoadedFile = strFileName; UpdateTitle(); // Меняем заголовок окна IsModified = false; // Нет изменений текста return true; } // Диалог открытия файла возвращает true при успехе bool DisplayOpenDialog() { flag = CheckModifiedAndSaveIt(); // Проверяем и сохраняем изменения if(!flag) return flag; OpenFileDialog dlg = new OpenFileDialog(); dlg.Filter = "Text Documents(*.txt)|*.txt|All Files(*.*)|*.*"; bool result = (bool)dlg.ShowDialog(this); // Желание пользователя if (result) result = OpenFile(dlg.FileName); // Возможность компьютера return result; } // Открывает файл и при успехе возвращает true bool OpenFile(string strFileName) { try { txtBox1.Text = File.ReadAllText(strFileName, System.Text.Encoding.GetEncoding(1251)); } catch (Exception e) { // Ловим все исключения и выводим диалог MessageBox.Show( "Ошибка чтения файла:\n" + e.Message, "", MessageBoxButton.OK, MessageBoxImage.Asterisk ); return false; } strLoadedFile = strFileName; UpdateTitle(); // Меняем заголовок окна IsModified = false; // Нет изменений текста // Сбрасываем границы выделенного текста поля редактирования txtBox1.SelectionStart = 0; txtBox1.SelectionLength = 0; return true; } // Коррекция заголовка окна void UpdateTitle() { // Извлекаем заголовок окна из словаря ресурсов String title = Application.Current. Resources["ApplicationTitle1"].ToString(); //if (strLoadedFile == null || strLoadedFile.Trim() == String.Empty) if (String.IsNullOrEmpty(strLoadedFile)) // Проще! { this.Title = "Untitled - " + title; return; } // Извлекаем имя файла из полного пути int startIndex = strLoadedFile.LastIndexOf('\\') + 1; int endIndex; // Проверяем в системном реестре настройки системы по скрытию расширения файлов using (RegistryKey filekey = Registry.CurrentUser.CreateSubKey( @"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced")) { if ((filekey != null) && (filekey.GetValue("HideFileExt", 0).ToString() == "0")) { endIndex = strLoadedFile.Length; // Нет расширения } else { endIndex = strLoadedFile.LastIndexOf('.'); // Отсекаем расширение } } if (endIndex > startIndex) { this.Title = strLoadedFile.Substring(startIndex) + " - " + title; } else { this.Title = strLoadedFile.Substring(startIndex, endIndex - startIndex) + " - " + title; } } #endregion Auxiliary Methods } }
Смысл добавленного кода подробно помечен коментариями и следует его внимательно изучить. Ключевым полем работы кода является флаг IsModified, сигнализирующий об изменении содержимого элемента TextBox. Поднятие флага выполним в обработчике события TextChanged этого элемента.
- В разметку TextBox добавьте регистрацию обработчика события TextChanged и создайте сам обработчик
<!-- Многострочное текстовое поле редактирования --> <TextBox TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" VerticalScrollBarVisibility="Auto" Name="txtBox1" TextChanged="txtBox1_TextChanged" > .................................................... </TextBox>
- Обработчик txtBox1_TextChanged() заполните так
private void txtBox1_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e) { if (IsModified) return; else IsModified = true; }