Шрифт:
Результат. «Дикая» кнопка с пустым заголовком не дает на себя нажать, «убегая» от курсора мыши. Для того чтобы ее «приручить», надо переместить на нее курсор, держа нажатой клавишу Ctrl. После щелчка на дикой кнопке она приручается: на ней появляется заголовок «Изменить», и она перестает убегать от курсора мыши. Следует заметить, что приручить кнопку можно и с помощью клавиатуры, переместив на нее фокус с помощью клавиш со стрелками (или клавиши Tab) и нажав на клавишу пробела.
Недочет. Если попытаться «приручить» кнопку, переместив на нее фокус и нажав клавишу пробела, то перед приручением она прыгает по окну, пока не будет отпущена клавиша пробела. Причины такого поведения непонятны, поскольку нажатие клавиши пробела не должно приводить к активизации события, связанного с перемещением мыши. Следует, однако, отметить, что нажатие пробела обрабатывается в WPF особым образом, и по этой причине оно может приводить к таким странным эффектам.
Исправление. Дополните условие в методе button2_MouseMove:
Прирученная кнопка пока ничего не делает. Это будет исправлено в следующем пункте.
Комментарии
1. В данном пункте демонстрируется возможность отсоединения метода-обработчика от события, с которым он ранее был связан. Для этого используется операция –=, слева от которой указывается событие, а справа – тот обработчик, который надо отсоединить от события.
2. В обработчике button2_MouseMove определяются текущие размеры компонента Canvas, чтобы обеспечить случайное перемещение дикой кнопки только в пределах этого компонента (метод r.NextDouble возвращает случайное вещественное число в полуинтервале [0; 1), при этом вычитание числа 5 гарантирует, что дикая кнопка будет видна на экране хотя бы частично). Заметим, что программа правильно реагирует на изменение размера окна: дикая кнопка всегда перемещается в пределах его текущего размера. Это обеспечивается благодаря тому, что панель Canvas по умолчанию занимает всю клиентскую область своего родителя-окна.
Поскольку мы не присвоили компоненту Canvas имя, нам пришлось обращаться к нему через его родителя, вызвав для окна его свойство Content и, кроме того, выполнив явное приведение типа с помощью операции as. Вместо приведения к типу Canvas можно было бы выполнить приведение к типу FrameworkElement – первому типу в иерархии наследования компонентов, в котором определены свойства, связанные с размерами. Можно было выполнить приведение к типу Panel – непосредственному потомку FrameworkElement, который является предком всех группирующих компонентов. Заметим, что выполнить приведение класса Canvas к типу Control не удастся, так как группирующие компоненты к данному типу не относятся (потомками Control являются, в частности, компоненты, имеющие свойство Content, например кнопки).
3. Следует обратить внимание на способ, с помощью которого в обработчике button2_MouseMove проверяется, нажата ли клавиша Ctrl. Обычно дополнительная информация о произошедшем событии передается в обработчик с помощью второго параметра e. Например, в обработчике button2_MouseMove с помощью данного параметра (типа MouseEventArgs) можно определить текущую позицию мыши (вызвав метод e.GetPosition) или состояние кнопок мыши (вызвав, например, свойство e.LeftButton и сравнив его с одним из его возможных значений – MouseButtonState.Pressed или MouseButtonState.Released). Однако информацию о нажатых в данный момент клавишах параметр e типа MouseEventArgs не содержит. Тем не менее подобную информацию можно получить с помощью статического свойства IsKeyDown класса Keyboard. Это же свойство мы использовали при исправлении обнаруженного недочета, чтобы в случае, если нажата клавиша пробела, обработчик button2_MouseMove не выполнял никаких действий.
4. Обратите также внимание на то, что поле r в классе WainWinbdow не только описывается, но и сразу инициализируется (с помощью конструктора Random без параметров). В какой момент выполняется данная инициализация? По правилам языка C#, явно указанные операторы инициализации всех полей класса автоматически помещаются в начало любого конструктора данного класса. Таким образом, поле r будет инициализировано в начале выполнения конструктора окна (перед выполнением оператора InitializeComponent, указанного в теле данного конструктора). Разумеется, можно было бы поступить иначе: описать поле r типа Random без его инициализации
и поместить в конструктор окна оператор
1.5. Присоединение к событию другого обработчика
Для того чтобы прирученная кнопка при нажатии на нее выполняла какие-либо действия, можно добавить эти действия к уже имеющемуся обработчику button2_Click. Однако в этом случае обработчик должен проверять, в каком состоянии находится кнопка – диком или прирученном. Поступим по-другому: свяжем событие Click для прирученной кнопки с новым обработчиком. Такой подход позволит продемонстрировать в нашем проекте ряд особенностей, связанных с действиями по присоединению и отсоединению обработчиков.
Новый обработчик (назовем его button2_Click2) создадим «вручную», не прибегая к услугам окна Properties или xaml-файла. Для этого в конце описания класса MainWindow в файле MainWindow.xaml.cs (перед двумя последними скобками «}») добавим описание этого обработчика:
Чтобы подчеркнуть, что в данном случае никакая часть обработчика не создается автоматически, мы выделили весь текст обработчика полужирным шрифтом.
В метод button2_Click добавьте следующие операторы (здесь и далее в книге предполагается, что если место добавления не уточняется, то операторы надо добавлять в конец метода):
В метод Canvas_MouseDown добавьте операторы:
Результат. Прирученная кнопка теперь выполняет полезную работу – щелчок на ней приводит к разворачиванию окна программы на весь экран, а новый щелчок восстанавливает первоначальное состояние окна. Если же щелкнуть мышью на окне (не на кнопке), то услужливая кнопка «Закрыть» прибежит на вызов, а прирученная кнопка «Изменить» снова одичает, потеряет текст своего заголовка и начнет убегать от мыши.
Комментарий
Приведенные тексты методов показывают, что при смене обработчика недостаточно присоединить к событию новый обработчик; необходимо также отсоединить от события обработчик, ранее связанный с ним. Данное обстоятельство обусловлено тем важным фактом, что к одному и тому же событию можно последовательно присоединить несколько обработчиков (для этого достаточно применить к этому событию несколько раз оператор +=). Следует отметить, что данная возможность для событий визуальных компонентов применяется крайне редко (достаточно отметить, что с помощью окна Properties или xaml-файла присоединить к одному событию несколько обработчиков нельзя). В то же время при явном присоединении обработчиков эта особенность может приводить к появлению трудно выявляемых ошибок, если, например, один и тот же обработчик будет присоединен к событию несколько раз. Подобные проблемы можно проиллюстрировать с помощью нашей программы, если закомментировать заголовок оператора if в обработчике Canvas_MouseDown:
Если теперь после запуска программы несколько раз щелкнуть мышью на окне, а затем приручить кнопку button2, то при ее последующем нажатии окно несколько раз последовательно перейдет из развернутого состояния в стандартное и обратно. Это объясняется тем, что теперь каждый щелчок на окне присоединяет к событию Click кнопки button2 новый экземпляр обработчика button2_Click, и при нажатии на эту кнопку каждый экземпляр обработчика последовательно запускается на выполнение. Ситуация осложняется еще тем обстоятельством, что в программе невозможно выяснить, сколько и какие обработчики присоединены в настоящий момент к данному событию (а не зная этого, нельзя и обеспечить отсоединение от события всех его обработчиков).
Итак, к действиям по явному присоединению обработчика к событию и его последующему отсоединению следует подходить крайне осторожно.