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

События и команды в WPF

Реализация части обработчиков раздела меню Edit

Задачи раздела меню Edit, связанные с использованием буфера обмена, реализовать достаточно легко, поскольку вся необходимая функциональность уже заложена в элементе TextBox. Одними из первых подключим откаты Undo и Redo. Можно было бы создать свой механизм хранения откатов, но мы воспользуемся встроенным в TextBox журналом откатов. Он поддерживается методами Undo() и Redo(). Все члены класса TextBox можно посмотреть в MSDN на русском языке по ссылке

http://msdn.microsoft.com/ru-ru/library/system.windows.controls.textbox_members.aspx

Еще раз вспомним, что мы пока не управляем доступностью элементов-источников задач.

  • В файле Edit.cs заполните часть обработчиков следующим кодом
partial class Window1
    {
        //------------------------------------------------------
        //
        //  Обработчики источников задач Edit
        //
        //------------------------------------------------------
    
        private void UndoOnExecute(object sender, RoutedEventArgs e)
        {
            txtBox1.Undo();
        }
    
        private void RedoOnExecute(object sender, RoutedEventArgs e)
        {
            txtBox1.Redo();
        }
    
        private void CutOnExecute(object sender, RoutedEventArgs e)
        {
            txtBox1.Cut();
    
            // Вариант
            //Clipboard.SetText(txtBox1.SelectedText);
            //txtBox1.SelectedText = "";
        }
    
        private void CopyOnExecute(object sender, RoutedEventArgs e)
        {
            txtBox1.Copy();
    
            // Вариант
            //Clipboard.SetText(txtBox1.SelectedText);
        }
    
        private void PasteOnExecute(object sender, RoutedEventArgs e)
        {
            // Если в буфере содержатся данные текстового формата
            if (Clipboard.ContainsText())
                txtBox1.Paste();
        }
    
        private void DeleteOnExecute(object sender, RoutedEventArgs e)
        {
            txtBox1.SelectedText = String.Empty;
        }
    
        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)
        {
            txtBox1.SelectAll();
        }
    }

Оставшиеся обработчики задач меню Edit требуют более сложного кода с использованием немодальных диалоговых окон, которые должны располагаться всегда поверх основного окна с редактируемым текстом. Попробуем последовательно реализовать их.

Разработка и кодирование диалогового окна Find and Replace

Создадим диалоговое окно и назначим ему в качестве владельца основное окно Window1. Окно, имеющее владельца, всегда располагается поверх него, свертывается и закрывается вместе с ним. Именно так и должно вести себя проектируемое окно Find and Replace в режиме runtime (при выполнении).

  • В панели Solution Explorer выделите узел проекта Notepad1 и командой меню Project/Add Window добавьте новое окно с именем FindAndReplaceDialog
  • Заполните файл FindAndReplaceDialog.xaml разметкой создания интерфейса диалогового окна
<Window x:Class="Notepad1.FindAndReplaceDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Find and Replace"
    WindowStartupLocation="CenterOwner"
    SizeToContent="WidthAndHeight"
    ResizeMode="NoResize"
    ShowInTaskbar="false"
    Activated="OnActivated"
    Background="{StaticResource DialogBackgroundBrush}"
        >
    <Grid ShowGridLines="False" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <!-- Контейнер для кнопок -->
        <StackPanel
            Grid.Row="0"
            Grid.RowSpan="2"
            Grid.Column="2"
            Margin="5"
            >
            <!-- Задаем отступы кнопок -->
            <StackPanel.Resources>
                <Style TargetType="{x:Type Button}">
                    <Setter Property="Margin" Value="3" />
                </Style>
            </StackPanel.Resources>
            <Button
                MinWidth="75"
                MinHeight="23"
                Name="_findNext"
                IsDefault="True"
                IsEnabled="False"
                Click="FindNextClicked"
                Content="_Find Next" />
            <Button
                MinWidth="75"
                MinHeight="23"
                Name="_replace"
                Visibility="Collapsed"
                Click="ReplaceClicked"
                Content="_Replace" />
            <Button
                MinWidth="75"
                MinHeight="23"
                Name="_replaceAll"
                Click="ReplaceAllClicked"
                Visibility="Collapsed"
                Content="Replace _All" />
            <Button
                MinWidth="75"
                MinHeight="23"
                Click="CancelClicked"
                IsCancel="True"
                Content="Cancel" />
        </StackPanel>
    
        <!-- Текстовые метки и текстовые поля в левом верхнем квадранте -->
        <Grid
            ShowGridLines="False"
            Grid.Row="0"
            Grid.Column="0"
            Margin="5 10 0 0" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Label
                Grid.Row="0"
                Grid.Column="0"
                VerticalAlignment="Center"
                Target="{Binding ElementName=_findWhat}"
                Content="Fi_nd what:" />
            <TextBox
                Name="_findWhat"
                Grid.Row="0"
                Grid.Column="1"
                Width="150"
                MaxLength="100"
                Margin="0 5 0 5"
                AcceptsReturn="False"
                TextChanged="FindTextChanged" />
    
            <Label
                Name="_replaceLabel"
                Grid.Row="1"
                Grid.Column="0"
                VerticalAlignment="Center"
                Target="{Binding ElementName=_replaceWith}"
                Visibility="Collapsed"
                Content="Re_place with:" />
            <TextBox
                Name="_replaceWith"
                Grid.Row="1"
                Grid.Column="1"
                Width="150"
                MaxLength="100"
                VerticalAlignment="Center"
                Visibility="Collapsed"
                AcceptsReturn="False" />
        </Grid>
    
        <!-- Секция установки регистра и направления поиска для замены -->
        <Grid
            Grid.Row="1"
            Grid.Column="0"
            >
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
    
            <!-- Установка регистра -->
            <CheckBox
                Name="_matchCase"
                Grid.Column="0"
                Margin="8 0 10 10"
                HorizontalAlignment="Left"
                VerticalAlignment="Bottom"
                IsChecked="False"
                Focusable="True"
                Content="Match _case" />
    
            <!-- Группа радиокнопок направления поиска -->
            <GroupBox 
                Name="_directionGroupBox"
                Header="Direction"
                Grid.Column="1"
                HorizontalAlignment="Right"
                Margin="0 10 0 10">
                <StackPanel Orientation="Horizontal" Margin="10 20 10 10">
                    <RadioButton
                        Name="_findDown"
                        IsChecked="True"
                        Content="_Down" />
                    <RadioButton
                        Name="_findUp"
                        Margin="8 0 0 0"
                        Content="_Up" />
                </StackPanel>
            </GroupBox>
        </Grid>
    </Grid>
</Window>

В настройках окна мы устанавливаем цвет фона из статического ресурса параметром

Background="{StaticResource DialogBackgroundBrush}"

  • Обратите внимание и на другие настройки интерфейса окна и элементов
  • Добавьте в ресурсы приложения файла 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="FindAndReplaceDialog.xaml">
    <Application.Resources>
        <String xmlns="clr-namespace:System;assembly=Mscorlib" x:Key="ApplicationTitle1">
            "Window1: Управление состоянием источников команд"
        </String>
        <String xmlns="clr-namespace:System;assembly=Mscorlib" x:Key="ApplicationTitle2">
            "Window2: Управление состоянием источников команд"
        </String>
        <SolidColorBrush x:Key="DialogBackgroundBrush" 
                         Color="{x:Static SystemColors.ControlColor}" />
    </Application.Resources>
</Application>

Обратите внимание, что временно для просмотра интерфейса в режиме выполнения мы назначили диалоговое окно стартовым окном приложения. Чуть позже мы вернем класс Window1 в категорию стартовых, а пока нам нужно просто посмотреть, как выглядит созданный интерфейс.

  • Запустите приложение Notepad1 - начальный интерфейс для задачи Find должен быть таким


В настройках элементов разметки, связанных с задачей Replace, мы использовали значение атрибута Visibility=" Collapsed ". Есть еще значения Visibility=" Hidden " и Visibility=" Visible " (по умолчанию). Hidden - элемент скрыт (не отображается, но занимает место), Collapsed - элемент свернут (не отображается и не занимает место).

  • Заполните кодовую часть в файле FindAndReplaceDialog.xaml.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.Shapes;
    
namespace Notepad1
{
    public partial class FindAndReplaceDialog : Window
    {
        public FindAndReplaceDialog()
        {
            InitializeComponent();
    
            // Начальная доступность кнопок, хотя кнопки Replece  
            _findNext.IsEnabled = _replace.IsEnabled = _replaceAll.IsEnabled
                = !String.IsNullOrEmpty(_findWhat.Text);
        }
    
        #region Открытые свойства - обертки закрытых полей
        //------------------------------------------------------
        //
        //  Открытые свойства - обертки закрытых полей
        //
        //------------------------------------------------------
    
        // Содержимое текстового поля _findWhat
        public string FindWhat
        {
            get 
            { 
                return _findWhat.Text; 
            }
            set
            {
                _findWhat.Text = value;
            }
        }
    
        // Содержимое текстового поля _replaceWith
        public string ReplaceWith
        {
            get 
            { 
                return _replaceWith.Text; 
            }
            set
            {
                _replaceWith.Text = value;
            }
        }
    
        // Определение состояния флага учета регистра
        public bool? MatchCase { get { return _matchCase.IsChecked; } }
    
        // Определение состояния радиокнопки направления поиска назад
        public bool? SearchUp { get { return _findUp.IsChecked; } }
    
        // Управление видимостью интерфейса замены
        // Hidden - элемент скрыт (не отображается, но занимает место)
        // Collapsed - элемент свернут (не отображается и не занимает место)
        public bool ShowReplace
        {
            get { return _replaceWith.Visibility == Visibility.Visible; }
            set
            {
                Visibility show;
                if (value)
                {
                    // Отобразить
                    show = Visibility.Visible;
                    _directionGroupBox.Visibility = Visibility.Collapsed;
                    _findDown.IsChecked = true;
                }
                else
                {
                    // Свернуть
                    show = Visibility.Collapsed;
                    _directionGroupBox.Visibility = Visibility.Visible;
                }
    
                _replaceLabel.Visibility = _replaceWith.Visibility = 
                    _replace.Visibility = _replaceAll.Visibility = show;
            }
        }
        #endregion Общедоступные свойства - обертки полей
    
        #region Открытые события для обработки в основном классе
        //------------------------------------------------------
        //
        // Открытые события для обработки в основном классе Window1
        // Обеспечивают взаимодействие диалогового окна с владельцем
        //
        //------------------------------------------------------
    
        // Объявляем немаршрутизованные события
        public event EventHandler FindNext;
        public event EventHandler Replace;
        public event EventHandler ReplaceAll;
    
        #endregion Открытые события для обработки в основном классе Window1
    
        #region Закрытые обработчики
        //------------------------------------------------------
        //
        // Закрытые методы
        // При возбуждении событий первый параметр - ссылка на диалог,
        // которую в основном классе приведем к самому диалоговому окну
        //
        //------------------------------------------------------
    
        void OnActivated(object sender, EventArgs e)
        {
            _findWhat.Focus();
        }
    
        private void FindNextClicked(object sender, RoutedEventArgs e)
        {
            // Если на событие подписались, возбуждаем его
            if (FindNext != null)
            {
                FindNext(this, EventArgs.Empty);
            }
        }
    
        private void ReplaceClicked(object sender, RoutedEventArgs e)
        {
            // Возбуждаем событие, если для него существует обработчик
            if (Replace != null)
            {
                Replace(this, EventArgs.Empty);
            }
        }
    
        private void ReplaceAllClicked(object sender, RoutedEventArgs e)
        {
            // Проверяем наличие обработчика и возбуждаем событие
            if (ReplaceAll != null)
            {
                ReplaceAll(this, EventArgs.Empty);
            }
        }
    
        private void CancelClicked(object sender, RoutedEventArgs e)
        {
            this.Close();
        }
    
        private void FindTextChanged(object sender, TextChangedEventArgs e)
        {
            // Управление доступность кнопок в зависимости от текстового поля
            _findNext.IsEnabled = _replace.IsEnabled = _replaceAll.IsEnabled 
                = !String.IsNullOrEmpty(_findWhat.Text);
        }
        #endregion Закрытые обработчики
    }
}

При щелчке на кнопках собственные обработчики кнопок возбуждают в диалоге события, которые будет прослушивать основное окно, перехватывать их и обрабатывать своими обработчиками. При объявлении свойств MatchCase и SearchUp использован тип " bool?", поскольку элементы CheckBox и RadioButton имеют три состояния: IsChecked=" False ", IsChecked=" True " и IsChecked=" {x:Null} ". Для выборки только булевых состояний при программном управлении в клиентском коде этими элементами через открытые свойства нужно будет применять явное приведение типов. Например, caseFlag=(bool)_dlg.MatchCase;, где _dlg - это экземпляр класса FindAndReplaceDialog в клиенте.

  • Запустите приложение для проверки отсутствия синтаксических ошибок

Кодирование функциональности Find and Replace в основном окне

Теперь нужно подключить диалоговое окно Find and Replace к основному окну Window1 для совместной работы и наделить его соответствующей функциональностью. Но прежде надо вновь назначить Window1 стартовым окном приложения.

  • Откройте файл App.xaml приложения Notepad1 и внесите изменения в открывающий дескриптор:
Было      StartupUri="FindAndReplaceDialog.xaml"
  
  Стало     StartupUri="Window1.xaml"

Поскольку код программного управления диалоговым окном Find and Replace будет несколько великоват, разместим его в отдельном файле с именем EditFind.cs.

  • В панели Solution Explorer выделите узел проекта Notepad1 и вызовите командой меню Project/Add New Item одноименное диалоговое окно оболочки, которое настройте так

>

После щелчка на кнопке Add мастер добавит пустой файл в текущий проект решения.

  • Заполните файл EditFind.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
    {
        internal FindAndReplaceDialog _dlg = null; // Для видимости в обработчиках
        string _findString, _replaceString; // Для видимости в обработчиках
    
        void CreateDialog()
        {
            if (_dlg != null)// Уже существует
            {
                // Если есть выделенное, обновляем прежнее
                if (!String.IsNullOrEmpty(txtBox1.SelectedText))
                    _findString = _dlg._findWhat.SelectedText = txtBox1.SelectedText;
                _dlg._findWhat.Focus();
                return;
            }
    
            // Создать заново
            _dlg = new FindAndReplaceDialog();
    
            _dlg.Owner = this;  // Привязываем диалог к владельцу
            _dlg.Show();// Немодальное, поэтому не перехватывает управление
    
            // Продолжаем настраивать
            _dlg._findWhat.Focus();
    
            // Если есть выделенное, обновляем прежнее
            if (!String.IsNullOrEmpty(txtBox1.SelectedText))
                _findString = _dlg._findWhat.SelectedText = txtBox1.SelectedText;
    
            _dlg.ReplaceWith = _replaceString;
    
            // Анонимные обработчики
            _dlg.FindNext += delegate(object sender, EventArgs args)
            {
                FindNextExec();
            }; //!!!
            _dlg.Replace += delegate(object sender, EventArgs args)
            {
                ReplaceExec();
            }; //!!!
            _dlg.ReplaceAll += delegate(object sender, EventArgs args)
            {   // У Петцольда (WPF с.466) есть иной вариант этого обработчика
                _replaceString = _dlg.ReplaceWith;
                txtBox1.SelectionStart = 0;
                txtBox1.SelectionLength = 0;
    
                while (FindNextExec())
                {
                    using (txtBox1.DeclareChangeBlock())
                    {
                        txtBox1.SelectedText = _replaceString;
                        txtBox1.SelectionLength = _replaceString.Length;
                    }
                }
    
                txtBox1.SelectionStart = 0;
                txtBox1.SelectionLength = 0;
            }; //!!!
            _dlg.Closed += delegate(object sender, EventArgs args)
            {
                _dlg = null;
            }; //!!! Точка с запятой обязательна - заканчивает строку
        }
    
        bool FindNextExec()
        {
            int indexStart, indexFind;// Откуда начать и начало следующего
            _findString = _dlg.FindWhat; // Извлекаем текст поиска
    
            // Учет регистра при поиске, однострочный условный оператор
            StringComparison strComp = (bool)_dlg.MatchCase ? StringComparison.Ordinal :
                    StringComparison.OrdinalIgnoreCase;
    
            if ((bool)_dlg.SearchUp)// Ищем вверх
            {
                indexStart = txtBox1.SelectionStart - 1;
                indexFind = txtBox1.Text.LastIndexOf(_findString, indexStart, strComp);
            }
            else                    // Ищем вниз
            {
                indexStart = txtBox1.SelectionStart + txtBox1.SelectionLength;
                indexFind = txtBox1.Text.IndexOf(_findString, indexStart, strComp);
            }
    
            // Анализируем и принимаем решение
            if (indexFind != -1)
            {
                txtBox1.Select(indexFind, _findString.Length);// Выделяем найденное 
                txtBox1.Focus();
                return true;
            }
            else
            {
                MessageBox.Show("Текст \"" + _findString + "\" не найден!",
                    this.Title, MessageBoxButton.OK, MessageBoxImage.Information);
                txtBox1.Focus();
                return false;
            }
        }
    
        private void ReplaceExec()
        {
            // Извлекаем тексты поиска и замены
            _findString = _dlg.FindWhat;
            _replaceString = _dlg.ReplaceWith;
    
            // Учет регистра при поиске, однострочный условный оператор
            StringComparison strComp = (bool)_dlg.MatchCase ? StringComparison.Ordinal :
                    StringComparison.OrdinalIgnoreCase;
    
            if (_findString.Equals(txtBox1.SelectedText, strComp))
                txtBox1.SelectedText = _replaceString;
    
            FindNextExec();
        }
    }
}

Привязать к владельцу диалоговое окно нужно раньше, чем оно будет нарисовано, иначе заданная опция центрирования относительно владельца действовать не будет!

  • Код получился (на мой взгляд!) интересным и поучительным - попробуйте разобраться в нем

Для разнообразия, чтобы не придумывать новые имена обработчикам, которые все равно как методы нигде далее сами вызывать не будем (кроме как автоматически через события), мы применили синтаксис анонимных обработчиков. Тот код, который нам придется вызывать не только через события диалогового окна, но и через интерфейсные элементы основного окна Window1, упакован в отдельные функции.

Обратите внимание, что при объявлении ссылки на экземпляр окна мы применили модификатор доступности internal (внутренний). Этот оператор обычно используется для типов (а не членов типа), видимость которых нужно ограничить только текущей сборкой. В нашем случае это ни на что не влияет и его можно убрать или заменить на private, но интересно отметить вот что...

  • Откройте панель Class View в разделе View оболочки и посмотрите на пиктограммы членов класса Window1, который мы как раз сейчас и расширяем


Оказывается, что те члены-поля класса, которые мы применили как элементы в разметке, считаются с видимостью internal (конверт на пиктограмме). А все члены класса, которые мы объявили в процедурном коде без указания модификатора видимости (доступности), считаются закрытыми (замок на пиктограмме). Ну это так, к слову, и на наше приложение никак не влияет.

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

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

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