- Откройте файл Window1.xaml.cs текущего проекта Notepad2, найдите объявление поля modified и переименуйте его вновь в IsModified так
- Откройте файл EnabledControls.cs, уберите в нем лишний код, чтобы остался только такой
- Внесите в файл EnabledControls.cs код определения и инициализации команд вместе со встроенными жестами
Мы объявили псевдонимы команд как общедоступные поля класса Window1. Команды, которые имеют встроенные жесты или не должны иметь жестов, мы инициализировали сразу. Две команды только объявили, но сами объекты собираемся создать в коде. Код создания этих команд мы поместим в статический конструктор для добавления жестов. Это нужно для того, что добавление жестов требует действий, а это разрешено только в методах. Конструктор должен быть обязательно статическим, чтобы мог выполниться до создания экземпляра окна. Все команды должны к этому времени уже существовать, поскольку используются в разметке окна при создании интерфейсных элементов.
- Добавьте в класс Window1 файла EnabledControls.cs статический конструктор со следующим кодом
Не забывайте, что статический конструктор класса в C# не принимает параметров и может существовать только в единственном экземпляре (если объявим).
Теперь в двух пользовательских командах имеются как жесты, так и вся необходимая информация для отображения в пунктах меню.
В соответствии с планом, присоединим команды к источникам, вначале в коде.
- Добавьте в функцию AdditionalHandlers() файла EnabledControls.cs следующий код
- Добавьте в открывающем дескрипторе <Window> файла Window1.xaml параметр отображения пространства имен класса процедурного кода на разметку, чтобы компилятор видел вставляемые в разметку команды. Имя отображения можно принять произвольно - выберите myCmd
Когда вы начнете вручную набирать запись xmlns:myCmd=, то после ввода знака присваивания IntelliSense выдаст подсказку, в которой нужно выбрать выделенную на снимке опцию списка
В результате будет догенерирована следующая запись
<Window x:Class="Notepad2.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:myCmd="clr-namespace:Notepad2" Title="Window1: Управление состоянием источников команд" Width="500" Height="375" MinWidth="500" MinHeight="375" WindowStartupLocation="CenterScreen" ResizeMode="CanResizeWithGrip" Loaded="Window_Loaded" Icon="Notepad.ico" Closing="Window_Closing" Activated="Window_Activated" >.............................................. </Window>Теперь отредактируем разметку 12 запланированных источников задач в соответствии с таблицей
12 задач | ||
Где присоединить к источнику? | Задача | Alias (псевдоним) |
Разметка | Save | SaveCommand |
Разметка | Page Setup | PageSetupCommand |
Разметка | Undo | UndoCommand |
Разметка | Redo | RedoCommand |
Разметка | Cut | CutCommand |
Разметка | Copy | CopyCommand |
Paste | PasteCommand | |
Delete | DeleteCommand | |
Find Next | FindNextCommand | |
Replace | ReplaceCommand | |
Go To | GoToCommand | |
Select All | SelectAllCommand |
- В файле Window1.xaml текущего проекта удалите в источниках для первых 6 задач таблицы тексты жестов и замените записи с событием Click на записи с присоединением команд
- В файле Window1.xaml текущего проекта удалите в источниках для последних 6 задач таблицы тексты жестов и записи события Click. Для этих источников присоединение команд мы уже выполнили в процедурном коде
Правленный код разметки станет таким (для удобства файл Window1.xaml с новым содержимым приводится полностью)
<Window x:Class="Notepad2.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:myCmd="clr-namespace:Notepad2" Title="Window1: Управление состоянием источников команд" Width="500" Height="375" MinWidth="500" MinHeight="375" WindowStartupLocation="CenterScreen" ResizeMode="CanResizeWithGrip" Loaded="Window_Loaded" Icon="Notepad.ico" Closing="Window_Closing" Activated="Window_Activated" > <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" Command="myCmd:Window1.SaveCommand" Header="_Save" Icon="{StaticResource iconSave}" /> <MenuItem Name="itemSaveAs" Click="SaveAsOnExecute" Header="Save _As..." /> <Separator /> <MenuItem Name="itemPageSetup" Command="myCmd:Window1.PageSetupCommand" 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" Command="myCmd:Window1.UndoCommand" Header="_Undo" Icon="{StaticResource iconUndo}" /> <MenuItem Name="itemRedo" Command="myCmd:Window1.RedoCommand" Header="_Redo" Icon="{StaticResource iconRedo}" /> <Separator></Separator> <MenuItem Name="itemCut" Command="myCmd:Window1.CutCommand" Header="Cu_t" Icon="{StaticResource iconCut}" /> <MenuItem Name="itemCopy" Command="myCmd:Window1.CopyCommand" Header="_Copy" Icon="{StaticResource iconCopy}" /> <MenuItem Name="itemPaste" Header="_Paste" Icon="{StaticResource iconPaste}" /> <MenuItem Name="itemDelete" Header="De_lete" Icon="{StaticResource iconDelete}" /> <Separator></Separator> <MenuItem Name="itemFind" Click="FindOnExecute" Header="_Find..." InputGestureText="Ctrl+F" Icon="{StaticResource iconFind}" /> <MenuItem Name="itemFindNext" Header="Find _Next" /> <MenuItem Name="itemReplace" Header="_Replace..." /> <MenuItem Name="itemGoTo" Header="_Go To..." /> <Separator></Separator> <MenuItem Name="itemSelectAll" Header="Select _All" /> </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" Command="myCmd:Window1.SaveCommand" Width="23" Content="{StaticResource iconSave}" /> </ToolBar> <ToolBar> <Button Name="btnUndo" Command="myCmd:Window1.UndoCommand" Width="23" Content="{StaticResource iconUndo}" /> <Button Name="btnRedo" Command="myCmd:Window1.RedoCommand" Width="23" Content="{StaticResource iconRedo}" /> <Separator /> <Button Name="btnCut" Command="myCmd:Window1.CutCommand" Width="23" Content="{StaticResource iconCut}" /> <Button Name="btnCopy" Command="myCmd:Window1.CopyCommand" Width="23" Content="{StaticResource iconCopy}" /> <Button Name="btnPaste" Width="23" Content="{StaticResource iconPaste}" /> <Button Name="btnDelete" 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" TextChanged="txtBox1_TextChanged" HorizontalScrollBarVisibility="Auto" > <TextBox.ContextMenu> <ContextMenu Width="100"> <MenuItem Name="contextCut" Command="myCmd:Window1.CutCommand" Header="Cu_t" Icon="{StaticResource iconCut}" /> <MenuItem Name="contextCopy" Command="myCmd:Window1.CopyCommand" Header="_Copy" Icon="{StaticResource iconCopy}"/> <MenuItem Name="contextPaste" Header="_Paste" Icon="{StaticResource iconPaste}" /> <MenuItem Name="contextDelete" Header="De_lete" Icon="{StaticResource iconDelete}" /> </ContextMenu> </TextBox.ContextMenu> </TextBox> </DockPanel></Window>Эта разметка визуально получилась достаточно широкой, ее лучше прежде скопировать, а потом разбирать.
- Удалите (я закомментировал) код создания жестов в функции CreateGestures() файла KeyGestures.cs для выбранных нами 12 задач (для задачи Page Setup жестов нет)
- Запустите приложение и убедитесь, что тексты жестов во всех источниках команд присутствуют несмотря на то, что мы их только что явно удалили. Теперь жесты в источники попадают из команд
В начальном состоянии источники команд являются недоступными, поскольку пока нет привязки к элементам визуального дерева и обработчикам. Исправим это и привяжем команды к коллекции CommandBindings окна Window1. Причем, в соответствии с намеченным планом выполним привязку одной части команд в коде, а остальной - в разметке.
В месте привязки для каждого объекта привязки нужно указать имя команды, имя обработчика события Executed и имя обработчика события CanExecute для управления доступностью источников. Оба события всплывающие, поэтому местом привязки можно выбрать любой элемент маршрута, например, окно Window1. Это корень визуального дерева и ни одно всплывающее событие мимо него не пройдет.
Обработчики события Executed для выполнения команд у нас есть, мы их уже создавали для обработки события Click. Например, для команды Save обработчик имеет имя SaveOnExecute и его сигнатура выглядит так
private void SaveOnExecute(object sender, RoutedEventArgs e) { }Нам предстоит создать обработчики события CanExecute и мы разместим их в файле EnabledControls.cs. Они будут иметь несколько иную сигнатуру. Например, для команды Save такой обработчик должен иметь следующую заготовку
private void SaveCanExecute(object sender, CanExecuteRoutedEventArgs e) { }- Добавьте в класс Window1 файла EnabledControls.cs заготовки обработчиков события CanExecute для выбранных нами ранее 12 команд (число-то какое хорошее!)
Теперь пришла пора выполнить саму привязку в соответствии с намеченным планом
План 12 задач, которые предстоит реализовать командами | ||||
Где привязать к окну? | Задача | Alias (псевдоним) | Жесты | Регулировать доступность источников? |
Разметка | Save | SaveCommand | Ctrl+S | Да |
Разметка | Page Setup | PageSetupCommand | Нет | |
Разметка | Undo | UndoCommand | Ctrl+Z | Да |
Код | Redo | RedoCommand | Ctrl+Y | Да |
Код | Cut | CutCommand | Ctrl+X | Да |
Код | Copy | CopyCommand | Ctrl+C | Да |
Разметка | Paste | PasteCommand | Ctrl+V | Да |
Разметка | Delete | DeleteCommand | Del | Да |
Разметка | Find Next | FindNextCommand | F3 | Да |
Код | Replace | ReplaceCommand | Ctrl+H | Да |
Код | Go To | GoToCommand | Ctrl+G | Нет |
Код | Select All | SelectAllCommand | Ctrl+A | Да |
Обратите внимание, что источники команд PageSetupCommand и GoToCommand должны быть доступны всегда, поэтому привяжем для них только обработчики события Executed
- Удалите в файле EnabledControls.cs заготовки обработчиков события CanExecute для команд PageSetupCommand и GoToCommand (или не удаляйте и пусть болтаются как незадействованные методы)
- В файле Window1.xaml текущего проекта выполните привязку части команд к объекту окна, для этого после открывающего дескриптора окна вставьте следующую разметку
- В файле EnabledControls.cs добавьте в функцию AdditionalHandlers() код привязки части команд к объекту окна, после чего функция должна стать такой
После того, как ссылка binding, которая пока единствественная адресовала объект, передаст адрес закрепленного за ней объекта в коллекцию CommandBindings элемента, этот объект станет адресоваться в двух местах: в коллекции и в ссылке. Поэтому такая ссылка станет уже излишней и ее можно будет использовать для присвоения адреса нового объекта. А коллекция так и будет продолжать адресовать брошенный ссылкой объект. В коде показано применение вариантов перегрузок конструктора класса CommandBinding и настройки свойств объекта.
- Запустите приложение и убедитесь в следующем...
Источники, к которым мы присоединили команды, по прежнему остаются недоступными, как после того, как мы к ним команды только присоединили. Но заметьте, что два источника: File/Page Setup... и Edit/Go To... открылись и функционируют, а для Go To даже клавиатурный жест Ctrl+G действует нормально. Это происходит потому, что мы для них не зарегистрировали обработчики событий CanExecute и они свободно вызывают обработчики, зарегистрированные в событии Executed, а остальные источники ждут разрешение на доступность. Создание таких разрешений в обработчиках CanExecute и составляет суть реализации логики доступности источников команд.