Практикум прикладного программирования на C# в среде VS.NET 2005

Стандартные меню приложений

Разбить на страницы
Показывать лекцию целиком

Файлы к лабораторной работе Вы можете скачать здесь.

В библиотеке .NET Framework 2.0 появились три новых класса для организации пользовательского интерфейса управления приложением ( Strip - полоса):

  • System.Windows.Forms.MenuStrip - главное меню
  • System.Windows.Forms.ToolStrip - панель инструментов
  • System.Windows.Forms.StatusStrip - панель состояния
  • Наряду с этими новыми элементами существуют и поддерживаются прежние аналогичные классы, существующие в версиях .NET Framework 1.x:

  • System.Windows.Forms.MainMenu
  • System.Windows.Forms.ToolBar
  • System.Windows.Forms.StatusBar
  • По умолчанию эти старые классы не подключены к панели Toolbox, поэтому не видны в ней, хотя и находятся в GAC (Global Assembly Cache). Но их легко подключить в любой свиток панели, вызвав для этого свитка контекстное меню и выполнив команду Choose Items

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

    В данной работе мы будем рассматривать вопросы, касающиеся способов построения различных видов меню. Для демонстрации упражнений с элементами меню создадим заготовку приложения со стартовой формой.

    Заготовка приложения со стартовой формой

    Мы еще не знаем, сколько упражнений получится в этой работе, поэтому применим список ListBox, в который можно добавлять сколько угодно опций вызова упражнений. При этом будем использовать рассмотренный ранее прием предотвращения запуска уже запущенной формы.

  • Выполните команду File/New/Project и заполните мастер создания приложения в соответствии с рисунком
  • Через панель Solution Explorer переименуйте файл Form1.cs в Start.cs, а заголовку окна формы присвойте значение Text =" Lab48 - Имя студента "
  • Поместите на форму библиотечный компонент ListBox и настройте его в соответствии с таблицей свойств
  • Таблица свойств экземпляра ListBox
    Свойство Значение
    Name listBoxChoice
    Items Упражнение 1.
      Упражнение 2.
      Упражнение 3.
    Dock Fill
  • Через панель Properties создайте обработчик для события SelectedIndexChanged объекта listBoxChoice, который заполните так
  • Form frm;   // Объявили поле
            private void listBoxChoice_SelectedIndexChanged(object sender, EventArgs e)
            {
                // Привязали номер упражнения к индексу списка
                ListBox list = (ListBox)sender;
                string formName = "Form" + (list.SelectedIndex + 1);
        
                ///////////////////////////////////////////////////
                // Следующий код взят из лабораторной работы №47
                ///////////////////////////////////////////////////
                // Читаем коллекцию запущенных форм
                FormCollection fc = Application.OpenForms;
                bool IsRun = false;
                // Перебираем коллекцию
                foreach (Form form in fc)
                {
                    // Имя очередной запущенной формы сравниваем с закрепленным за кнопкой
                    if (form.Name == formName)
                    {
                        IsRun = true;   // Нашли, что форма запущена, поднимаем флаг
                        frm = form;     // Сохраняем ссылку на запущенную форму для фокуса
                        break;          // Выходим из цикла
                    }
                }
        
                // Если форма не запущена - запускаем
                if (!IsRun)
                {
                    switch (formName)
                    {
                        case "Form1":
                            frm = new Form1();
                            break;
                        case "Form2":
                            frm = new Form2();
                            break;
                        case "Form3":
                            frm = new Form3();
                            break;
                    }
        
                    this.AddOwnedForm(frm);     // Сделать новую frm подчиненной Start
                    // frm.Owner = this;        // Алтернативный способ назначить владельца
                    frm.ShowInTaskbar = false;  // Не отображать метку окна в панели задач
                    frm.Show();                 // Показать новую форму
                }
                else
                    frm.Focus();                // Передать фокус запущенной форме        
            }

    Недостаток данного кода в том, что мы привязали вызов формы упражнения к индексу списка.

  • Командой меню оболочки Project/Add Windows Form добавьте в проект еще три формы с именами Form1, Form2, Form3
  • Запустите приложение и убедитесь в работоспособности механизма вызова отдельных упражнений
  • Упражнение 1. Простое меню

    Рассмотрим простую программу, имитирующую работу меню.

  • Откройте файл Form1.cs в режиме View Code и добавьте к конструктору класса код создания простого меню
  • public Form1()
            {
                InitializeComponent();
        
                this.Text = "Простое меню";
        
                // Главное меню
                MenuStrip menu = new MenuStrip();
                this.Controls.Add(menu);
        
                // Раздел File
                ToolStripMenuItem itemFile = new ToolStripMenuItem();
                itemFile.Text = "File";
                menu.Items.Add(itemFile);// Элемент верхнего уровня
        
                // Раздел Help
                ToolStripMenuItem itemHelp = new ToolStripMenuItem();
                itemHelp.Text = "Help";
                menu.Items.Add(itemHelp);
        
                // Заполнение раздела File
                // Команда Open...
                ToolStripMenuItem itemOpen = new ToolStripMenuItem();
                itemOpen.Text = "Open...";
                itemOpen.ShortcutKeys = Keys.Control | Keys.O; // Ctrl+O
                itemOpen.ShowShortcutKeys = true;// Отображать в команде
                // Регистрировать вручную!!!!!!!!!!!!!!
                itemOpen.Click += new EventHandler(itemOpen_Click);
                itemFile.DropDownItems.Add(itemOpen);
                // itemOpen.ShowShortcutKeys = false;// Скрыть комбинацию 
        
                // Разделитель
                ToolStripSeparator itemSep = new ToolStripSeparator();
                itemFile.DropDownItems.Add(itemSep);
        
                // Команда Exit
                ToolStripMenuItem itemExit = new ToolStripMenuItem();
                itemExit.Text = "Exit";
                // Регистрировать вручную!!!!!!!!!!!!!!
                itemExit.Click += new EventHandler(itemExit_Click);
                itemFile.DropDownItems.Add(itemExit);
        
                // Заполнение раздела Help
                // Команда About...
                ToolStripMenuItem itemAbout = new ToolStripMenuItem();
                itemAbout.Text = "About...";
                itemAbout.ShortcutKeys = Keys.Control | Keys.A; // Ctrl+A
                itemAbout.ShortcutKeyDisplayString = "Ctrl^A";// Свое отображение
                itemAbout.ShowShortcutKeys = true;// Отображать в команде
                // Регистрировать вручную!!!!!!!!!!!!!!
                itemAbout.Click += new EventHandler(itemAbout_Click);
                itemHelp.DropDownItems.Add(itemAbout);
            }
  • Перепишите выделенные строки кода регистрации обработчиков события Click вручную, чтобы правильно создать заготовки обработчиков, и заполните обработчики следующим кодом
  • void itemAbout_Click(object sender, EventArgs e)
            {
                MessageBox.Show("Заготовка опции \"About...\"", this.Text);
            }
        
            void itemExit_Click(object sender, EventArgs e)
            {
                this.Close();
            }
        
            void itemOpen_Click(object sender, EventArgs e)
            {
                MessageBox.Show("Заготовка опции \"Open\"", this.Text);
            }
        }

    В программе мы создаем объект меню, как экземпляр класса MenuStrip, и привязали его к объекту формы . У класса MenuStrip есть свойство Items, которое является коллекцией элементов меню верхнего уровня. Мы создаем два таких объекта itemFile и itemHelp типа ToolStripMenuItem и добавляем их в свойство Items. У каждого объекта типа ToolStripMenuItem есть свойство DropDownItems, готовое хранить коллекцию команд и разделителей, в том числе вложенных для построения меню сложной иерархии. В эти свойства элементов верхнего уровня мы добавляем объекты команд.

    Схематично такая процедура построения меню примера изображена на рисунке

    Символ амперсанда в значении свойства Text наименования команды ассоциируется со знаком подчеркивания следующей за ним буквы и позволяет после нажатия клавиши Alt (не удерживая) вызывать команды простым нажатием соответствующих клавиш. Знак подчеркивания в работающей программе появляется только после щелчка на клавише Alt.

    Свойство ShortcutKeys объекта команды ожидает побитовую комбинацию элементов перечисления System.Windows.Forms. Keys, задающую клавиши быстрого вызова этой команды. Логическое свойство ShowShortcutKeys объекта команды включает или выключает отображение заданной комбинации быстрого вызова справа от команды. По умолчанию задано значение true и комбинация клавиш отображается в том случае, если она назначена в свойстве ShortcutKeys. Свойство ShortcutKeyDisplayString позволяет задать иную строку отображения установленной комбинации, чем принято по умолчанию (например, вместо Ctrl+A по умолчанию можно задать Ctrl^A ).

    Событие Click объекта команды возникает при выборе соответствующей команды и вызывает выполнение закрепленного обработчика.

    Следует иметь ввиду, что как в коллекции Items меню верхнего уровня, так и в коллекциях DropDownItems выпадающих меню элементы отображаются в порядке их добавления в коллекцию. Свойства Items и DropDownItems являются экземплярами класса ToolStripItemCollection, который имеет метод Add(). Именно этим методом мы добавляем в коллекции объекты элементов меню. Сами элементы меню, добавляемые в коллекции, являются экземплярами класса ToolStripMenuItem.

    Однако можно использовать и метод коллекции AddRange(), который ожидает ссылку на массив объектов команд. Так, например, пусть объекты itemFile, itemOpen, itemSep, itemExit уже созданы. Тогда к объекту верхнего уровня можно присоединить сразу все подчиненные объекты команд такой строкой кода:

    itemFile.DropDownItems.AddRange(new ToolStripMenuItem[]{
                itemOpen, itemSep, itemExit});

    Перегрузки метода Add() позволяют добавить команду меню вместе с текстом, текстом и изображением, текстом + изображением + именем обработчика события Click.

    В коде упражнения есть один недостаток. Ссылки на команды меню объявляются внутри конструктора как локальные переменные. Если элементами меню нужно управлять из других мест программы, то эти переменные будут недоступны. Логичнее объявлять ссылки на элементы меню как поля класса.

    Упражнение 2. Меню с флажками

    В современных меню слева от команд находится столбец, в котором могут отображаться изображения или флажки. В этом упражнении рассмотрим управление флажками.

    Появление флажка слева от команды включается булевым свойством Checked объекта команды. В качестве альтернативы можно установить свойство CheckState в одно из значений одноименного перечисления. Например для объекта itemOpen это можно определить так

  • itemOpen.CheckState = CheckState.Indeterminate ;
  • itemOpen.CheckState = CheckState.Checked ;
  • itemOpen.CheckState = CheckState.Unchecked ;
  • Опция Indeterminate позволяет отобразить неопределенное состояние, когда на месте флажка помещается точка.

    Булево свойство CheckOnClick делает флажок самоуправляемым, когда его состояние меняется по щелчку автоматически. Но если группа флажков используется как радиокнопки для выбора одного из нескольких взаимоисключающих элементов, то управление их состоянием нужно организовывать вручную.

    В этом упражнении мы создадим меню Font, которое будет выводить список шрифтов, поддерживаемых компьютером. Обычно у современных операционных систем этот список большой и не помещается на экране, поэтому будем использовать объект класса ToolStripDropDownMenu. Этот класс отвечает за появление стрелок, позволяющих перемещаться по меню, не вмещающему команды. Здесь мы используем создание и настройку меню частично в декларативном, частично в программном режиме. Ну уж а управление логикой работы меню можно выполнить только в программном режиме.

    Декларативное создание меню

  • Откройте форму Form2 в режиме View Designer и поместите из панели Toolbox на форму экземпляр компонента MenuStrip с именем menu
  • При выделенном элементе меню в панели Properties раскройте свойство Items
  • В окне редактора меню выберите из раскрывающегося списка элемент MenuItem и щелчком на кнопке Add добавьте в коллекцию Items узел верхнего уровня
  • Присвойте добавленному элементу через таблицу свойств редактора меню Name = itemFormat, Text = Format и щелчком на кнопке OK завершите редактирование
  • При выделенном элементе ItemFormat в панели Properties раскройте свойство DropDownItems
  • В окне редактора меню выберите из раскрывающегося списка элемент MenuItem и щелчком на кнопке Add добавьте в коллекцию DropDownItems объект команды меню
  • Присвойте добавленному элементу через таблицу свойств редактора меню Name = itemFont, Text = Font и щелчком на кнопке OK завершите редактирование
  • Выполните приложение и проверьте работоспособность Form2 на данном этапе
  • Поместите из панели Toolbox экземпляр элемента Panel и настройте его свойства так:
  • Name = panel, Dock = Fill
  • Выделите объект panel и через панель Properties в режиме Events зарегистрируйте для события Paint обработчик и именем PanelOnPaint
  • На этом декларативная часть работы закончена. Далее займемся программной частью.

    Программирование меню

  • Перейдите в режим View Code формы Form2 и добавьте перед конструктором класса Form2 объявление ссылки на элемент меню
  • public partial class Form2 : Form
        {
            // Объявили поле-ссылку на объект команды меню
            ToolStripMenuItem itemSelectedFont;
        
            public Form2()
            {
                InitializeComponent();
            }
        
            private void PanelOnPaint(object sender, PaintEventArgs e)
            {
            }
        }
  • Добавьте в конструктор класса Form2 после вызова функции инициализации компонентов код динамического заполнения списка команд в коллекцию команды Font
  • public Form2()
            {
                InitializeComponent();
        
                // Заполнение коллекции объекта itemFont 
                // списком доступных шрифтов стиля "Обычный"
                Graphics gr = CreateGraphics(); // Ссылка на контекст устройства
                // Читаем все шрифты 
                FontFamily[] fonts = FontFamily.GetFamilies(gr);
        
                // Перебираем шрифты и формируем команды меню Font
                foreach (FontFamily font in fonts)
                {
                    // Выбираем шрифты одного стиля "Обычный"
                    if (font.IsStyleAvailable(FontStyle.Regular))
                    {
                        // Добавляем в коллекцию команды Font
                        ToolStripMenuItem item = 
                            (ToolStripMenuItem)itemFont.DropDownItems.Add(font.Name);
        
                        // Если встретился текущий шрифт формы, 
                        // запоминаем эту команду и отмечаем флажком
                        if (font.Name == this.Font.Name)
                        {
                            itemSelectedFont = item;
                            itemSelectedFont.Checked = true;
                        }
                    }
                }
        
                gr.Dispose(); // Освобождаем ограниченный ресурс 
            }
  • Запустите приложение и удостоверьтесь, что при раскрытии команды меню Font она уже заполнена элементами названий шрифтов
  • Теперь обеспечим нужную функциональность выбора шрифтов и отображения их названия на клиентской области панели panel.

  • Подпишите добавленный в коллекцию команды Font элемент item на событие Click. Для этого наберите вручную!!!, чтобы правильно создать обработчик, следующий код в конце условия if
  • public Form2()
            {
                ....................................................
        
                // Перебираем шрифты и формируем команды меню Font
                foreach (FontFamily font in fonts)
                {
                    // Выбираем шрифты одного стиля "Обычный"
                    if (font.IsStyleAvailable(FontStyle.Regular))
                    {
                        ...............................................
        
                        // Следующую строчку введите вручную!!!
                        item.Click += new EventHandler(item_Click);
                    }
                }
        
                gr.Dispose(); // Освобождаем ограниченный ресурс 
            }
  • Заполните обработчик item_Click() следующим кодом, который будет сбрасывать флажок с выбранного ранее шрифта и устанавливать его на новом выбранном шрифте
  • void item_Click(object sender, EventArgs e)
            {
                // Повышаем полномочия ссылки для адресации к выделенной команде 
                ToolStripMenuItem item = (ToolStripMenuItem)sender;
                itemSelectedFont.Checked = false;   // Сбрасываем предыдущий флажок
                item.Checked = true;    // Поднимаем новый флажок
                itemSelectedFont = item;// Запоминаем отмеченную команду
                panel.Invalidate();     // Перерисовываем панель с новым шрифтом
            }
  • Найдите созданный ранее обработчик события Paint панели и заполните его кодом перерисовки панели с новым шрифтом
  • private void PanelOnPaint(object sender, PaintEventArgs e)
            {
                Graphics gr = e.Graphics;   // Получаем контекст устройства 
        
                // Создаем новый шрифт по выбранному в меню с заведомо
                // большим размером, чтобы потом подогнать уменьшением
                float emSize = 100.0F;
                System.Drawing.Font font = 
                    new Font(itemSelectedFont.Text.Trim(), emSize); 
                String str = font.Name; // Строка для рисования
        
                // Подгоняем размер текстового блока под ширину формы 
                float widthText = gr.MeasureString(str, 
                    font = new Font(itemSelectedFont.Text, emSize)).Width;
                while ((panel.ClientSize.Width - widthText) < 0.0F)
                {
                    emSize -= 0.5F;
                    widthText = gr.MeasureString(str, 
                        font = new Font(itemSelectedFont.Text, emSize)).Width;
                }
        
                // Настроим точку привязки текстового блока
                System.Drawing.StringFormat fmt = new StringFormat();
                fmt.Alignment = StringAlignment.Center; // По горизонтали
                fmt.LineAlignment = StringAlignment.Center;   // По вертикали
        
                // Настроим точку привязки клиентской области панели
                PointF point = new PointF(panel.ClientSize.Width / 2,
                    panel.ClientSize.Height / 2);
        
                // Рисуем на панели название выбранного шрифта текущим цветом формы
                gr.DrawString(str, font, 
                    new SolidBrush(this.ForeColor), point, fmt);
            }
  • В классе Form2 переопределите унаследованный виртуальный метод диспетчеризации OnResize() (ввод начните с ключевого слова override ) и заполните его следующим кодом
  • protected override void OnResize(EventArgs e)
            {
                panel.Invalidate();
                base.OnResize(e);
            }

    Этот метод нужен для перерисовки панели внутри формы при изменении пользователем ее размеров.

  • Запустите Form2 и удостовертесь в ее работоспособности, результат будет таким
  • Обратите внимание, что мы обеспечили центрирование текста на панели и автоматическую его подгонку под размеры формы.

    Упражнение 3. Меню с изображениями

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

    В поставку оболочки Visual Studio 2005 Standard Edition входит архив с большим набором готовых пиктограмм, который после установки на компьютере находится в каталоге C:\Program Files\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary

    Если распаковать этот архив, то используемые в меню изображения хранятся в папке bitmaps/commands, рассортированные по разной глубине цвета. Если в Вашей системе нет этих пиктограмм, то их можно найти в прилагаемом к данной лабораторной работе каталоге Source.

    Служебные изображения в программе удобнее использовать как ресурсы, внедренные в исполнимую сборку и переносимые вместе с этой сборкой. Файл изображения добавляется к проекту командой Project/Add Existing Item, после чего оболочка физически скопирует его в каталог размещения проекта и он появится в панели Solution Explorer.

    Далее можно использовать один из способов превращения этого файла в ресурс приложения: выделить этот файл и через панель Properties установить для него опцию Build Action равной Embedded Resource. Это заставит оболочку при компиляции внедрять указанный файл как ресурс в сборку приложения. Внутри самого кода приложения мы можем адресоваться к этому файлу.

    Пусть, например, мы хотим использовать пиктограмму Open.bmp и добавили этот файл в проект. Доступ к изображению из кода программы можно осуществить разными способа, например, с помощью следующего кода

    Bitmap bmCut = new Bitmap(this.GetType(), "path.Open.bmp");

    Первый аргумент должен ссылаться на класс приложения, в нашем случае это порождающий класс текущего объекта. Второй аргумент, это имя файла изображения с путем относительно корневой папки проекта

    Присоединить изображение к элементу меню можно присвоением ссылки свойству Image соответствующей команды, например

    itemCut.Image = bmCut;

    После таких действий пиктограмма элемента будет отображаться слева от текста меню на месте отображения флажка.

    В данном упражнении разработаем простой редактор изображений, в котором можно будет загружать и сохранять файлы изображений, использовать буфер обмена и отображать рисунки несколькими способами. При этом задействуем ряд стандартных диалоговых окон. Для тренировки рассмотрим несколько способов загрузки изображений в элементы меню.

  • Откройте файл Form3.cs в режиме View Designer и задайте свойству Text формы значение " Простой редактор изображений "
  • Поместите на форму компонент PictureBox и настройте его так: Name = pictureBox, Dock = Fill
  • В дальнейшем экземпляр элемента управления PictuteBox будет служить контейнером при просмотре изображений в нашем законченном упражнении.

    Декларативная часть создания меню

    Меню для нашего упражнения мы создадим, чередуя декларативный и программный способ. Мы с вами знаем, что при декларативном способе создания объектов работу по написанию кода за нас выполняет оболочка в закулисной части класса формы, который имеет расширение Designer.cs. По большому счету разницы нет, как создавать код: с помощью графического инструмента или непосредственно вручную. Главное знать, что писать и как позиционировать объекты. При создании меню проблемы с позиционированием не возникает, главное - порядок следования компонентов. Но познакомиться с графическими возможностями оболочки по созданию меню не помешает.

  • Поместите на форму компонент MenuStrip с именем menu и настройте его декларативно в соответствии с приведенным ниже деревом свойств
  • Создавать декларативным способом новые элементы меню любого уровня удобнее, если навести курсор справа от заготовки команды и раскрыть список предлагаемых типов, затем выделенный элемент меню можно настроить через панель Properties.

    Дерево свойств для создания и настройки меню упражнения

    На схеме свойство ( Name ) взято в скобки, поскольку это не свойство элемента меню, а имя ссылки, адресующейся к объекту. Хотя у элементов управления все же есть свойство Name типа String, но его чаще всего используют как метку элемента для его узнаваемости в коллекции, но не как адресную ссылку.

    Свойство ImageTransparentColor устанавливает цвет в изображении пиктограммы, который система должна считать прозрачным. В нашем случае каждая загружаемая пиктограмма имеет рамку цвета Magenta, и если не установить этот цвет в свойстве ImageTransparentColor, то рамка будет видна на пиктограмме в меню.

    Когда снимают фильм со спецэффектами, передний план тоже снимают на фоне экрана, выкрашенного в однотонный редко встречающийся цвет. При монтаже цвет фона легко убирают и подсовывают вместо него любой нужный материал. Этот редкий экзотический цвет и можно считать ImageTransparentColor, который система считает прозрачным.

    Добавление пиктограмм к объектам команд

    Мы создали декларативным способом объекты команд всплывающего меню только для двух элементов верхнего уровня. Для третьего элемента объекты команд создадим программно чуть позже. Но сначала нужно в проект загрузить пиктограммы декларативно.

    Изображения мы планируем добавить к следующим объектам команд:

    Элемент меню Файл с пиктограммой

    itemOpen

    Open.bmp

    itemCut

    Cut.bmp

    itemCopy

    Copy.bmp

    itemPaste

    Paste.bmp

    itemDelete

    Delete.bmp
    itemSaveAs Save.bmp

    Большинство современных компьютеров поддерживают глубину цвета 24 бита, поэтому готовые рисунки будем копировать из соответствующей папки с именем 24bitcolor, которую можно найти в прилагаемом каталоге Source (или в архиве C:\Program Files\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary\VS2005ImageLibrary.zip ).

    Пиктограммы в исполнимую сборку программы, для тренировки, будем загружать несколькими способами:

  • Предварительно поместим рисунки в файл локальный ресурсов Form3.resx на этапе проектирования, код которого при компиляции будет внедрен в исполнимую сборку (файлы Cut.bmp и Copy.bmp )
  • Добавим к проекту файлы рисунков и отметим в их свойствах, что это внедряемый ресурс ( Embedded Resource ), чтобы компилятор оболочки автоматически включил их код в исполнимую сборку (файл Paste.bmp )
  • Присоединим пиктограмму немедленно к свойству Image элемента через стандартный файл глобальных ресурсов, используя графические средства оболочки (файл Delete.bmp )
  • Добавим к проекту свой ( пользовательский ) файл глобальных ресурсов и поместим в него ресурс нужной картинки (файл Open.bmp )
  • Будем загружать пиктограмму в элемент меню на этапе выполнения непосредственно из файла рисунка, который должен физически находиться вместе с исполнимой сборкой (файл Save.bmp )
  • Во всех случаях после компиляции код рисунков должен оказаться в сборке и мы во время исполнения будем присоединять его к элементам меню по мере необходимости, правда, немного по разному.

    Способ 1. Загрузка рисунков через локальный файл ресурсов

  • В панели Solution Explorer двойным щелчком на локальном XML -файле разметки Form3.resx вызовите редактор ресурсов и через первый узел меню установите режим Images
  • Выполните команду Add Resource/Add Existing File меню редактора ресурсов и, удерживая клавишу Ctrl, добавьте в ресурс файлы Cut.bmp и Copy.bmp глубиной 24bitcolor из прилагаемого к работе каталога Source
  • Выполните команду View Details меню View редактора ресурсов, чтобы отобразить представление ресурсов в виде таблицы
  • Откройте панель Solution Explorer и убедитесь, что оболочка создала каталог Resources, в который скопировала отмеченные файлы
  • Оболочка автоматически присвоила ресурсам подходящие имена, которые мы можем изменить в панели Properties, выделяя соответствующие ресурсы. Здесь же мы можем указать, где физически до компиляции будут располагаться рисунки: в XML -файле ресурсов Form3.resx или в отдельных BMP -файлах, на которые в XML -файле разметки будут помещены только ссылки.

    Пока у нас рисунки имеют состояние Linked at compile time, что означает, что при компиляции их код будет внедрен в исполнимую сборку прямо из BMP -файлов по ссылкам, помещенным в XML -файл Form3.resx. Поэтому присутствие BMP -файлов в проекте обязательно при каждой компиляции. При установке для любого рисунка опции Embedded in .resx оболочка уже на этапе проектирования сразу же физически разместит его код в файле Form3.resx. После чего мы можем удалить соответствующий BMP -файл из проекта.

    Чтобы убедиться в сказанном, выполните следующее:

  • В панели Solution Explorer вызовите контекстное меню для узла Form3.resx и выполните команду View Em, чтобы открыть XML -файл ресурсов в режиме разметки
  • Убедитесь, что в конце XML -файла прописаны только ссылки на добавленные в проект файлы рисунков
  • <data name="Copy" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>Resources\Copy.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>
      <data name="Cut" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>Resources\Cut.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>
  • В панели Solution Explorer вызовите контекстное меню для узла Form3.resx и выполните команду View Designer, чтобы открыть файл ресурсов в режиме визуального конструктора
  • Выделите ресурс Cut и через панель Properties установите для него свойство Persistence в значение Embedded in .resx
  • Вновь откройте Form3.resx в режиме View Em и убедитесь, что вместо ссылки на файл Cut.bmp оболочка включила в ресурс сам код этого рисунка
  • <data name="Copy" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>Resources\Copy.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>
      <data name="Cut" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>
            Qk02AwAAAAAAADYAAAAoAAAAEAAAABAAAAABABgAAAAAAAAAAAASCwAAEgsAAAAAAAAAAAAA/wD//wD/
            /wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/u6GUl19FY0Iy
            oo2C/wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/zYZjq2I/ilAzYkU3vb2+uZ+SZkM0
            Xj8wqJGG/wD//wD//wD//wD//wD//wD//wD/3YNVw7KquJyOhU4zwMDAxYJhqmI+YzwpLyUj/wD//wD/
            /wD//wD//wD//wD//wD/4YVXxbOqva6ojFI1vLi21HxSwrKqrpSGa0It/wD//wD//wD//wD//wD//wD/
            /wD/2ph25YZWsGZBj1U4nHJdyXVNwbm1up6Qj1U2/wD//wD//wD//wD//wD//wD//wD/yLWs645d24JW
            n19AlGBHvnRP1XtPuWtFrYd0/wD//wD//wD//wD//wD//wD//wD//wD/yLau3pl234RWxo1wzHhN0nxR
            xpF3vaec/wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/iHNmcWBWhmdVq56Z/wD//wD//wD//wD/
            /wD//wD//wD//wD//wD//wD//wD/uLSxjoB0fm5jaV1VdnJu/wD//wD//wD//wD//wD//wD//wD//wD/
            /wD//wD//wD/urCqopSLqKCZjoF3Y1tV/wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/vrGq
            oZSLyMbEzcXAa19Wra2u/wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/qZuSsqijw8PD6OTifG9m
            qKmq/wD//wD//wD//wD//wD//wD//wD//wD//wD/u7i2o5aMvLq5/wD/3dzcvLKrpqGe/wD//wD//wD/
            /wD//wD//wD//wD//wD//wD/v7izs6ie/wD//wD/y8vL4t3aoZeQ/wD//wD//wD//wD//wD//wD//wD/
            /wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/
        </value>
      </data>
  • Выделите ресурс Cut и через панель Properties верните свойство Persistence в прежнее значение Linked at compile time
  • Способ 2. Назначение рисункам свойств внедряемых ресурсов

    Добавим к проекту файл рисунка Paste.bmp и сообщим компилятору о необходимости включать его код в исполнимую сборку.

  • В панели Solution Explorer вызовите контекстное меню для папки Resources, в которой уже должны находиться файлы Cut.bmp и Copy.bmp с глубиной цвета 24bitcolor, и командой Add/Existing Item добавьте к проекту из каталога Source файл Paste.bmp с такой же глубиной цвета, предварительно установив для диалогового окна соответствующий фильтр
  • Выделите файл Paste.bmp и в панели Properties установите для него Build Action = Embedded Resource
  • При такой настройке при компиляции код файла будет копироваться в исполнимую сборку.

    Способ 3. Присоединение рисунка к свойству Image элемента меню через стандартный файл глобальных ресурсов проекта

    Присоединим к свойству Image элемента меню itemDelete файл с рисунком Delete.bmp через стандартный файл глобальных ресурсов. Этот файл может хранить в себе множественные ресурсы, которые доступны в любом месте кода программы.

  • Откройте файл Form3.cs в режиме View Designer, выделите через раскрывающийся список панели Properties элемент itemDelete, раскройте его свойство Image и для режима Project resource file загрузите в элемент кнопкой Import файл Delete.bmp из прилагаемого к работе каталога Source
  • Загруженная пиктограмма немедленно появится слева от команды Delete на меню формы

  • Откройте в каталоге Properties проекта стандартный файл глобальных ресурсов Resources проекта в режиме View Em и убедитесь, что оболочка поместила относительную ссылку на Delete.bmp в этот файл
  • <data name="Delete" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>..\Resources\Delete.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>
  • Откройте файл Form3.Designer.cs и в секции #region убедитесь, что оболочка сразу прописала код присоединения рисунка к элементу меню
  • // 
                // itemDelete
                // 
                this.itemDelete.Image = global::StripControls.Properties.Resources.Delete;

    Никаких дополнительных строк кода для загрузки рисунка указанным способом в дальнейшем не потребуется, оболочка все сделала за нас.

    Замечание. Если пытаться немедленно присоединить к элементу файл рисунка через локальный ресурс в использованном нами окне Select Resource, то оболочка выкинет все ранее присоединенные файлы из этого ресурса (В нашем случае Cut и Copy ) и оставит только один непосредственно присоединяемый к элементу управления ресурс.

    Способ 4. Размещение рисунка в пользовательском файле глобальных ресурсов

    Загрузим рисунок Open.bmp в файл пользовательский глобальных ресурсов MyResources.resx, который нужно создать и добавить к проекту в любое место.

  • В панели Solution Explorer вызовите контекстное меню для папки Resources и добавьте в нее командой Add/New Item новый файл глобальных ресурсов с именем MyResources.resx
  • В панели Solution Explorer двойным щелчком на файле Resources.resx откройте его в режиме View Designer
  • Переведите редактор в режим Images и командой Add Resource/Add Existing File добавьте к проекту файл Open.bmp глубиной цвета 24bitcolor из прилагаемого каталога Source точно также, как мы это делали ранее при загрузке файлов Cut.bmp и Copy.bmp в локальный ресурс Form3.resx
  • Оболочка физически скопирует файл Open.bmp в каталог Resources проекта и пропишет относительную ссылку на него в пользовательском XML -файле разметки MyResources.resx глобальных ресурсов

    <data name="Open" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>..\Resources\Open.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, 
          Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>

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

    Способ 5. Загрузка пиктограммы из файла рисунка во время выполнения

    Файл Save.bmp мы будем загружать в элемент itemSaveAs непосредственно во время выполнения программы. Поэтому нужно присоединить его к проекту и настроить свойства так, чтобы оболочка при компиляции копировала этот файл в место размещения исполнимой сборки.

  • В панели Solution Explorer вызовите контекстное меню для папки Resources и командой Add/Existing Item добавьте в проект файл Save.bmp ( 24bitcolor ) из прилагаемого каталога Source
  • Выделите скопированный файл Save.bmp и через панель Properties установите свойство Copy to Output Directory в значение Copy if newer, чтобы оболочка скопировала файл в каталог размещения исполнимой сборки
  • Динамическое присоединение пиктограмм к элементам меню

    Итак, на предудущих этапах работы мы загрузили все нужные файлы с пиктограммами в файлы ресурсов проекта или просто в каталог Resources проекта. Ресурс файла Delete.bmp мы сразу присоединили к свойству Image элемента меню itemDelete, поэтому дальнейших действий для этого элемента никаких не требуется. Ресурсы остальных пиктограмм пропишем в коде и они будут присоединяться к элементам меню на этапе выполнения программы.

  • Откройте файл Form3.cs в режиме View Em и добавьте в класс Form3 следующую функцию с именем LoadImages() для присоединения рисунков к элементам меню
  • // Загрузка изображений в элементы меню
            void LoadImages()
            {
                // Присоединяем 2 пиктограммы из 
                // файла локальных ресурсов Form3.resx 
                ResourceManager resources =
                    new ResourceManager(typeof(Form3));//или (this.GetType())
                itemCopy.Image = (Image)resources.GetObject("Copy");
                itemCut.Image = (Image)resources.GetObject("Cut");
        
                // Присоединяем пиктограмму из файла,
                // помеченного как Embedded Resource 
                itemPaste.Image = new Bitmap(this.GetType(),    // или (typeof(Form3)
                    "Resources.Paste.bmp");
        
                ///////////////////////////////
                // Пиктограмма для Delete внедрена декларативно
                // через стандартный файл глобальных ресурсов,
                // поэтому для нее ничего не делаем 
                ///////////////////////////////
        
                // Присоединяем пиктограмму из пользовательского 
                // файла глобальных ресурсов MyResources.resx 
                itemOpen.Image = Resources.MyResources.Open;
        
                // Присоединяем пиктограмму непосредственно из файла рисунка
                itemSaveAs.Image = new Bitmap("Resources\\Save.bmp");
            }
  • Вставьте в конструктор класса Form3 после вызова метода InitializeComponent() следующий код, выполняющий присоединение
  • public Form3()
            {
                InitializeComponent();
        
                LoadImages();// Присоединяем пиктограммы 
            }
  • Запустите Упражнение 3 на выполнение и убедитесь в наличии пиктограмм для назначенных элементов меню
  • Обратите внимание на то, что для меню View заготовлен только узел itemView, а его коллекция DropDownItems пока пуста, поскольку ранее мы договорились наполнить ее программным способом. Вот это сейчас мы и выполним.

    Создание элементов меню программным способом

    Меню View будет управлять формой представления рисунков элементом pictureBox, который мы добавили на Form3 ранее. Через общий обработчик элементов меню View мы будем передавать в pictureBox требуемые значения его свойства SizeMode из библиотечного перечисления PictureBoxSizeMode. Закрепленное за элементом меню значение перечисления PictureBoxSizeMode будем сохранять в свойстве Tag элемента и извлекать его в обработчике с последующей распаковкой ссылочного типа Object в значимый тип PictureBoxSizeMode.

    Схема свойств меню View

    Здесь свойство Name - это свойство элемента типа String, а не имя ссылки с адресом объекта. При настройке элементов меню по приведенному ниже коду не забудьте начать набор строки регистрации обработчика события Click для первого элемента DropDownItems[0] вручную - с применением клавиши Tab. Это позволит создать обработчик с правильной сигнатурой. После правильного создания общего обработчика для подписки события Click последующих элементов можно воспользоваться упрощенным синтаксисом.

  • Добавьте в класс Form3 функцию с именем CreateView() для создания и настройки элементов меню View. Код функции соответствует приведенной выше схеме и представлен ниже
  • // Функция создания элементов меню View программным способом
            void CreateView()
            {
                // Имя элемента меню на верхнем уровне для возможности 
                // адресации в коллекции Items по его имени 
                itemView.Name = "View";
        
                ToolStripMenuItem item=new ToolStripMenuItem("Normal");
                item.Tag = PictureBoxSizeMode.Normal;
                // Создайте вручную!!!!!!!
                item.Click += new EventHandler(ViewOnClick);
                itemView.DropDownItems.Add(item);// Добавили в коллекцию View 
                // Имя элемента меню View для возможности адресации
                // в коллекции DropDownItems по его имени
                item.Name = "Normal";
                // Отмечаем флажок, поскольку в начальном состоянии 
                // свойство SizeMode элемента PictureBox именно Normal
                item.Checked = true;
        
                item = new ToolStripMenuItem();
                item.Text = "Center";
                item.Tag = PictureBoxSizeMode.CenterImage;
                item.Click += ViewOnClick;
                itemView.DropDownItems.Add(item);
                item.Name = "Center";
        
                item = new ToolStripMenuItem("Stretch");
                item.Tag = PictureBoxSizeMode.StretchImage;
                item.Click += ViewOnClick;
                itemView.DropDownItems.Add(item);
                item.Name = "Stretch";
        
                item = new ToolStripMenuItem();
                item.Text = "Zoom";
                item.Tag = PictureBoxSizeMode.Zoom;
                item.Click += ViewOnClick;
                itemView.DropDownItems.Add(item);
                item.Name = "Zoom";
        
                item = new ToolStripMenuItem("AutoSize");
                item.Tag = PictureBoxSizeMode.AutoSize;
                item.Click += ViewOnClick;
                itemView.DropDownItems.Add(item);
                item.Name = "AutoSize";
            }

    Еще раз вспомним, что порядок следования кода добавления элементов в коллекцию определяет их порядок прорисовки в работающем приложении. При создании элементов меню View мы поочередно используем разные перегрузки конструктора класса ToolStripMenuItem. Мы задаем свойству Tag типа Object упакованное значение перечисления PictureBoxSizeMode, чтобы затем в обработчике распаковать его. Помечая элемент значением свойства Name мы перестраховываемся на тот случай, если вдруг далее нам придется к нему адресоваться в коллекции по синтаксису ассоциативного массива (адресация в массиве по индексу или по имени).

  • Вставьте в конец конструктора Form3() одноименного класса код вызова функции CreateView()
  • public partial class Form3 : Form
        {
            public Form3()
            {
                InitializeComponent();
        
                LoadImages();// Присоединяем пиктограммы 
        
                CreateView();// Динамически заполняем меню View
            }
  • В созданный обработчик ViewOnClick() внесите следующий код
  • void ViewOnClick(object sender, EventArgs e)
            {
                // Приводим ссылку на элемент меню View, сгенерировавший событие 
                ToolStripMenuItem item = (ToolStripMenuItem)sender;
        
                // Распаковываем объект и используем сохраненную 
                // в нем информацию для управления контейнером рисунков
                pictureBox.SizeMode = (PictureBoxSizeMode)item.Tag;
        
                ////////////////////////////////////////////////////////
                // Управление флажками выполним в обработчике события
                // DropDownOpening, которое информирует о раскрытия узла View
                ////////////////////////////////////////////////////////
                //////////// Следующий код закомментирован /////////////
                ////////////      Жалко выбрасывать!!!     /////////////
                /*//////////////////////////////////////////////////////
                // Сбрасываем флажки всех элементов 
                foreach (ToolStripMenuItem it in itemView.DropDownItems)
                    it.Checked = false;
                // Устанавливаем флажок на выделенном элементе,
                // адресуясь к коллекции по имени элемента
                ((ToolStripMenuItem)itemView.DropDownItems[item.Name]).Checked = true;
                //item.Checked = true;  // Альтернативный вариант
                //*/////////////////////////////////////////////////////
            }
  • Запустите Упражнение 3 и убедитесь, что команды узла View динамически формируются
  • Программирование узлов меню верхнего уровня

    Команды меню (опции) являются неотъемлемой частью интерфейса пользователя в современных программах. Код обработчиков элементов меню реализует основную функциональность приложения и требует пристального внимания разработчика. Заполним наши обработчики команд нужным кодом. Тщательно разберитесь в нем, поскольку он реализует логику программы и во многом может стать для вас (программистов!!!, но не кочегаров и зольщиков) поучительным.

    Ранее мы создали заготовки обработчиков для всех значимых элементов меню. Пришла пора их запрограммировать, тем самым обеспечив функциональность программе. Значимые элементы меню недаром называют командами. Именно через них пользователь, незнакомый с тонкостями программирования, управляет компьютером для выполнения своей работы.

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

    Узел File:

  • Найдите обработчик FileOnDropDown() события DropDownOpening, которое генерируется при раскрытии элемента File, и запрограммируйте его следующим образом
  • private void FileOnDropDown(object sender, EventArgs e)
            {
                // Сделать недоступным команду SaveAs, если контейнер пустой 
                itemSaveAs.Enabled = pictureBox.Image != null;
            }

    Узел Edit:

  • Найдите обработчик EditOnDropDown() события DropDownOpening узла Edit и запрограммируйте его так
  • private void EditOnDropDown(object sender, EventArgs e)
            {
                /////////////////////////////////////////////////
                // Регулируем доступность команд Copy, Cut, Delete
                /////////////////////////////////////////////////
                // Нечего редактировать - контейнер пустой 
                itemCopy.Enabled = itemCut.Enabled = itemDelete.Enabled =
                    pictureBox.Image != null;
        
                /////////////////////////////////////////////////
                // Регулируем доступность команды Paste
                /////////////////////////////////////////////////
                // Ивлекаем адрес системного буфера обмена, 
                // используя интерфейсную ссылку базового типа
                IDataObject data = Clipboard.GetDataObject();
                // Если системный буфер не содержит рисунков 
                // подходящего типа, то вставлять тоже нечего 
                itemPaste.Enabled = data.GetDataPresent(typeof(Bitmap))
                    || data.GetDataPresent(typeof(System.Drawing.Imaging.Metafile));
            }

    Узел View:

  • Найдите обработчик ViewOnDropDown() для узла View и внесите в него следующий код
  • private void ViewOnDropDown(object sender, EventArgs e)
            {
                // При раскрытии узла View перебираем коллекцию 
                // и управляем флажками с помощью состояния pictureBox
                foreach (ToolStripMenuItem item in itemView.DropDownItems)
                {
                    item.Checked = 
                        (PictureBoxSizeMode)item.Tag == pictureBox.SizeMode;
                }
            }

    Программирование команд

    Приступим к программированию самих команд всплывающих меню.

    Команда Exit:

  • Найдите обработчик ExitOnClick() для команды Exit и закодируйте его так
  • private void ExitOnClick(object sender, EventArgs e)
            {
                this.Close();
            }

    Команда Delete:

  • Найдите обработчик DeleteOnClick() для команды Delete и внесите в него следующий код
  • private void DeleteOnClick(object sender, EventArgs e)
            {
                pictureBox.Image = null;
            }

    В этом обработчике мы просто обнуляем ссылку на рисунок и изображение исчезает из контейнера pictureBox.

    Команда Copy:

  • Найдите обработчик CopyOnClick() для команды Copy и заполните его так
  • private void CopyOnClick(object sender, EventArgs e)
            {
                // Заполняем системный буфер обмена рисунком контейнера
                // Если при раскрытии узла Edit текущий 
                // рисунок есть, то команда Copy доступна
                Clipboard.SetDataObject(pictureBox.Image, true);
            }

    Здесь вызывается статический метод класса системного буфера обмена. Первым параметром метода является ссылка на объект, содержащий копируемые данные. Второй параметр устанавливает, чтобы данные сохранялись в системном буфере даже после закрытия приложения.

    Команда Cut:

  • Найдите обработчик CutOnClick() для команды Cut и заполните его так
  • private void CutOnClick(object sender, EventArgs e)
            {
                ////////////////////////////////////////////////
                // Используем уже готовые обработчики!!!
                ////////////////////////////////////////////////
                // Копируем в системный буфер обмена
                CopyOnClick(sender, e);
                // Обнуляем контейнер рисунка
                DeleteOnClick(sender, e);
            }

    Команда Paste:

  • Найдите обработчик PasteOnClick() для команды Paste и заполните его так
  • private void PasteOnClick(object sender, EventArgs e)
            {
                // Подключаемся к системному буферу обмена 
                IDataObject data = Clipboard.GetDataObject();
        
                // Распознаем тип изображения в буфере обмена и читаем данные
                Type typeMeta = typeof(System.Drawing.Imaging.Metafile);
                if (data.GetDataPresent(typeMeta))
                    pictureBox.Image = (Image)data.GetData(typeMeta);
                else
                    pictureBox.Image = (Image)data.GetData(typeof(Bitmap));
            }

    Доступность команды Paste проверяется в узле Edit и зависит от типа ожидаемых данных. Только в случае допустимости типа изображения может сработать обработчик PasteOnClick(), поэтому-то мы и рассматриваем в нем только две альтернативы.

    Для команд Open и Save As нам потребуется механизм отображения имени загруженного файла в заголовке окна наряду с самим заголовком. Для этого нужно объявить два поля типа String.

  • Добавьте в начало класса Form3 два поля, связанные с заголовком окна, и инициализируйте одно из них в конструкторе класса
  • public partial class Form3 : Form
        {
            String originalTitle;   // Поле для хранения чистого заголовка
            String name;            // Поле для хранения имени загруженного рисунка
        
            public Form3()
            {
                InitializeComponent();
        
                LoadImages();// Присоединяем пиктограммы 
        
                CreateView();// Динамически заполняем меню View
        
                originalTitle = this.Text;// Сохраняем чистый заголовок
            }
            ............................................................
        }

    Команда Open:

    В этой команде мы будем использовать стандартный диалог OpenFileDialog, который должным образом настроим. При загрузке файла в контейнер рисунка следует использовать защищенный код, перехватывающий и обрабатывающий возможные системные исключения на случай попытки загрузить файл формата, который элементом PictureBox не поддерживается.

  • Найдите обработчик OpenOnClick() для команды Open и заполните его так
  • // Обработчик команды Open 
            OpenFileDialog openDialog; // Объявили ссылку на диалог
            bool openFlag = true;// Запускается в первый раз 
            //
            private void OpenOnClick(object sender, EventArgs e)
            {
                if (openFlag)
                {
                    openFlag = false;   // Больше не создавать и не настраивать!
                    // Создаем и настраиваем диалог
                    openDialog = new OpenFileDialog();
                    openDialog.Filter = "All allowable Image Files" +                               // 1)Заголовок
                        "|*.bmp;*.ico;*.gif;*.jpeg;*.jpg;*.jfif;*.png;*.tif;*.tiff;*.wmf;*.emf|" +  // 1)|Шаблон|
                        "Windows Bitmap (*.bmp)|*.bmp|" +                               // 2) Заголовок|Шаблон| 
                        "Graphics Interchange Format (*.gif)|*.gif|" +                  // 3) Заголовок|Шаблон|
                        "JPEG File Interchange Format (*.jpg)|*.jpeg;*.jpg;*.jfif|" +   // 4) Заголовок|Шаблон|
                        "Portable Network Graphics (*.png)|*.png|" +                    // 5) Заголовок|Шаблон|
                        "Tagged Image File Format (*.tif)|*.tif;*.tiff|" +              // 6) Заголовок|Шаблон|
                        "Windows Metafile (*.wmf)|*.wmf|" +                             // 7) Заголовок|Шаблон|
                        "Enhanced Metafile (*emf)|*.emf|" +                             // 8) Заголовок|Шаблон|
                        "All Files (*.*)|*.*";                                          // 9) Заголовок|Шаблон
        
                    // Устанавливаем последний шаблон активным (по умолчанию 1!)
                    String[] str = openDialog.Filter.Split('|');
                    openDialog.FilterIndex = str.Length / 2;
                }
        
                // Защищенный код для перехвата системных исключений на 
                // случай попытки загрузки неподдерживаемого формата данных 
                if (openDialog.ShowDialog() == DialogResult.OK)
                    try
                    {
                        pictureBox.Image = Image.FromFile(openDialog.FileName);
        
                        // Выводим имя рисунка в заголовок окна
                        name = openDialog.FileName;
                        String[] str = name.Split('\\');
                        name = str[str.Length - 1].ToLower();
                        this.Text = "(" + name + ") " + originalTitle;
                    }
                    catch (Exception ext)
                    {
                        MessageBox.Show(ext.Message, this.Text, 
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
            }

    В приведенном коде ссылка на диалог объявлена как поле, чтобы он создавался только один раз и сохранял предыдущие настройки пользователя при повторных обращениях. Фильтр диалога мы тоже настраиваем только раз и для тренировки устанавливаем его на последний шаблон (первый шаблон устанавливается по умолчанию и индекс массива начинается с 1). При формировании фильтра нужно строго следить, чтобы второй член пары "Массив-Шаблон ", т.е. шаблон, не имел пробелов, иначе ошибки не будет, но шаблон не сработает!.

    Команда Save As:

    В этой команде мы будем использовать стандартный диалог SaveFileDialog, который должным образом настроим. При выгрузке файла рисунка на диск также, на случай переполнения, защиты от записи и т.д., будем использовать защищенный код, перехватывающий и обрабатывающий возможные системные исключения.

  • Найдите обработчик SaveAsOnClick() для команды Save As и заполните его так
  • // Обработчик команды Save As 
            SaveFileDialog saveDialog; // Объявили ссылку на диалог
            System.Drawing.Imaging.ImageFormat[] imageFormat;// Объявили ссылку на массив
            bool saveFlag = true;// Запускается в первый раз 
            //
            private void SaveAsOnClick(object sender, EventArgs e)
            {
                if (saveFlag)
                {
                    saveFlag = false;   // Больше не создавать и не настраивать!
                    // Создаем диалог
                    saveDialog = new SaveFileDialog();
                    // Настраиваем фильтр
                    saveDialog.Filter = "Windows Bitmap (*.bmp)|*.bmp|" + 
                        "Graphics Interchange Format (*.gif)|*.gif|" + 
                        "JPEG File Interchange Format (*.jpg)|*.jpeg;*.jpg;*.jfif|" + 
                        "Portable Network Graphics (*.png)|*.png|" + 
                        "Tagged Image File Format (*.tif)|*.tif;*.tiff";
        
                    // Инициализируем массив допустимыми форматами рисунков
                    imageFormat = new System.Drawing.Imaging.ImageFormat[] {
                        System.Drawing.Imaging.ImageFormat.Bmp,
                        System.Drawing.Imaging.ImageFormat.Gif,
                        System.Drawing.Imaging.ImageFormat.Jpeg,
                        System.Drawing.Imaging.ImageFormat.Png,
                        System.Drawing.Imaging.ImageFormat.Tiff
                    };
                }
        
                // Заполняем поле именем загруженного рисунка без расширения
                saveDialog.FileName = name.Substring(0, name.LastIndexOf('.'));
        
                // Защищенный код для перехвата системных 
                // исключений на случай сбоя записи
                if (saveDialog.ShowDialog() == DialogResult.OK)
                    try
                    {
                        // Пытаемся сохранить в формате текущего фильтра
                        pictureBox.Image.Save(saveDialog.FileName,
                            imageFormat[saveDialog.FilterIndex - 1]);
                        // Обновляем контейнер новым форматом - ???????????????
                        //pictureBox.Image = Image.FromFile(saveDialog.FileName);
       
                        // Выводим в заголовок окна имя файла с новым расширением
                        name = saveDialog.FileName;
                        String[] str = name.Split('\\');
                        name = str[str.Length - 1].ToLower();
                        this.Text = "(" + name + ") " + originalTitle;
                    }
                    catch (Exception ext)
                    {
                        MessageBox.Show(ext.Message, this.Text,
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
            }

    На этом выполнение Упражнения 3 завершено. Испытание можно проводить на рисунках, хранящихся в прилагаемом каталоге Source, или любых других.

  • Запустите приложение и испытайте его работу. Результат должен получиться примерно таким
  • Упражнение 4. Использование списка изображений для хранения пиктограмм меню

    В этом упражнении мы создадим заготовку меню с пиктограммами, которые будем присоединять к элементам меню из списка изображений. Сам список с изображениями можно заполнять из хранилища ресурсов, которое находится внутри файла исполнимой сборки нашего приложения. В свою очередь, хранилище ресурсов сборки заполняется при компиляции сборки из указанного XML -файла, который нужно сформировать заранее и поместить в него ссылки на необходимые текстовые или двоичные ресурсы. XML - или двоичный файл можно создать декларативно (вручную) или программно, написав соответствующую консольную утилиту.

    Таким образом, план действий будет таким:

  • Написать утилиту, создающую и наполняющую файл двоичными ресурсами изображений и текстовым ресурсом заголовка окна
  • Создать метод, извлекающий элементы ресурсов в список изображений
  • Создать окно с заготовкой меню и присоединить к его элементам пиктограммы из списка изображений
  • Создание файла ресурсов программным способом

    В пространстве имен System.Resources двух библиотечных сборок mscorlib.dll и System.Windows.Forms.dll находятся все необходимые типы, которые обеспечивают программную работу с ресурсами. Необходимые для работы приложения ресурсы находятся в готовой исполнимой сборке в специальной секции, называемой хранилищем ресурсов, или отдельно в двоичном файле. В хранилище ресурсов они попадают при компиляции приложения из предварительно подготовленного файла ресурсов. На рисунке приведены варианты формирования хранилища ресурсов сборки

    Разработаем утилиту, которая будет создавать файл ресурсов и наполнять его нужными объектами. Для разнообразия в файле ресурсов сохраним и строковую информацию заголовка окна с заготовкой меню.

  • Добавьте к текущему решению командой File/Add/New Project еще один проект с именем UtilityResource типа консольного приложения
  • Добавьте к проекту UtilityResource ссылки на сборки System.Windows.Forms и System.Drawing. Для этого выделите в панели Solution Explorer узел UtilityResource и выполните команду Project/Add Reference
  • В панели Solution Explorer скопируйте файлы Cut.bmp, Copy.bmp, Paste.bmp, Delete.bmp, Open.bmp, Save.bmp из узла Resources проекта StripControls в корневой узел проекта UtilityResource (или добавьте напрямую из прилагаемой папки Source, как показано ниже для иконки приложения)
  • В панели Solution Explorer вызовите контекстное меню для корневого узла UtilityResource и выполните команду Add/Existing Item. В прилагаемом каталоге Source выберите иконку, например 1.ico, и добавьте ее в проект UtilityResource
  • Выделите все добавленные к проекту UtilityResource файлы изображений и в панели Properties определите для них опции Copy to Output Directory=Copy always, Build Action=None, чтобы при исполнении проекта оболочка копировала их в каталог размещения сборки
  • Откройте на редактирование файл Program.cs проекта UtilityResource и заполните его следующим кодом создания бинарного файла ресурсов
  • using System;
        
    namespace UtilityResource
    {
        using System.Resources;
        using System.Drawing;
        
        class Program
        {
            static void Main(string[] args)
            {
                // Создаем объект для записи в двоичный файл ресурсов
                ResourceWriter rw = new ResourceWriter("ResXForm4.resources");
        
                // Добавляем в файл описание ресурсов 'Ключ-Значение' (Key-Value)
                // Ресурсы должны находиться в одном каталоге с утилитой!!!
                rw.AddResource("Cut", new Bitmap("Cut.bmp"));
                rw.AddResource("Copy", new Bitmap("Copy.bmp"));
                rw.AddResource("Paste", new Bitmap("Paste.bmp"));
                rw.AddResource("Delete", new Bitmap("Delete.bmp"));
                rw.AddResource("Open", new Bitmap("Open.bmp"));
                rw.AddResource("Save", new Bitmap("Save.bmp"));
                rw.AddResource("strTitle", "Список изображений");
                rw.AddResource("icoTitle", new Icon("1.ico"));
        
                // Сохраняем файл с добавленными ресурсами и закрываем его
                rw.Generate();
                rw.Close();
            }
        }
    }
  • В панели Solution Explorer вызовите контекстное меню для узла UtilityResource и выполните команду Set as StartUp Project, чтобы сделать проект стартовым (либо просто щелкните левой кнопкой мыши на узле UtilityResource ). Корневой узел стартового проекта выделяется в панели Solution Explorer жирным шрифтом и должен быть в решении только один
  • Выполните проект UtilityResource, чтобы создать файл ресурсов с именем ResXForm4.resources программным способом
  • В панели Solution Explorer вызовите контекстное меню для узла проекта UtilityResource и исключите его из решения командой Remove
  • В панели Solution Explorer вызовите контекстное меню для корневого узла проекта StripControls и командой Add/Existing Item добавьте созданный ранее файл ресурсов ResXForm4.resources из каталога UtilityResource/bin/Debug отсоединенного проекта (при поиске файла не забудьте настроить фильтр диалогового окна на отображение всех файлов)
  • Настройте через панель Properties добавленный к проекту StripControls файл ресурсов ResXForm4.resources так, чтобы он стал копируемым при компиляции проекта в каталог размещения сборки, т.е. установите опции Copy to Output Directory=Copy always, Build Action=None
  • На этом подготовительная часть по формированию файла ресурсов из конкретных источников с рисунками закончена и можно приступать к использованию этих ресурсов в меню приложения.

    Создание формы с заготовкой меню

    Наша главная задача в упражнении 4 - посмотреть способ загрузки пиктограмм в меню из файла ресурсов через список изображений. Для этого мы не будем создавать полнофункциональное окно, а ограничимся лишь окном с заготовкой меню, в которое загружаются пиктограммы. Первые два узла заготовки меню сделаем точно таким же, как в упражнении 3, только создадим их программным способом.

  • Добавьте к проекту StripControls форму с именем Form4 командой Project/Add Windows Form
  • Поместите из панели Toolbox на форму кнопку Button (или любой другой элемент управления) и сразу удалите ее, чтобы явно прописалось конструктором оболочки свойство Name=Form4 формы в файле Form4.Designer.cs (или сами пропишите его в этом файле)
  • Откройте файл Start.cs в режиме View Designer, выделите объект списка listBoxChoice и добавьте в его свойство Items строку: "Упражнение 4."
  • Дополните файл Start.cs блоком кода, запускающим созданную форму Form4
  • // Если форма не запущена - запускаем
                if (!IsRun)
                {
                    switch (formName)
                    {
                        case "Form1":
                            frm = new Form1();
                            break;
                        case "Form2":
                            frm = new Form2();
                            break;
                        case "Form3":
                            frm = new Form3();
                            break;
                        case "Form4":
                            frm = new Form4();
                            break;
                    }
        
                    this.AddOwnedForm(frm);     // Сделать новую frm подчиненной Start
                    // frm.Owner = this;        // Алтернативный способ назначить владельца
                    frm.ShowInTaskbar = false;  // Не отображать метку окна в панели задач
                    frm.Show();                 // Показать новую форму
                }
                else
                    frm.Focus();                // Передать фокус запущенной форме
  • Откройте файл Form4.cs в режиме View Em и добавьте в него функцию CreateMenu() программного создания меню, которая будет иметь следующий код
  • // Видимые в методах класса поля-ссылки на элементы меню
            MenuStrip menu;
            ToolStripMenuItem itemFile;
            ToolStripMenuItem itemOpen;
            ToolStripMenuItem itemSaveAs;
            ToolStripMenuItem itemExit;
        
            ToolStripMenuItem itemEdit;
            ToolStripMenuItem itemCut;
            ToolStripMenuItem itemCopy;
            ToolStripMenuItem itemPaste;
            ToolStripMenuItem itemDelete;
        
            void CreateMenu()
            {
                // Создание меню
                menu = new MenuStrip();
                menu.Parent = this;
        
                // Формирование верхней линейки меню
                itemFile = new ToolStripMenuItem("File");
                menu.Items.Add(itemFile);
                itemEdit = new ToolStripMenuItem("Edit");
                menu.Items.Add(itemEdit);
        
                // Сборка узла File
                itemFile.DropDownItems.Add(itemOpen = new ToolStripMenuItem("Open"));
                itemFile.DropDownItems.Add(itemSaveAs = new ToolStripMenuItem("Save As..."));
                itemFile.DropDownItems.Add(new ToolStripSeparator());
                itemFile.DropDownItems.Add(itemExit = new ToolStripMenuItem("Exit"));
        
                // Сборка узла Edit
                itemEdit.DropDownItems.Add(itemCut = new ToolStripMenuItem("Cut"));
                itemEdit.DropDownItems.Add(itemCopy = new ToolStripMenuItem("Copy"));
                itemEdit.DropDownItems.Add(itemPaste = new ToolStripMenuItem("Paste"));
                itemEdit.DropDownItems.Add(itemDelete = new ToolStripMenuItem("Delete"));
            }
  • Добавьте в файл Form4.cs функцию LoadImage() загрузки пиктограмм меню из файла ресурсов через список изображений, которая будет представлена следующим кодом
  • // Создаем список для хранения изображений 
            ImageList imageList = new ImageList();
        
            void LoadImage()
            {
                // Присоединяем ранее сформированные ресурсы из файла ресурсов 
                ResourceReader rr = new ResourceReader("ResXForm4.resources");
                IDictionaryEnumerator en = rr.GetEnumerator();
                while (en.MoveNext())
                {
                    switch (en.Key.ToString())
                    {
                        default:
                            imageList.Images.Add(en.Key.ToString(), (Image)en.Value);
                            break;
                        case "icoTitle":
                            this.Icon = (Icon)en.Value;     // Иконка окна
                            break;
                        case "strTitle":
                            this.Text = (String)en.Value;   // Заголовок окна
                            break;
                    }
                }
                rr.Close(); // Закрыли файл ресурсов
        
                // Определяем цвет, имитирующий прозрачность окраин пиктограмм 
                imageList.TransparentColor = Color.Magenta;
        
                // Закрепляем пиктограммы из списка за командами меню с помощью ключей
                itemOpen.Image = imageList.Images["Open"];
                itemSaveAs.Image = imageList.Images["Save"];
                itemCut.Image = imageList.Images["Cut"];
                itemCopy.Image = imageList.Images["Copy"];
                itemPaste.Image = imageList.Images["Paste"];
                itemDelete.Image = imageList.Images["Delete"];
            }
  • Вставьте вызовы функций создания меню и загрузки изображений в конструктор класса Form4 и добавьте инструкции подключения библиотечных пространств имен
  • using System.Resources;
  • using System.Collections;
  • Общий код файла Form4.cs будет таким

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
        
    namespace StripControls
    {
        using System.Resources;
        using System.Collections;
        
        public partial class Form4 : Form
        {
            public Form4()
            {
                InitializeComponent();
        
                CreateMenu();// Создаем меню
        
                // Загружаем в меню пиктограммы опций
                LoadImage();
            }
        
            // Видимые в методах поля-ссылки на элементы меню
            MenuStrip menu;
            ToolStripMenuItem itemFile;
            ToolStripMenuItem itemOpen;
            ToolStripMenuItem itemSaveAs;
            ToolStripMenuItem itemExit;
        
            ToolStripMenuItem itemEdit;
            ToolStripMenuItem itemCut;
            ToolStripMenuItem itemCopy;
            ToolStripMenuItem itemPaste;
            ToolStripMenuItem itemDelete;
        
            void CreateMenu()
            {
                // Создание меню
                menu = new MenuStrip();
                menu.Parent = this;
        
                // Формирование верхней линейки меню
                itemFile = new ToolStripMenuItem("File");
                menu.Items.Add(itemFile);
                itemEdit = new ToolStripMenuItem("Edit");
                menu.Items.Add(itemEdit);
        
                // Сборка узла File
                itemFile.DropDownItems.Add(itemOpen = new ToolStripMenuItem("Open"));
                itemFile.DropDownItems.Add(itemSaveAs = new ToolStripMenuItem("Save As..."));
                itemFile.DropDownItems.Add(new ToolStripSeparator());
                itemFile.DropDownItems.Add(itemExit = new ToolStripMenuItem("Exit"));
        
                // Сборка узла Edit
                itemEdit.DropDownItems.Add(itemCut = new ToolStripMenuItem("Cut"));
                itemEdit.DropDownItems.Add(itemCopy = new ToolStripMenuItem("Copy"));
                itemEdit.DropDownItems.Add(itemPaste = new ToolStripMenuItem("Paste"));
                itemEdit.DropDownItems.Add(itemDelete = new ToolStripMenuItem("Delete"));
            }
        
            // Создаем список для хранения изображений 
            ImageList imageList = new ImageList();
        
            void LoadImage()
            {
                // Присоединяем ранее сформированные ресурсы из файла ресурсов 
                ResourceReader rr = new ResourceReader("ResXForm4.resources");
                IDictionaryEnumerator en = rr.GetEnumerator();
                while (en.MoveNext())
                {
                    switch (en.Key.ToString())
                    {
                        default:
                            imageList.Images.Add(en.Key.ToString(), (Image)en.Value);
                            break;
                        case "icoTitle":
                            this.Icon = (Icon)en.Value;     // Иконка окна
                            break;
                        case "strTitle":
                            this.Text = (String)en.Value;   // Заголовок окна
                            break;
                    }
                }
                rr.Close(); // Закрыли файл ресурсов
        
                // Определяем цвет фона, воспринимаемый прозрачным
                imageList.TransparentColor = Color.Magenta;
        
                // Закрепляем пиктограммы из списка за командами меню с помощью ключей
                itemOpen.Image = imageList.Images["Open"];
                itemSaveAs.Image = imageList.Images["Save"];
                itemCut.Image = imageList.Images["Cut"];
                itemCopy.Image = imageList.Images["Copy"];
                itemPaste.Image = imageList.Images["Paste"];
                itemDelete.Image = imageList.Images["Delete"];
            }
        }
    }
  • Запустите на выполнение Упражнение 4 и убедитесь, что пиктограммы нормально загружаются из файла ресурсов через список изображений, а также успешно подгружаются и другие необходимые приложению ресурсы (пиктограмма и строка заголовка окна)
  • Упражнение 5. Контекстное меню

    Контекстное меню вызывается по щелчку правой кнопки мыши на соответствующем элементе управления и зависит от конкретного состояния этого элемента. Контекстное меню представлено классом System.Windows.Forms.ContextMenuStrip. Любой визуальный элемент управления, производный от класса Control, наследует общедоступное виртуальное свойство ContextMenuStrip, к которому можно привязать объект контекстного меню.

    Элементами контекстного меню ContextMenuStrip, как и в случае с основным меню, порожденным классом MenuStrip, являются объекты класса ToolStripMenuItem. Эти объекты добавляются в коллекцию Items, являющуюся свойством класса контекстного меню ContextMenuStrip. Каждый из объектов-элементов контекстного меню имеет свойство-коллекцию DropDownItems, в которую помещают элементы вложенного меню.

    Мы можем заготовить несколько различных объектов контекстного меню и привязать каждый из них к определенному элементу управления. Но эти же несколько объектов контекстного меню можно поочередно привязывать к одному и тому же элементу управления в динамическом режиме в зависимости от текущего состояния контекста.

    Рассмотрим Упражнение 5, в котором будем использовать объекты контекстного меню. Мы создадим несколько контекстных меню, часть динамическим способом, часть декларативным способом. Объекты контекстных меню будем присоединять к различным элементам управления через свойство этих элементов ContextMenuStrip.

    Создание формы для Упражнения 5

  • В панели Solution Explorer выделите корневой узел проекта StripControls и командой меню оболочки Project/Add Windows Form добавьте к проекту файл Form5.cs
  • Откройте файл Start.cs в режиме View Designer, выделите объект listBoxChoice и добавьте через панель Properties в свойство Items ссылку на Упражнение 5
  • Откройте файл Start.cs в режиме View Em, найдите обработчик listBoxChoice_SelectedIndexChanged() и добавьте в него код запуска формы Form5
  • Form frm;   // Объявили поле
            private void listBoxChoice_SelectedIndexChanged(object sender, EventArgs e)
            {
                // Привязали номер упражнения к индексу списка
                ListBox list = (ListBox)sender;
                string formName = "Form" + (list.SelectedIndex + 1);
        
                ///////////////////////////////////////////////////
                // Следующий код взят из лабораторной работы №47
                ///////////////////////////////////////////////////
                // Читаем коллекцию запущенных форм
                FormCollection fc = Application.OpenForms;
                bool IsRun = false;
                // Перебираем коллекцию
                foreach (Form form in fc)
                {
                    // Имя очередной запущенной формы сравниваем с закрепленным за кнопкой
                    if (form.Name == formName)
                    {
                        IsRun = true;   // Нашли, что форма запущена, поднимаем флаг
                        frm = form;     // Сохраняем ссылку на запущенную форму для фокуса
                        break;          // Выходим из цикла
                    }
                }
        
                // Если форма не запущена - запускаем
                if (!IsRun)
                {
                    switch (formName)
                    {
                        case "Form1":
                            frm = new Form1();
                            break;
                        case "Form2":
                            frm = new Form2();
                            break;
                        case "Form3":
                            frm = new Form3();
                            break;
                        case "Form4":
                            frm = new Form4();
                            break;
                        case "Form5":
                            frm = new Form5();
                            break;
                    }
        
                    this.AddOwnedForm(frm);     // Сделать новую frm подчиненной Start
                    // frm.Owner = this;        // Алтернативный способ назначить владельца
                    frm.ShowInTaskbar = false;  // Не отображать метку окна в панели задач
                    frm.Show();                 // Показать новую форму
                }
                else
                    frm.Focus();                // Передать фокус запущенной форме        
            }
  • Поместите на форму Form5 компонент SplitContainer, присвойте его экземпляру имя splitContainer и установите свойства: Orientation= Horizontal, BorderStyle=Fixed3D, Dock=Fill
  • Создание контекстного меню динамическим способом

  • Откройте файл Form5.cs в режиме View Code и откорректируйте код, чтобы он стал таким
  • using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Reflection;
        
    namespace StripControls
    {
        public partial class Form5 : Form
        {
            // Поле для сохранения выбранного элемента меню
            ToolStripMenuItem itemChecked = new ToolStripMenuItem();
        
            // Конструктор
            public Form5()
            {
                InitializeComponent();
        
                splitContainer.Panel1.BackColor = Color.White;// Начальный цвет верхней панели
        
                // Разделение цветов на алфавитные группы, представленные подменю
                //             A, B, C, D, E, F, G, H, I, J, K, L, M,           
                int[] iMenu ={ 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4,  
                    5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6 };
                //  N, O, P, Q, R, S, T, U, V, W, X, Y, Z
        
                // Создание контекстного меню и присоединение к объекту формы
                System.Windows.Forms.ContextMenuStrip menu =
                    new System.Windows.Forms.ContextMenuStrip();
                // Будет вызываться для верхней панели
                splitContainer.Panel1.ContextMenuStrip = menu;
        
                // Формируем пункты подменю в контекстном меню
                for (int i = 0; i <= 6; i++)
                {
                    // Индекс первого вхождения образца соответствует коду ASCII для буквы 
                    char first = Convert.ToChar(Array.IndexOf(iMenu, i) + 'A');   // Позиция в массиве
                    // Индекс последнего вхождения образца соответствует ASCII латинской буквы 
                    char last = Convert.ToChar(Array.LastIndexOf(iMenu, i) + 'A');// Позиция в массиве
        
                    // Создали, настроили и добавили в коллекцию элемент меню
                    ToolStripMenuItem item = new ToolStripMenuItem();
                    item.Text = String.Format("Цвета от {0} до {1}", first, last);
                    menu.Items.Add(item);
                    // Включили в подменю столбец с флажками в 
                    // дополнение к столбцу с изображениями
                    ((ToolStripDropDownMenu)item.DropDown).ShowCheckMargin = true;
                }
        
                // Применяем рефлексию к структуре, представляющей цвета
                PropertyInfo[] api = typeof(Color).GetProperties();
                // Перебор цветов
                foreach (PropertyInfo pi in api)
                {
                    if (pi.CanRead  pi.PropertyType == typeof(Color)
                         pi.Name!="Transparent")
                    {
                        // Извлекаем очередной цвет
                        Color color = (Color)pi.GetValue(null, null);
        
                        // Создаем очередной элемент подменю
                        ToolStripMenuItem item = new ToolStripMenuItem();
                        // По букве цвета определяем подменю, в котором разместим цвет
                        int i = iMenu[color.Name[0] - 'A'];// Любой букве - своя позиция
                        ((ToolStripMenuItem)menu.Items[i]).DropDownItems.Add(item);
        
                        // Продолжаем настраивать команду
                        item.Text = InsertSpaceOut(color.Name);// Пробелы перед заглавными буквами цвета
                        item.Name = color.Name;// Сохраняем название цвета для обработчика ColorOnClick
                        item.Image = CreateBitmap(color);// Тонкой ссылке присваиваем толстый объект
                        item.Click += new EventHandler(ColorOnClick);// Регистрация обработчика
                        if (color.Equals(this.BackColor))// Отметить флажком текущий цвет и сохранить ссылку
                            (itemChecked = item).Checked = true;
                    }
                }
            }
        
            void ColorOnClick(object sender, EventArgs e)
            {
                // Для выбранного устанавливаем новый флажок
                // и меняем цвет клиентской области формы
                ToolStripMenuItem item = (ToolStripMenuItem)sender;
                itemChecked.Checked = false;// Сбрасываем старое
                (itemChecked = item).Checked = true;// Устанавливаем флажок и запоминаем элемент
                splitContainer.Panel1.BackColor = Color.FromName(item.Name);// Красим верхнюю панель
            }
        
            Bitmap CreateBitmap(Color color)
            {
                // Генерируем изображение в виде цветного прямоугольника
                Bitmap bm = new Bitmap(16, 16);
                Graphics gr = Graphics.FromImage(bm);
                gr.FillRectangle(new SolidBrush(color), 0, 0, 16, 16);
                gr.Dispose();   // Освобождаем контекст устройства
                return bm;
            }
        
            string InsertSpaceOut(string str)
            {
                // Вставляем пробелы перед заглавными буквами названия цвета
                for (int i = str.Length - 1; i > 1; i--)
                    if (Char.IsUpper(str[i]))
                        str = str.Insert(i, " ");
                return str;
            }
        }
    }
  • Запустите приложение и для верхней панели сплиттера испытайте работу контекстного меню
  • Должно получиться нечто, похожее показанному на рисунке с изменением выбранного цвета фона на верхней панели сплиттера

    Создание линейки контекстных меню декларативным способом

    Теперь посмотрим, как присоединяются поочередно контекстные меню к одному и тому же элементу управления в зависимости от некоторых условий.

  • Поместите на нижнюю панель объекта splitContainer еще один экземпляр компонента SplitContainer и оставьте его имя по умолчанию splitContainer1
  • Установите настройки объекта splitContainer1: Dock=Fill, BorderStyle=Fixed3D
  • Поместите на левую нижнюю панель splitContainer1.Panel1 три экземпляра компонента радиокнопки RadioButton с именами radioButton1, radioButton2, radioButton3 и измените их свойство Text на ContextMenu1, ContextMenu2, ContextMenu1
  • Установите для правой нижней панели splitContainer1.Panel2 свойство BackColor=Lime
  • После проведения указанных действий интерфейс формы Form5 должен стать таким

  • Создайте три экземпляра компонента ContextMenuStrip с именами по умолчанию: contextMenuStrip1, contextMenuStrip2, contextMenuStrip3
  • Последовательно выделите каждый объект из только что добавленных контекстных меню и создайте в нем декларативно заготовки элементов MenuItem
  • В результате должны получиться три объекта-заготовки контекстных меню со следующим интерфейсом

    contextMenuStrip1 contextMenuStrip2 contextMenuStrip3
  • Выделите на форме Form5 все три радиокнопки одновременно и через панель Properties в режиме Events для события CheckedChanged создайте обработчик с именем radioButton_CheckedChanged(), который заполните следующим кодом
  • private void radioButton_CheckedChanged(object sender, EventArgs e)
            {
                    if (radioButton1.Checked)
                        splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip1;
                    else if (radioButton2.Checked)
                        splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip2;
                    else
                        splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip3;
            }

    При смене активной радиокнопки в обработчике происходит присоединение к правой нижней панели соответствующего объекта контекстного меню. Вызов контекстного меню выполняется щелчком правой кнопки мыши на правой нижней панели, представленной элементом splitContainer1.Panel2. Для обеспечения подобного механизма работы при первом вызове контекстного меню необходимо задействовать для этого элемента событие BindingContextChanged.

  • Выделите элемент splitContainer1.Panel2 и через панель Properties в режиме Events создайте для события BindingContextChanged обработчик splitContainer1_Panel2_BindingContextChanged(), который заполните так
  • private void splitContainer1_Panel2_BindingContextChanged(object sender, EventArgs e)
            {
                if (radioButton1.Checked)
                    splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip1;
                else if (radioButton2.Checked)
                    splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip2;
                else
                    splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip3;
            }
  • Запустите приложение и убедитесь в работоспособности всех 4-х контекстных меню, первое из которых создано динамически, а три остальных - декларативно
  • Страницы:

    Файлы к лабораторной работе Вы можете скачать здесь.

    В библиотеке .NET Framework 2.0 появились три новых класса для организации пользовательского интерфейса управления приложением ( Strip - полоса):

  • System.Windows.Forms.MenuStrip - главное меню
  • System.Windows.Forms.ToolStrip - панель инструментов
  • System.Windows.Forms.StatusStrip - панель состояния
  • Наряду с этими новыми элементами существуют и поддерживаются прежние аналогичные классы, существующие в версиях .NET Framework 1.x:

  • System.Windows.Forms.MainMenu
  • System.Windows.Forms.ToolBar
  • System.Windows.Forms.StatusBar
  • По умолчанию эти старые классы не подключены к панели Toolbox, поэтому не видны в ней, хотя и находятся в GAC (Global Assembly Cache). Но их легко подключить в любой свиток панели, вызвав для этого свитка контекстное меню и выполнив команду Choose Items

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

    В данной работе мы будем рассматривать вопросы, касающиеся способов построения различных видов меню. Для демонстрации упражнений с элементами меню создадим заготовку приложения со стартовой формой.

    Заготовка приложения со стартовой формой

    Мы еще не знаем, сколько упражнений получится в этой работе, поэтому применим список ListBox, в который можно добавлять сколько угодно опций вызова упражнений. При этом будем использовать рассмотренный ранее прием предотвращения запуска уже запущенной формы.

  • Выполните команду File/New/Project и заполните мастер создания приложения в соответствии с рисунком
  • Через панель Solution Explorer переименуйте файл Form1.cs в Start.cs, а заголовку окна формы присвойте значение Text =" Lab48 - Имя студента "
  • Поместите на форму библиотечный компонент ListBox и настройте его в соответствии с таблицей свойств
  • Таблица свойств экземпляра ListBox
    Свойство Значение
    Name listBoxChoice
    Items Упражнение 1.
      Упражнение 2.
      Упражнение 3.
    Dock Fill
  • Через панель Properties создайте обработчик для события SelectedIndexChanged объекта listBoxChoice, который заполните так
  • Form frm;   // Объявили поле
            private void listBoxChoice_SelectedIndexChanged(object sender, EventArgs e)
            {
                // Привязали номер упражнения к индексу списка
                ListBox list = (ListBox)sender;
                string formName = "Form" + (list.SelectedIndex + 1);
        
                ///////////////////////////////////////////////////
                // Следующий код взят из лабораторной работы №47
                ///////////////////////////////////////////////////
                // Читаем коллекцию запущенных форм
                FormCollection fc = Application.OpenForms;
                bool IsRun = false;
                // Перебираем коллекцию
                foreach (Form form in fc)
                {
                    // Имя очередной запущенной формы сравниваем с закрепленным за кнопкой
                    if (form.Name == formName)
                    {
                        IsRun = true;   // Нашли, что форма запущена, поднимаем флаг
                        frm = form;     // Сохраняем ссылку на запущенную форму для фокуса
                        break;          // Выходим из цикла
                    }
                }
        
                // Если форма не запущена - запускаем
                if (!IsRun)
                {
                    switch (formName)
                    {
                        case "Form1":
                            frm = new Form1();
                            break;
                        case "Form2":
                            frm = new Form2();
                            break;
                        case "Form3":
                            frm = new Form3();
                            break;
                    }
        
                    this.AddOwnedForm(frm);     // Сделать новую frm подчиненной Start
                    // frm.Owner = this;        // Алтернативный способ назначить владельца
                    frm.ShowInTaskbar = false;  // Не отображать метку окна в панели задач
                    frm.Show();                 // Показать новую форму
                }
                else
                    frm.Focus();                // Передать фокус запущенной форме        
            }

    Недостаток данного кода в том, что мы привязали вызов формы упражнения к индексу списка.

  • Командой меню оболочки Project/Add Windows Form добавьте в проект еще три формы с именами Form1, Form2, Form3
  • Запустите приложение и убедитесь в работоспособности механизма вызова отдельных упражнений
  • Упражнение 1. Простое меню

    Рассмотрим простую программу, имитирующую работу меню.

  • Откройте файл Form1.cs в режиме View Code и добавьте к конструктору класса код создания простого меню
  • public Form1()
            {
                InitializeComponent();
        
                this.Text = "Простое меню";
        
                // Главное меню
                MenuStrip menu = new MenuStrip();
                this.Controls.Add(menu);
        
                // Раздел File
                ToolStripMenuItem itemFile = new ToolStripMenuItem();
                itemFile.Text = "File";
                menu.Items.Add(itemFile);// Элемент верхнего уровня
        
                // Раздел Help
                ToolStripMenuItem itemHelp = new ToolStripMenuItem();
                itemHelp.Text = "Help";
                menu.Items.Add(itemHelp);
        
                // Заполнение раздела File
                // Команда Open...
                ToolStripMenuItem itemOpen = new ToolStripMenuItem();
                itemOpen.Text = "Open...";
                itemOpen.ShortcutKeys = Keys.Control | Keys.O; // Ctrl+O
                itemOpen.ShowShortcutKeys = true;// Отображать в команде
                // Регистрировать вручную!!!!!!!!!!!!!!
                itemOpen.Click += new EventHandler(itemOpen_Click);
                itemFile.DropDownItems.Add(itemOpen);
                // itemOpen.ShowShortcutKeys = false;// Скрыть комбинацию 
        
                // Разделитель
                ToolStripSeparator itemSep = new ToolStripSeparator();
                itemFile.DropDownItems.Add(itemSep);
        
                // Команда Exit
                ToolStripMenuItem itemExit = new ToolStripMenuItem();
                itemExit.Text = "Exit";
                // Регистрировать вручную!!!!!!!!!!!!!!
                itemExit.Click += new EventHandler(itemExit_Click);
                itemFile.DropDownItems.Add(itemExit);
        
                // Заполнение раздела Help
                // Команда About...
                ToolStripMenuItem itemAbout = new ToolStripMenuItem();
                itemAbout.Text = "About...";
                itemAbout.ShortcutKeys = Keys.Control | Keys.A; // Ctrl+A
                itemAbout.ShortcutKeyDisplayString = "Ctrl^A";// Свое отображение
                itemAbout.ShowShortcutKeys = true;// Отображать в команде
                // Регистрировать вручную!!!!!!!!!!!!!!
                itemAbout.Click += new EventHandler(itemAbout_Click);
                itemHelp.DropDownItems.Add(itemAbout);
            }
  • Перепишите выделенные строки кода регистрации обработчиков события Click вручную, чтобы правильно создать заготовки обработчиков, и заполните обработчики следующим кодом
  • void itemAbout_Click(object sender, EventArgs e)
            {
                MessageBox.Show("Заготовка опции \"About...\"", this.Text);
            }
        
            void itemExit_Click(object sender, EventArgs e)
            {
                this.Close();
            }
        
            void itemOpen_Click(object sender, EventArgs e)
            {
                MessageBox.Show("Заготовка опции \"Open\"", this.Text);
            }
        }

    В программе мы создаем объект меню, как экземпляр класса MenuStrip, и привязали его к объекту формы . У класса MenuStrip есть свойство Items, которое является коллекцией элементов меню верхнего уровня. Мы создаем два таких объекта itemFile и itemHelp типа ToolStripMenuItem и добавляем их в свойство Items. У каждого объекта типа ToolStripMenuItem есть свойство DropDownItems, готовое хранить коллекцию команд и разделителей, в том числе вложенных для построения меню сложной иерархии. В эти свойства элементов верхнего уровня мы добавляем объекты команд.

    Схематично такая процедура построения меню примера изображена на рисунке

    Символ амперсанда в значении свойства Text наименования команды ассоциируется со знаком подчеркивания следующей за ним буквы и позволяет после нажатия клавиши Alt (не удерживая) вызывать команды простым нажатием соответствующих клавиш. Знак подчеркивания в работающей программе появляется только после щелчка на клавише Alt.

    Свойство ShortcutKeys объекта команды ожидает побитовую комбинацию элементов перечисления System.Windows.Forms. Keys, задающую клавиши быстрого вызова этой команды. Логическое свойство ShowShortcutKeys объекта команды включает или выключает отображение заданной комбинации быстрого вызова справа от команды. По умолчанию задано значение true и комбинация клавиш отображается в том случае, если она назначена в свойстве ShortcutKeys. Свойство ShortcutKeyDisplayString позволяет задать иную строку отображения установленной комбинации, чем принято по умолчанию (например, вместо Ctrl+A по умолчанию можно задать Ctrl^A ).

    Событие Click объекта команды возникает при выборе соответствующей команды и вызывает выполнение закрепленного обработчика.

    Следует иметь ввиду, что как в коллекции Items меню верхнего уровня, так и в коллекциях DropDownItems выпадающих меню элементы отображаются в порядке их добавления в коллекцию. Свойства Items и DropDownItems являются экземплярами класса ToolStripItemCollection, который имеет метод Add(). Именно этим методом мы добавляем в коллекции объекты элементов меню. Сами элементы меню, добавляемые в коллекции, являются экземплярами класса ToolStripMenuItem.

    Однако можно использовать и метод коллекции AddRange(), который ожидает ссылку на массив объектов команд. Так, например, пусть объекты itemFile, itemOpen, itemSep, itemExit уже созданы. Тогда к объекту верхнего уровня можно присоединить сразу все подчиненные объекты команд такой строкой кода:

    itemFile.DropDownItems.AddRange(new ToolStripMenuItem[]{
                itemOpen, itemSep, itemExit});

    Перегрузки метода Add() позволяют добавить команду меню вместе с текстом, текстом и изображением, текстом + изображением + именем обработчика события Click.

    В коде упражнения есть один недостаток. Ссылки на команды меню объявляются внутри конструктора как локальные переменные. Если элементами меню нужно управлять из других мест программы, то эти переменные будут недоступны. Логичнее объявлять ссылки на элементы меню как поля класса.

    Упражнение 2. Меню с флажками

    В современных меню слева от команд находится столбец, в котором могут отображаться изображения или флажки. В этом упражнении рассмотрим управление флажками.

    Появление флажка слева от команды включается булевым свойством Checked объекта команды. В качестве альтернативы можно установить свойство CheckState в одно из значений одноименного перечисления. Например для объекта itemOpen это можно определить так

  • itemOpen.CheckState = CheckState.Indeterminate ;
  • itemOpen.CheckState = CheckState.Checked ;
  • itemOpen.CheckState = CheckState.Unchecked ;
  • Опция Indeterminate позволяет отобразить неопределенное состояние, когда на месте флажка помещается точка.

    Булево свойство CheckOnClick делает флажок самоуправляемым, когда его состояние меняется по щелчку автоматически. Но если группа флажков используется как радиокнопки для выбора одного из нескольких взаимоисключающих элементов, то управление их состоянием нужно организовывать вручную.

    В этом упражнении мы создадим меню Font, которое будет выводить список шрифтов, поддерживаемых компьютером. Обычно у современных операционных систем этот список большой и не помещается на экране, поэтому будем использовать объект класса ToolStripDropDownMenu. Этот класс отвечает за появление стрелок, позволяющих перемещаться по меню, не вмещающему команды. Здесь мы используем создание и настройку меню частично в декларативном, частично в программном режиме. Ну уж а управление логикой работы меню можно выполнить только в программном режиме.

    Декларативное создание меню

  • Откройте форму Form2 в режиме View Designer и поместите из панели Toolbox на форму экземпляр компонента MenuStrip с именем menu
  • При выделенном элементе меню в панели Properties раскройте свойство Items
  • В окне редактора меню выберите из раскрывающегося списка элемент MenuItem и щелчком на кнопке Add добавьте в коллекцию Items узел верхнего уровня
  • Присвойте добавленному элементу через таблицу свойств редактора меню Name = itemFormat, Text = Format и щелчком на кнопке OK завершите редактирование
  • При выделенном элементе ItemFormat в панели Properties раскройте свойство DropDownItems
  • В окне редактора меню выберите из раскрывающегося списка элемент MenuItem и щелчком на кнопке Add добавьте в коллекцию DropDownItems объект команды меню
  • Присвойте добавленному элементу через таблицу свойств редактора меню Name = itemFont, Text = Font и щелчком на кнопке OK завершите редактирование
  • Выполните приложение и проверьте работоспособность Form2 на данном этапе
  • Поместите из панели Toolbox экземпляр элемента Panel и настройте его свойства так:
  • Name = panel, Dock = Fill
  • Выделите объект panel и через панель Properties в режиме Events зарегистрируйте для события Paint обработчик и именем PanelOnPaint
  • На этом декларативная часть работы закончена. Далее займемся программной частью.

    Программирование меню

  • Перейдите в режим View Code формы Form2 и добавьте перед конструктором класса Form2 объявление ссылки на элемент меню
  • public partial class Form2 : Form
        {
            // Объявили поле-ссылку на объект команды меню
            ToolStripMenuItem itemSelectedFont;
        
            public Form2()
            {
                InitializeComponent();
            }
        
            private void PanelOnPaint(object sender, PaintEventArgs e)
            {
            }
        }
  • Добавьте в конструктор класса Form2 после вызова функции инициализации компонентов код динамического заполнения списка команд в коллекцию команды Font
  • public Form2()
            {
                InitializeComponent();
        
                // Заполнение коллекции объекта itemFont 
                // списком доступных шрифтов стиля "Обычный"
                Graphics gr = CreateGraphics(); // Ссылка на контекст устройства
                // Читаем все шрифты 
                FontFamily[] fonts = FontFamily.GetFamilies(gr);
        
                // Перебираем шрифты и формируем команды меню Font
                foreach (FontFamily font in fonts)
                {
                    // Выбираем шрифты одного стиля "Обычный"
                    if (font.IsStyleAvailable(FontStyle.Regular))
                    {
                        // Добавляем в коллекцию команды Font
                        ToolStripMenuItem item = 
                            (ToolStripMenuItem)itemFont.DropDownItems.Add(font.Name);
        
                        // Если встретился текущий шрифт формы, 
                        // запоминаем эту команду и отмечаем флажком
                        if (font.Name == this.Font.Name)
                        {
                            itemSelectedFont = item;
                            itemSelectedFont.Checked = true;
                        }
                    }
                }
        
                gr.Dispose(); // Освобождаем ограниченный ресурс 
            }
  • Запустите приложение и удостоверьтесь, что при раскрытии команды меню Font она уже заполнена элементами названий шрифтов
  • Теперь обеспечим нужную функциональность выбора шрифтов и отображения их названия на клиентской области панели panel.

  • Подпишите добавленный в коллекцию команды Font элемент item на событие Click. Для этого наберите вручную!!!, чтобы правильно создать обработчик, следующий код в конце условия if
  • public Form2()
            {
                ....................................................
        
                // Перебираем шрифты и формируем команды меню Font
                foreach (FontFamily font in fonts)
                {
                    // Выбираем шрифты одного стиля "Обычный"
                    if (font.IsStyleAvailable(FontStyle.Regular))
                    {
                        ...............................................
        
                        // Следующую строчку введите вручную!!!
                        item.Click += new EventHandler(item_Click);
                    }
                }
        
                gr.Dispose(); // Освобождаем ограниченный ресурс 
            }
  • Заполните обработчик item_Click() следующим кодом, который будет сбрасывать флажок с выбранного ранее шрифта и устанавливать его на новом выбранном шрифте
  • void item_Click(object sender, EventArgs e)
            {
                // Повышаем полномочия ссылки для адресации к выделенной команде 
                ToolStripMenuItem item = (ToolStripMenuItem)sender;
                itemSelectedFont.Checked = false;   // Сбрасываем предыдущий флажок
                item.Checked = true;    // Поднимаем новый флажок
                itemSelectedFont = item;// Запоминаем отмеченную команду
                panel.Invalidate();     // Перерисовываем панель с новым шрифтом
            }
  • Найдите созданный ранее обработчик события Paint панели и заполните его кодом перерисовки панели с новым шрифтом
  • private void PanelOnPaint(object sender, PaintEventArgs e)
            {
                Graphics gr = e.Graphics;   // Получаем контекст устройства 
        
                // Создаем новый шрифт по выбранному в меню с заведомо
                // большим размером, чтобы потом подогнать уменьшением
                float emSize = 100.0F;
                System.Drawing.Font font = 
                    new Font(itemSelectedFont.Text.Trim(), emSize); 
                String str = font.Name; // Строка для рисования
        
                // Подгоняем размер текстового блока под ширину формы 
                float widthText = gr.MeasureString(str, 
                    font = new Font(itemSelectedFont.Text, emSize)).Width;
                while ((panel.ClientSize.Width - widthText) < 0.0F)
                {
                    emSize -= 0.5F;
                    widthText = gr.MeasureString(str, 
                        font = new Font(itemSelectedFont.Text, emSize)).Width;
                }
        
                // Настроим точку привязки текстового блока
                System.Drawing.StringFormat fmt = new StringFormat();
                fmt.Alignment = StringAlignment.Center; // По горизонтали
                fmt.LineAlignment = StringAlignment.Center;   // По вертикали
        
                // Настроим точку привязки клиентской области панели
                PointF point = new PointF(panel.ClientSize.Width / 2,
                    panel.ClientSize.Height / 2);
        
                // Рисуем на панели название выбранного шрифта текущим цветом формы
                gr.DrawString(str, font, 
                    new SolidBrush(this.ForeColor), point, fmt);
            }
  • В классе Form2 переопределите унаследованный виртуальный метод диспетчеризации OnResize() (ввод начните с ключевого слова override ) и заполните его следующим кодом
  • protected override void OnResize(EventArgs e)
            {
                panel.Invalidate();
                base.OnResize(e);
            }

    Этот метод нужен для перерисовки панели внутри формы при изменении пользователем ее размеров.

  • Запустите Form2 и удостовертесь в ее работоспособности, результат будет таким
  • Обратите внимание, что мы обеспечили центрирование текста на панели и автоматическую его подгонку под размеры формы.

    Упражнение 3. Меню с изображениями

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

    В поставку оболочки Visual Studio 2005 Standard Edition входит архив с большим набором готовых пиктограмм, который после установки на компьютере находится в каталоге C:\Program Files\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary

    Если распаковать этот архив, то используемые в меню изображения хранятся в папке bitmaps/commands, рассортированные по разной глубине цвета. Если в Вашей системе нет этих пиктограмм, то их можно найти в прилагаемом к данной лабораторной работе каталоге Source.

    Служебные изображения в программе удобнее использовать как ресурсы, внедренные в исполнимую сборку и переносимые вместе с этой сборкой. Файл изображения добавляется к проекту командой Project/Add Existing Item, после чего оболочка физически скопирует его в каталог размещения проекта и он появится в панели Solution Explorer.

    Далее можно использовать один из способов превращения этого файла в ресурс приложения: выделить этот файл и через панель Properties установить для него опцию Build Action равной Embedded Resource. Это заставит оболочку при компиляции внедрять указанный файл как ресурс в сборку приложения. Внутри самого кода приложения мы можем адресоваться к этому файлу.

    Пусть, например, мы хотим использовать пиктограмму Open.bmp и добавили этот файл в проект. Доступ к изображению из кода программы можно осуществить разными способа, например, с помощью следующего кода

    Bitmap bmCut = new Bitmap(this.GetType(), "path.Open.bmp");

    Первый аргумент должен ссылаться на класс приложения, в нашем случае это порождающий класс текущего объекта. Второй аргумент, это имя файла изображения с путем относительно корневой папки проекта

    Присоединить изображение к элементу меню можно присвоением ссылки свойству Image соответствующей команды, например

    itemCut.Image = bmCut;

    После таких действий пиктограмма элемента будет отображаться слева от текста меню на месте отображения флажка.

    В данном упражнении разработаем простой редактор изображений, в котором можно будет загружать и сохранять файлы изображений, использовать буфер обмена и отображать рисунки несколькими способами. При этом задействуем ряд стандартных диалоговых окон. Для тренировки рассмотрим несколько способов загрузки изображений в элементы меню.

  • Откройте файл Form3.cs в режиме View Designer и задайте свойству Text формы значение " Простой редактор изображений "
  • Поместите на форму компонент PictureBox и настройте его так: Name = pictureBox, Dock = Fill
  • В дальнейшем экземпляр элемента управления PictuteBox будет служить контейнером при просмотре изображений в нашем законченном упражнении.

    Декларативная часть создания меню

    Меню для нашего упражнения мы создадим, чередуя декларативный и программный способ. Мы с вами знаем, что при декларативном способе создания объектов работу по написанию кода за нас выполняет оболочка в закулисной части класса формы, который имеет расширение Designer.cs. По большому счету разницы нет, как создавать код: с помощью графического инструмента или непосредственно вручную. Главное знать, что писать и как позиционировать объекты. При создании меню проблемы с позиционированием не возникает, главное - порядок следования компонентов. Но познакомиться с графическими возможностями оболочки по созданию меню не помешает.

  • Поместите на форму компонент MenuStrip с именем menu и настройте его декларативно в соответствии с приведенным ниже деревом свойств
  • Создавать декларативным способом новые элементы меню любого уровня удобнее, если навести курсор справа от заготовки команды и раскрыть список предлагаемых типов, затем выделенный элемент меню можно настроить через панель Properties.

    Дерево свойств для создания и настройки меню упражнения

    На схеме свойство ( Name ) взято в скобки, поскольку это не свойство элемента меню, а имя ссылки, адресующейся к объекту. Хотя у элементов управления все же есть свойство Name типа String, но его чаще всего используют как метку элемента для его узнаваемости в коллекции, но не как адресную ссылку.

    Свойство ImageTransparentColor устанавливает цвет в изображении пиктограммы, который система должна считать прозрачным. В нашем случае каждая загружаемая пиктограмма имеет рамку цвета Magenta, и если не установить этот цвет в свойстве ImageTransparentColor, то рамка будет видна на пиктограмме в меню.

    Когда снимают фильм со спецэффектами, передний план тоже снимают на фоне экрана, выкрашенного в однотонный редко встречающийся цвет. При монтаже цвет фона легко убирают и подсовывают вместо него любой нужный материал. Этот редкий экзотический цвет и можно считать ImageTransparentColor, который система считает прозрачным.

    Добавление пиктограмм к объектам команд

    Мы создали декларативным способом объекты команд всплывающего меню только для двух элементов верхнего уровня. Для третьего элемента объекты команд создадим программно чуть позже. Но сначала нужно в проект загрузить пиктограммы декларативно.

    Изображения мы планируем добавить к следующим объектам команд:

    Элемент меню Файл с пиктограммой

    itemOpen

    Open.bmp

    itemCut

    Cut.bmp

    itemCopy

    Copy.bmp

    itemPaste

    Paste.bmp

    itemDelete

    Delete.bmp
    itemSaveAs Save.bmp

    Большинство современных компьютеров поддерживают глубину цвета 24 бита, поэтому готовые рисунки будем копировать из соответствующей папки с именем 24bitcolor, которую можно найти в прилагаемом каталоге Source (или в архиве C:\Program Files\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary\VS2005ImageLibrary.zip ).

    Пиктограммы в исполнимую сборку программы, для тренировки, будем загружать несколькими способами:

  • Предварительно поместим рисунки в файл локальный ресурсов Form3.resx на этапе проектирования, код которого при компиляции будет внедрен в исполнимую сборку (файлы Cut.bmp и Copy.bmp )
  • Добавим к проекту файлы рисунков и отметим в их свойствах, что это внедряемый ресурс ( Embedded Resource ), чтобы компилятор оболочки автоматически включил их код в исполнимую сборку (файл Paste.bmp )
  • Присоединим пиктограмму немедленно к свойству Image элемента через стандартный файл глобальных ресурсов, используя графические средства оболочки (файл Delete.bmp )
  • Добавим к проекту свой ( пользовательский ) файл глобальных ресурсов и поместим в него ресурс нужной картинки (файл Open.bmp )
  • Будем загружать пиктограмму в элемент меню на этапе выполнения непосредственно из файла рисунка, который должен физически находиться вместе с исполнимой сборкой (файл Save.bmp )
  • Во всех случаях после компиляции код рисунков должен оказаться в сборке и мы во время исполнения будем присоединять его к элементам меню по мере необходимости, правда, немного по разному.

    Способ 1. Загрузка рисунков через локальный файл ресурсов

  • В панели Solution Explorer двойным щелчком на локальном XML -файле разметки Form3.resx вызовите редактор ресурсов и через первый узел меню установите режим Images
  • Выполните команду Add Resource/Add Existing File меню редактора ресурсов и, удерживая клавишу Ctrl, добавьте в ресурс файлы Cut.bmp и Copy.bmp глубиной 24bitcolor из прилагаемого к работе каталога Source
  • Выполните команду View Details меню View редактора ресурсов, чтобы отобразить представление ресурсов в виде таблицы
  • Откройте панель Solution Explorer и убедитесь, что оболочка создала каталог Resources, в который скопировала отмеченные файлы
  • Оболочка автоматически присвоила ресурсам подходящие имена, которые мы можем изменить в панели Properties, выделяя соответствующие ресурсы. Здесь же мы можем указать, где физически до компиляции будут располагаться рисунки: в XML -файле ресурсов Form3.resx или в отдельных BMP -файлах, на которые в XML -файле разметки будут помещены только ссылки.

    Пока у нас рисунки имеют состояние Linked at compile time, что означает, что при компиляции их код будет внедрен в исполнимую сборку прямо из BMP -файлов по ссылкам, помещенным в XML -файл Form3.resx. Поэтому присутствие BMP -файлов в проекте обязательно при каждой компиляции. При установке для любого рисунка опции Embedded in .resx оболочка уже на этапе проектирования сразу же физически разместит его код в файле Form3.resx. После чего мы можем удалить соответствующий BMP -файл из проекта.

    Чтобы убедиться в сказанном, выполните следующее:

  • В панели Solution Explorer вызовите контекстное меню для узла Form3.resx и выполните команду View Em, чтобы открыть XML -файл ресурсов в режиме разметки
  • Убедитесь, что в конце XML -файла прописаны только ссылки на добавленные в проект файлы рисунков
  • <data name="Copy" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>Resources\Copy.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>
      <data name="Cut" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>Resources\Cut.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>
  • В панели Solution Explorer вызовите контекстное меню для узла Form3.resx и выполните команду View Designer, чтобы открыть файл ресурсов в режиме визуального конструктора
  • Выделите ресурс Cut и через панель Properties установите для него свойство Persistence в значение Embedded in .resx
  • Вновь откройте Form3.resx в режиме View Em и убедитесь, что вместо ссылки на файл Cut.bmp оболочка включила в ресурс сам код этого рисунка
  • <data name="Copy" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>Resources\Copy.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>
      <data name="Cut" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>
            Qk02AwAAAAAAADYAAAAoAAAAEAAAABAAAAABABgAAAAAAAAAAAASCwAAEgsAAAAAAAAAAAAA/wD//wD/
            /wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/u6GUl19FY0Iy
            oo2C/wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/zYZjq2I/ilAzYkU3vb2+uZ+SZkM0
            Xj8wqJGG/wD//wD//wD//wD//wD//wD//wD/3YNVw7KquJyOhU4zwMDAxYJhqmI+YzwpLyUj/wD//wD/
            /wD//wD//wD//wD//wD/4YVXxbOqva6ojFI1vLi21HxSwrKqrpSGa0It/wD//wD//wD//wD//wD//wD/
            /wD/2ph25YZWsGZBj1U4nHJdyXVNwbm1up6Qj1U2/wD//wD//wD//wD//wD//wD//wD/yLWs645d24JW
            n19AlGBHvnRP1XtPuWtFrYd0/wD//wD//wD//wD//wD//wD//wD//wD/yLau3pl234RWxo1wzHhN0nxR
            xpF3vaec/wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/iHNmcWBWhmdVq56Z/wD//wD//wD//wD/
            /wD//wD//wD//wD//wD//wD//wD/uLSxjoB0fm5jaV1VdnJu/wD//wD//wD//wD//wD//wD//wD//wD/
            /wD//wD//wD/urCqopSLqKCZjoF3Y1tV/wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/vrGq
            oZSLyMbEzcXAa19Wra2u/wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/qZuSsqijw8PD6OTifG9m
            qKmq/wD//wD//wD//wD//wD//wD//wD//wD//wD/u7i2o5aMvLq5/wD/3dzcvLKrpqGe/wD//wD//wD/
            /wD//wD//wD//wD//wD//wD/v7izs6ie/wD//wD/y8vL4t3aoZeQ/wD//wD//wD//wD//wD//wD//wD/
            /wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD//wD/
        </value>
      </data>
  • Выделите ресурс Cut и через панель Properties верните свойство Persistence в прежнее значение Linked at compile time
  • Способ 2. Назначение рисункам свойств внедряемых ресурсов

    Добавим к проекту файл рисунка Paste.bmp и сообщим компилятору о необходимости включать его код в исполнимую сборку.

  • В панели Solution Explorer вызовите контекстное меню для папки Resources, в которой уже должны находиться файлы Cut.bmp и Copy.bmp с глубиной цвета 24bitcolor, и командой Add/Existing Item добавьте к проекту из каталога Source файл Paste.bmp с такой же глубиной цвета, предварительно установив для диалогового окна соответствующий фильтр
  • Выделите файл Paste.bmp и в панели Properties установите для него Build Action = Embedded Resource
  • При такой настройке при компиляции код файла будет копироваться в исполнимую сборку.

    Способ 3. Присоединение рисунка к свойству Image элемента меню через стандартный файл глобальных ресурсов проекта

    Присоединим к свойству Image элемента меню itemDelete файл с рисунком Delete.bmp через стандартный файл глобальных ресурсов. Этот файл может хранить в себе множественные ресурсы, которые доступны в любом месте кода программы.

  • Откройте файл Form3.cs в режиме View Designer, выделите через раскрывающийся список панели Properties элемент itemDelete, раскройте его свойство Image и для режима Project resource file загрузите в элемент кнопкой Import файл Delete.bmp из прилагаемого к работе каталога Source
  • Загруженная пиктограмма немедленно появится слева от команды Delete на меню формы

  • Откройте в каталоге Properties проекта стандартный файл глобальных ресурсов Resources проекта в режиме View Em и убедитесь, что оболочка поместила относительную ссылку на Delete.bmp в этот файл
  • <data name="Delete" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>..\Resources\Delete.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>
  • Откройте файл Form3.Designer.cs и в секции #region убедитесь, что оболочка сразу прописала код присоединения рисунка к элементу меню
  • // 
                // itemDelete
                // 
                this.itemDelete.Image = global::StripControls.Properties.Resources.Delete;

    Никаких дополнительных строк кода для загрузки рисунка указанным способом в дальнейшем не потребуется, оболочка все сделала за нас.

    Замечание. Если пытаться немедленно присоединить к элементу файл рисунка через локальный ресурс в использованном нами окне Select Resource, то оболочка выкинет все ранее присоединенные файлы из этого ресурса (В нашем случае Cut и Copy ) и оставит только один непосредственно присоединяемый к элементу управления ресурс.

    Способ 4. Размещение рисунка в пользовательском файле глобальных ресурсов

    Загрузим рисунок Open.bmp в файл пользовательский глобальных ресурсов MyResources.resx, который нужно создать и добавить к проекту в любое место.

  • В панели Solution Explorer вызовите контекстное меню для папки Resources и добавьте в нее командой Add/New Item новый файл глобальных ресурсов с именем MyResources.resx
  • В панели Solution Explorer двойным щелчком на файле Resources.resx откройте его в режиме View Designer
  • Переведите редактор в режим Images и командой Add Resource/Add Existing File добавьте к проекту файл Open.bmp глубиной цвета 24bitcolor из прилагаемого каталога Source точно также, как мы это делали ранее при загрузке файлов Cut.bmp и Copy.bmp в локальный ресурс Form3.resx
  • Оболочка физически скопирует файл Open.bmp в каталог Resources проекта и пропишет относительную ссылку на него в пользовательском XML -файле разметки MyResources.resx глобальных ресурсов

    <data name="Open" type="System.Resources.ResXFileRef, System.Windows.Forms">
        <value>..\Resources\Open.bmp;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, 
          Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
      </data>

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

    Способ 5. Загрузка пиктограммы из файла рисунка во время выполнения

    Файл Save.bmp мы будем загружать в элемент itemSaveAs непосредственно во время выполнения программы. Поэтому нужно присоединить его к проекту и настроить свойства так, чтобы оболочка при компиляции копировала этот файл в место размещения исполнимой сборки.

  • В панели Solution Explorer вызовите контекстное меню для папки Resources и командой Add/Existing Item добавьте в проект файл Save.bmp ( 24bitcolor ) из прилагаемого каталога Source
  • Выделите скопированный файл Save.bmp и через панель Properties установите свойство Copy to Output Directory в значение Copy if newer, чтобы оболочка скопировала файл в каталог размещения исполнимой сборки
  • Динамическое присоединение пиктограмм к элементам меню

    Итак, на предудущих этапах работы мы загрузили все нужные файлы с пиктограммами в файлы ресурсов проекта или просто в каталог Resources проекта. Ресурс файла Delete.bmp мы сразу присоединили к свойству Image элемента меню itemDelete, поэтому дальнейших действий для этого элемента никаких не требуется. Ресурсы остальных пиктограмм пропишем в коде и они будут присоединяться к элементам меню на этапе выполнения программы.

  • Откройте файл Form3.cs в режиме View Em и добавьте в класс Form3 следующую функцию с именем LoadImages() для присоединения рисунков к элементам меню
  • // Загрузка изображений в элементы меню
            void LoadImages()
            {
                // Присоединяем 2 пиктограммы из 
                // файла локальных ресурсов Form3.resx 
                ResourceManager resources =
                    new ResourceManager(typeof(Form3));//или (this.GetType())
                itemCopy.Image = (Image)resources.GetObject("Copy");
                itemCut.Image = (Image)resources.GetObject("Cut");
        
                // Присоединяем пиктограмму из файла,
                // помеченного как Embedded Resource 
                itemPaste.Image = new Bitmap(this.GetType(),    // или (typeof(Form3)
                    "Resources.Paste.bmp");
        
                ///////////////////////////////
                // Пиктограмма для Delete внедрена декларативно
                // через стандартный файл глобальных ресурсов,
                // поэтому для нее ничего не делаем 
                ///////////////////////////////
        
                // Присоединяем пиктограмму из пользовательского 
                // файла глобальных ресурсов MyResources.resx 
                itemOpen.Image = Resources.MyResources.Open;
        
                // Присоединяем пиктограмму непосредственно из файла рисунка
                itemSaveAs.Image = new Bitmap("Resources\\Save.bmp");
            }
  • Вставьте в конструктор класса Form3 после вызова метода InitializeComponent() следующий код, выполняющий присоединение
  • public Form3()
            {
                InitializeComponent();
        
                LoadImages();// Присоединяем пиктограммы 
            }
  • Запустите Упражнение 3 на выполнение и убедитесь в наличии пиктограмм для назначенных элементов меню
  • Обратите внимание на то, что для меню View заготовлен только узел itemView, а его коллекция DropDownItems пока пуста, поскольку ранее мы договорились наполнить ее программным способом. Вот это сейчас мы и выполним.

    Создание элементов меню программным способом

    Меню View будет управлять формой представления рисунков элементом pictureBox, который мы добавили на Form3 ранее. Через общий обработчик элементов меню View мы будем передавать в pictureBox требуемые значения его свойства SizeMode из библиотечного перечисления PictureBoxSizeMode. Закрепленное за элементом меню значение перечисления PictureBoxSizeMode будем сохранять в свойстве Tag элемента и извлекать его в обработчике с последующей распаковкой ссылочного типа Object в значимый тип PictureBoxSizeMode.

    Схема свойств меню View

    Здесь свойство Name - это свойство элемента типа String, а не имя ссылки с адресом объекта. При настройке элементов меню по приведенному ниже коду не забудьте начать набор строки регистрации обработчика события Click для первого элемента DropDownItems[0] вручную - с применением клавиши Tab. Это позволит создать обработчик с правильной сигнатурой. После правильного создания общего обработчика для подписки события Click последующих элементов можно воспользоваться упрощенным синтаксисом.

  • Добавьте в класс Form3 функцию с именем CreateView() для создания и настройки элементов меню View. Код функции соответствует приведенной выше схеме и представлен ниже
  • // Функция создания элементов меню View программным способом
            void CreateView()
            {
                // Имя элемента меню на верхнем уровне для возможности 
                // адресации в коллекции Items по его имени 
                itemView.Name = "View";
        
                ToolStripMenuItem item=new ToolStripMenuItem("Normal");
                item.Tag = PictureBoxSizeMode.Normal;
                // Создайте вручную!!!!!!!
                item.Click += new EventHandler(ViewOnClick);
                itemView.DropDownItems.Add(item);// Добавили в коллекцию View 
                // Имя элемента меню View для возможности адресации
                // в коллекции DropDownItems по его имени
                item.Name = "Normal";
                // Отмечаем флажок, поскольку в начальном состоянии 
                // свойство SizeMode элемента PictureBox именно Normal
                item.Checked = true;
        
                item = new ToolStripMenuItem();
                item.Text = "Center";
                item.Tag = PictureBoxSizeMode.CenterImage;
                item.Click += ViewOnClick;
                itemView.DropDownItems.Add(item);
                item.Name = "Center";
        
                item = new ToolStripMenuItem("Stretch");
                item.Tag = PictureBoxSizeMode.StretchImage;
                item.Click += ViewOnClick;
                itemView.DropDownItems.Add(item);
                item.Name = "Stretch";
        
                item = new ToolStripMenuItem();
                item.Text = "Zoom";
                item.Tag = PictureBoxSizeMode.Zoom;
                item.Click += ViewOnClick;
                itemView.DropDownItems.Add(item);
                item.Name = "Zoom";
        
                item = new ToolStripMenuItem("AutoSize");
                item.Tag = PictureBoxSizeMode.AutoSize;
                item.Click += ViewOnClick;
                itemView.DropDownItems.Add(item);
                item.Name = "AutoSize";
            }

    Еще раз вспомним, что порядок следования кода добавления элементов в коллекцию определяет их порядок прорисовки в работающем приложении. При создании элементов меню View мы поочередно используем разные перегрузки конструктора класса ToolStripMenuItem. Мы задаем свойству Tag типа Object упакованное значение перечисления PictureBoxSizeMode, чтобы затем в обработчике распаковать его. Помечая элемент значением свойства Name мы перестраховываемся на тот случай, если вдруг далее нам придется к нему адресоваться в коллекции по синтаксису ассоциативного массива (адресация в массиве по индексу или по имени).

  • Вставьте в конец конструктора Form3() одноименного класса код вызова функции CreateView()
  • public partial class Form3 : Form
        {
            public Form3()
            {
                InitializeComponent();
        
                LoadImages();// Присоединяем пиктограммы 
        
                CreateView();// Динамически заполняем меню View
            }
  • В созданный обработчик ViewOnClick() внесите следующий код
  • void ViewOnClick(object sender, EventArgs e)
            {
                // Приводим ссылку на элемент меню View, сгенерировавший событие 
                ToolStripMenuItem item = (ToolStripMenuItem)sender;
        
                // Распаковываем объект и используем сохраненную 
                // в нем информацию для управления контейнером рисунков
                pictureBox.SizeMode = (PictureBoxSizeMode)item.Tag;
        
                ////////////////////////////////////////////////////////
                // Управление флажками выполним в обработчике события
                // DropDownOpening, которое информирует о раскрытия узла View
                ////////////////////////////////////////////////////////
                //////////// Следующий код закомментирован /////////////
                ////////////      Жалко выбрасывать!!!     /////////////
                /*//////////////////////////////////////////////////////
                // Сбрасываем флажки всех элементов 
                foreach (ToolStripMenuItem it in itemView.DropDownItems)
                    it.Checked = false;
                // Устанавливаем флажок на выделенном элементе,
                // адресуясь к коллекции по имени элемента
                ((ToolStripMenuItem)itemView.DropDownItems[item.Name]).Checked = true;
                //item.Checked = true;  // Альтернативный вариант
                //*/////////////////////////////////////////////////////
            }
  • Запустите Упражнение 3 и убедитесь, что команды узла View динамически формируются
  • Программирование узлов меню верхнего уровня

    Команды меню (опции) являются неотъемлемой частью интерфейса пользователя в современных программах. Код обработчиков элементов меню реализует основную функциональность приложения и требует пристального внимания разработчика. Заполним наши обработчики команд нужным кодом. Тщательно разберитесь в нем, поскольку он реализует логику программы и во многом может стать для вас (программистов!!!, но не кочегаров и зольщиков) поучительным.

    Ранее мы создали заготовки обработчиков для всех значимых элементов меню. Пришла пора их запрограммировать, тем самым обеспечив функциональность программе. Значимые элементы меню недаром называют командами. Именно через них пользователь, незнакомый с тонкостями программирования, управляет компьютером для выполнения своей работы.

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

    Узел File:

  • Найдите обработчик FileOnDropDown() события DropDownOpening, которое генерируется при раскрытии элемента File, и запрограммируйте его следующим образом
  • private void FileOnDropDown(object sender, EventArgs e)
            {
                // Сделать недоступным команду SaveAs, если контейнер пустой 
                itemSaveAs.Enabled = pictureBox.Image != null;
            }

    Узел Edit:

  • Найдите обработчик EditOnDropDown() события DropDownOpening узла Edit и запрограммируйте его так
  • private void EditOnDropDown(object sender, EventArgs e)
            {
                /////////////////////////////////////////////////
                // Регулируем доступность команд Copy, Cut, Delete
                /////////////////////////////////////////////////
                // Нечего редактировать - контейнер пустой 
                itemCopy.Enabled = itemCut.Enabled = itemDelete.Enabled =
                    pictureBox.Image != null;
        
                /////////////////////////////////////////////////
                // Регулируем доступность команды Paste
                /////////////////////////////////////////////////
                // Ивлекаем адрес системного буфера обмена, 
                // используя интерфейсную ссылку базового типа
                IDataObject data = Clipboard.GetDataObject();
                // Если системный буфер не содержит рисунков 
                // подходящего типа, то вставлять тоже нечего 
                itemPaste.Enabled = data.GetDataPresent(typeof(Bitmap))
                    || data.GetDataPresent(typeof(System.Drawing.Imaging.Metafile));
            }

    Узел View:

  • Найдите обработчик ViewOnDropDown() для узла View и внесите в него следующий код
  • private void ViewOnDropDown(object sender, EventArgs e)
            {
                // При раскрытии узла View перебираем коллекцию 
                // и управляем флажками с помощью состояния pictureBox
                foreach (ToolStripMenuItem item in itemView.DropDownItems)
                {
                    item.Checked = 
                        (PictureBoxSizeMode)item.Tag == pictureBox.SizeMode;
                }
            }

    Программирование команд

    Приступим к программированию самих команд всплывающих меню.

    Команда Exit:

  • Найдите обработчик ExitOnClick() для команды Exit и закодируйте его так
  • private void ExitOnClick(object sender, EventArgs e)
            {
                this.Close();
            }

    Команда Delete:

  • Найдите обработчик DeleteOnClick() для команды Delete и внесите в него следующий код
  • private void DeleteOnClick(object sender, EventArgs e)
            {
                pictureBox.Image = null;
            }

    В этом обработчике мы просто обнуляем ссылку на рисунок и изображение исчезает из контейнера pictureBox.

    Команда Copy:

  • Найдите обработчик CopyOnClick() для команды Copy и заполните его так
  • private void CopyOnClick(object sender, EventArgs e)
            {
                // Заполняем системный буфер обмена рисунком контейнера
                // Если при раскрытии узла Edit текущий 
                // рисунок есть, то команда Copy доступна
                Clipboard.SetDataObject(pictureBox.Image, true);
            }

    Здесь вызывается статический метод класса системного буфера обмена. Первым параметром метода является ссылка на объект, содержащий копируемые данные. Второй параметр устанавливает, чтобы данные сохранялись в системном буфере даже после закрытия приложения.

    Команда Cut:

  • Найдите обработчик CutOnClick() для команды Cut и заполните его так
  • private void CutOnClick(object sender, EventArgs e)
            {
                ////////////////////////////////////////////////
                // Используем уже готовые обработчики!!!
                ////////////////////////////////////////////////
                // Копируем в системный буфер обмена
                CopyOnClick(sender, e);
                // Обнуляем контейнер рисунка
                DeleteOnClick(sender, e);
            }

    Команда Paste:

  • Найдите обработчик PasteOnClick() для команды Paste и заполните его так
  • private void PasteOnClick(object sender, EventArgs e)
            {
                // Подключаемся к системному буферу обмена 
                IDataObject data = Clipboard.GetDataObject();
        
                // Распознаем тип изображения в буфере обмена и читаем данные
                Type typeMeta = typeof(System.Drawing.Imaging.Metafile);
                if (data.GetDataPresent(typeMeta))
                    pictureBox.Image = (Image)data.GetData(typeMeta);
                else
                    pictureBox.Image = (Image)data.GetData(typeof(Bitmap));
            }

    Доступность команды Paste проверяется в узле Edit и зависит от типа ожидаемых данных. Только в случае допустимости типа изображения может сработать обработчик PasteOnClick(), поэтому-то мы и рассматриваем в нем только две альтернативы.

    Для команд Open и Save As нам потребуется механизм отображения имени загруженного файла в заголовке окна наряду с самим заголовком. Для этого нужно объявить два поля типа String.

  • Добавьте в начало класса Form3 два поля, связанные с заголовком окна, и инициализируйте одно из них в конструкторе класса
  • public partial class Form3 : Form
        {
            String originalTitle;   // Поле для хранения чистого заголовка
            String name;            // Поле для хранения имени загруженного рисунка
        
            public Form3()
            {
                InitializeComponent();
        
                LoadImages();// Присоединяем пиктограммы 
        
                CreateView();// Динамически заполняем меню View
        
                originalTitle = this.Text;// Сохраняем чистый заголовок
            }
            ............................................................
        }

    Команда Open:

    В этой команде мы будем использовать стандартный диалог OpenFileDialog, который должным образом настроим. При загрузке файла в контейнер рисунка следует использовать защищенный код, перехватывающий и обрабатывающий возможные системные исключения на случай попытки загрузить файл формата, который элементом PictureBox не поддерживается.

  • Найдите обработчик OpenOnClick() для команды Open и заполните его так
  • // Обработчик команды Open 
            OpenFileDialog openDialog; // Объявили ссылку на диалог
            bool openFlag = true;// Запускается в первый раз 
            //
            private void OpenOnClick(object sender, EventArgs e)
            {
                if (openFlag)
                {
                    openFlag = false;   // Больше не создавать и не настраивать!
                    // Создаем и настраиваем диалог
                    openDialog = new OpenFileDialog();
                    openDialog.Filter = "All allowable Image Files" +                               // 1)Заголовок
                        "|*.bmp;*.ico;*.gif;*.jpeg;*.jpg;*.jfif;*.png;*.tif;*.tiff;*.wmf;*.emf|" +  // 1)|Шаблон|
                        "Windows Bitmap (*.bmp)|*.bmp|" +                               // 2) Заголовок|Шаблон| 
                        "Graphics Interchange Format (*.gif)|*.gif|" +                  // 3) Заголовок|Шаблон|
                        "JPEG File Interchange Format (*.jpg)|*.jpeg;*.jpg;*.jfif|" +   // 4) Заголовок|Шаблон|
                        "Portable Network Graphics (*.png)|*.png|" +                    // 5) Заголовок|Шаблон|
                        "Tagged Image File Format (*.tif)|*.tif;*.tiff|" +              // 6) Заголовок|Шаблон|
                        "Windows Metafile (*.wmf)|*.wmf|" +                             // 7) Заголовок|Шаблон|
                        "Enhanced Metafile (*emf)|*.emf|" +                             // 8) Заголовок|Шаблон|
                        "All Files (*.*)|*.*";                                          // 9) Заголовок|Шаблон
        
                    // Устанавливаем последний шаблон активным (по умолчанию 1!)
                    String[] str = openDialog.Filter.Split('|');
                    openDialog.FilterIndex = str.Length / 2;
                }
        
                // Защищенный код для перехвата системных исключений на 
                // случай попытки загрузки неподдерживаемого формата данных 
                if (openDialog.ShowDialog() == DialogResult.OK)
                    try
                    {
                        pictureBox.Image = Image.FromFile(openDialog.FileName);
        
                        // Выводим имя рисунка в заголовок окна
                        name = openDialog.FileName;
                        String[] str = name.Split('\\');
                        name = str[str.Length - 1].ToLower();
                        this.Text = "(" + name + ") " + originalTitle;
                    }
                    catch (Exception ext)
                    {
                        MessageBox.Show(ext.Message, this.Text, 
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
            }

    В приведенном коде ссылка на диалог объявлена как поле, чтобы он создавался только один раз и сохранял предыдущие настройки пользователя при повторных обращениях. Фильтр диалога мы тоже настраиваем только раз и для тренировки устанавливаем его на последний шаблон (первый шаблон устанавливается по умолчанию и индекс массива начинается с 1). При формировании фильтра нужно строго следить, чтобы второй член пары "Массив-Шаблон ", т.е. шаблон, не имел пробелов, иначе ошибки не будет, но шаблон не сработает!.

    Команда Save As:

    В этой команде мы будем использовать стандартный диалог SaveFileDialog, который должным образом настроим. При выгрузке файла рисунка на диск также, на случай переполнения, защиты от записи и т.д., будем использовать защищенный код, перехватывающий и обрабатывающий возможные системные исключения.

  • Найдите обработчик SaveAsOnClick() для команды Save As и заполните его так
  • // Обработчик команды Save As 
            SaveFileDialog saveDialog; // Объявили ссылку на диалог
            System.Drawing.Imaging.ImageFormat[] imageFormat;// Объявили ссылку на массив
            bool saveFlag = true;// Запускается в первый раз 
            //
            private void SaveAsOnClick(object sender, EventArgs e)
            {
                if (saveFlag)
                {
                    saveFlag = false;   // Больше не создавать и не настраивать!
                    // Создаем диалог
                    saveDialog = new SaveFileDialog();
                    // Настраиваем фильтр
                    saveDialog.Filter = "Windows Bitmap (*.bmp)|*.bmp|" + 
                        "Graphics Interchange Format (*.gif)|*.gif|" + 
                        "JPEG File Interchange Format (*.jpg)|*.jpeg;*.jpg;*.jfif|" + 
                        "Portable Network Graphics (*.png)|*.png|" + 
                        "Tagged Image File Format (*.tif)|*.tif;*.tiff";
        
                    // Инициализируем массив допустимыми форматами рисунков
                    imageFormat = new System.Drawing.Imaging.ImageFormat[] {
                        System.Drawing.Imaging.ImageFormat.Bmp,
                        System.Drawing.Imaging.ImageFormat.Gif,
                        System.Drawing.Imaging.ImageFormat.Jpeg,
                        System.Drawing.Imaging.ImageFormat.Png,
                        System.Drawing.Imaging.ImageFormat.Tiff
                    };
                }
        
                // Заполняем поле именем загруженного рисунка без расширения
                saveDialog.FileName = name.Substring(0, name.LastIndexOf('.'));
        
                // Защищенный код для перехвата системных 
                // исключений на случай сбоя записи
                if (saveDialog.ShowDialog() == DialogResult.OK)
                    try
                    {
                        // Пытаемся сохранить в формате текущего фильтра
                        pictureBox.Image.Save(saveDialog.FileName,
                            imageFormat[saveDialog.FilterIndex - 1]);
                        // Обновляем контейнер новым форматом - ???????????????
                        //pictureBox.Image = Image.FromFile(saveDialog.FileName);
       
                        // Выводим в заголовок окна имя файла с новым расширением
                        name = saveDialog.FileName;
                        String[] str = name.Split('\\');
                        name = str[str.Length - 1].ToLower();
                        this.Text = "(" + name + ") " + originalTitle;
                    }
                    catch (Exception ext)
                    {
                        MessageBox.Show(ext.Message, this.Text,
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
            }

    На этом выполнение Упражнения 3 завершено. Испытание можно проводить на рисунках, хранящихся в прилагаемом каталоге Source, или любых других.

  • Запустите приложение и испытайте его работу. Результат должен получиться примерно таким
  • Упражнение 4. Использование списка изображений для хранения пиктограмм меню

    В этом упражнении мы создадим заготовку меню с пиктограммами, которые будем присоединять к элементам меню из списка изображений. Сам список с изображениями можно заполнять из хранилища ресурсов, которое находится внутри файла исполнимой сборки нашего приложения. В свою очередь, хранилище ресурсов сборки заполняется при компиляции сборки из указанного XML -файла, который нужно сформировать заранее и поместить в него ссылки на необходимые текстовые или двоичные ресурсы. XML - или двоичный файл можно создать декларативно (вручную) или программно, написав соответствующую консольную утилиту.

    Таким образом, план действий будет таким:

  • Написать утилиту, создающую и наполняющую файл двоичными ресурсами изображений и текстовым ресурсом заголовка окна
  • Создать метод, извлекающий элементы ресурсов в список изображений
  • Создать окно с заготовкой меню и присоединить к его элементам пиктограммы из списка изображений
  • Создание файла ресурсов программным способом

    В пространстве имен System.Resources двух библиотечных сборок mscorlib.dll и System.Windows.Forms.dll находятся все необходимые типы, которые обеспечивают программную работу с ресурсами. Необходимые для работы приложения ресурсы находятся в готовой исполнимой сборке в специальной секции, называемой хранилищем ресурсов, или отдельно в двоичном файле. В хранилище ресурсов они попадают при компиляции приложения из предварительно подготовленного файла ресурсов. На рисунке приведены варианты формирования хранилища ресурсов сборки

    Разработаем утилиту, которая будет создавать файл ресурсов и наполнять его нужными объектами. Для разнообразия в файле ресурсов сохраним и строковую информацию заголовка окна с заготовкой меню.

  • Добавьте к текущему решению командой File/Add/New Project еще один проект с именем UtilityResource типа консольного приложения
  • Добавьте к проекту UtilityResource ссылки на сборки System.Windows.Forms и System.Drawing. Для этого выделите в панели Solution Explorer узел UtilityResource и выполните команду Project/Add Reference
  • В панели Solution Explorer скопируйте файлы Cut.bmp, Copy.bmp, Paste.bmp, Delete.bmp, Open.bmp, Save.bmp из узла Resources проекта StripControls в корневой узел проекта UtilityResource (или добавьте напрямую из прилагаемой папки Source, как показано ниже для иконки приложения)
  • В панели Solution Explorer вызовите контекстное меню для корневого узла UtilityResource и выполните команду Add/Existing Item. В прилагаемом каталоге Source выберите иконку, например 1.ico, и добавьте ее в проект UtilityResource
  • Выделите все добавленные к проекту UtilityResource файлы изображений и в панели Properties определите для них опции Copy to Output Directory=Copy always, Build Action=None, чтобы при исполнении проекта оболочка копировала их в каталог размещения сборки
  • Откройте на редактирование файл Program.cs проекта UtilityResource и заполните его следующим кодом создания бинарного файла ресурсов
  • using System;
        
    namespace UtilityResource
    {
        using System.Resources;
        using System.Drawing;
        
        class Program
        {
            static void Main(string[] args)
            {
                // Создаем объект для записи в двоичный файл ресурсов
                ResourceWriter rw = new ResourceWriter("ResXForm4.resources");
        
                // Добавляем в файл описание ресурсов 'Ключ-Значение' (Key-Value)
                // Ресурсы должны находиться в одном каталоге с утилитой!!!
                rw.AddResource("Cut", new Bitmap("Cut.bmp"));
                rw.AddResource("Copy", new Bitmap("Copy.bmp"));
                rw.AddResource("Paste", new Bitmap("Paste.bmp"));
                rw.AddResource("Delete", new Bitmap("Delete.bmp"));
                rw.AddResource("Open", new Bitmap("Open.bmp"));
                rw.AddResource("Save", new Bitmap("Save.bmp"));
                rw.AddResource("strTitle", "Список изображений");
                rw.AddResource("icoTitle", new Icon("1.ico"));
        
                // Сохраняем файл с добавленными ресурсами и закрываем его
                rw.Generate();
                rw.Close();
            }
        }
    }
  • В панели Solution Explorer вызовите контекстное меню для узла UtilityResource и выполните команду Set as StartUp Project, чтобы сделать проект стартовым (либо просто щелкните левой кнопкой мыши на узле UtilityResource ). Корневой узел стартового проекта выделяется в панели Solution Explorer жирным шрифтом и должен быть в решении только один
  • Выполните проект UtilityResource, чтобы создать файл ресурсов с именем ResXForm4.resources программным способом
  • В панели Solution Explorer вызовите контекстное меню для узла проекта UtilityResource и исключите его из решения командой Remove
  • В панели Solution Explorer вызовите контекстное меню для корневого узла проекта StripControls и командой Add/Existing Item добавьте созданный ранее файл ресурсов ResXForm4.resources из каталога UtilityResource/bin/Debug отсоединенного проекта (при поиске файла не забудьте настроить фильтр диалогового окна на отображение всех файлов)
  • Настройте через панель Properties добавленный к проекту StripControls файл ресурсов ResXForm4.resources так, чтобы он стал копируемым при компиляции проекта в каталог размещения сборки, т.е. установите опции Copy to Output Directory=Copy always, Build Action=None
  • На этом подготовительная часть по формированию файла ресурсов из конкретных источников с рисунками закончена и можно приступать к использованию этих ресурсов в меню приложения.

    Создание формы с заготовкой меню

    Наша главная задача в упражнении 4 - посмотреть способ загрузки пиктограмм в меню из файла ресурсов через список изображений. Для этого мы не будем создавать полнофункциональное окно, а ограничимся лишь окном с заготовкой меню, в которое загружаются пиктограммы. Первые два узла заготовки меню сделаем точно таким же, как в упражнении 3, только создадим их программным способом.

  • Добавьте к проекту StripControls форму с именем Form4 командой Project/Add Windows Form
  • Поместите из панели Toolbox на форму кнопку Button (или любой другой элемент управления) и сразу удалите ее, чтобы явно прописалось конструктором оболочки свойство Name=Form4 формы в файле Form4.Designer.cs (или сами пропишите его в этом файле)
  • Откройте файл Start.cs в режиме View Designer, выделите объект списка listBoxChoice и добавьте в его свойство Items строку: "Упражнение 4."
  • Дополните файл Start.cs блоком кода, запускающим созданную форму Form4
  • // Если форма не запущена - запускаем
                if (!IsRun)
                {
                    switch (formName)
                    {
                        case "Form1":
                            frm = new Form1();
                            break;
                        case "Form2":
                            frm = new Form2();
                            break;
                        case "Form3":
                            frm = new Form3();
                            break;
                        case "Form4":
                            frm = new Form4();
                            break;
                    }
        
                    this.AddOwnedForm(frm);     // Сделать новую frm подчиненной Start
                    // frm.Owner = this;        // Алтернативный способ назначить владельца
                    frm.ShowInTaskbar = false;  // Не отображать метку окна в панели задач
                    frm.Show();                 // Показать новую форму
                }
                else
                    frm.Focus();                // Передать фокус запущенной форме
  • Откройте файл Form4.cs в режиме View Em и добавьте в него функцию CreateMenu() программного создания меню, которая будет иметь следующий код
  • // Видимые в методах класса поля-ссылки на элементы меню
            MenuStrip menu;
            ToolStripMenuItem itemFile;
            ToolStripMenuItem itemOpen;
            ToolStripMenuItem itemSaveAs;
            ToolStripMenuItem itemExit;
        
            ToolStripMenuItem itemEdit;
            ToolStripMenuItem itemCut;
            ToolStripMenuItem itemCopy;
            ToolStripMenuItem itemPaste;
            ToolStripMenuItem itemDelete;
        
            void CreateMenu()
            {
                // Создание меню
                menu = new MenuStrip();
                menu.Parent = this;
        
                // Формирование верхней линейки меню
                itemFile = new ToolStripMenuItem("File");
                menu.Items.Add(itemFile);
                itemEdit = new ToolStripMenuItem("Edit");
                menu.Items.Add(itemEdit);
        
                // Сборка узла File
                itemFile.DropDownItems.Add(itemOpen = new ToolStripMenuItem("Open"));
                itemFile.DropDownItems.Add(itemSaveAs = new ToolStripMenuItem("Save As..."));
                itemFile.DropDownItems.Add(new ToolStripSeparator());
                itemFile.DropDownItems.Add(itemExit = new ToolStripMenuItem("Exit"));
        
                // Сборка узла Edit
                itemEdit.DropDownItems.Add(itemCut = new ToolStripMenuItem("Cut"));
                itemEdit.DropDownItems.Add(itemCopy = new ToolStripMenuItem("Copy"));
                itemEdit.DropDownItems.Add(itemPaste = new ToolStripMenuItem("Paste"));
                itemEdit.DropDownItems.Add(itemDelete = new ToolStripMenuItem("Delete"));
            }
  • Добавьте в файл Form4.cs функцию LoadImage() загрузки пиктограмм меню из файла ресурсов через список изображений, которая будет представлена следующим кодом
  • // Создаем список для хранения изображений 
            ImageList imageList = new ImageList();
        
            void LoadImage()
            {
                // Присоединяем ранее сформированные ресурсы из файла ресурсов 
                ResourceReader rr = new ResourceReader("ResXForm4.resources");
                IDictionaryEnumerator en = rr.GetEnumerator();
                while (en.MoveNext())
                {
                    switch (en.Key.ToString())
                    {
                        default:
                            imageList.Images.Add(en.Key.ToString(), (Image)en.Value);
                            break;
                        case "icoTitle":
                            this.Icon = (Icon)en.Value;     // Иконка окна
                            break;
                        case "strTitle":
                            this.Text = (String)en.Value;   // Заголовок окна
                            break;
                    }
                }
                rr.Close(); // Закрыли файл ресурсов
        
                // Определяем цвет, имитирующий прозрачность окраин пиктограмм 
                imageList.TransparentColor = Color.Magenta;
        
                // Закрепляем пиктограммы из списка за командами меню с помощью ключей
                itemOpen.Image = imageList.Images["Open"];
                itemSaveAs.Image = imageList.Images["Save"];
                itemCut.Image = imageList.Images["Cut"];
                itemCopy.Image = imageList.Images["Copy"];
                itemPaste.Image = imageList.Images["Paste"];
                itemDelete.Image = imageList.Images["Delete"];
            }
  • Вставьте вызовы функций создания меню и загрузки изображений в конструктор класса Form4 и добавьте инструкции подключения библиотечных пространств имен
  • using System.Resources;
  • using System.Collections;
  • Общий код файла Form4.cs будет таким

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
        
    namespace StripControls
    {
        using System.Resources;
        using System.Collections;
        
        public partial class Form4 : Form
        {
            public Form4()
            {
                InitializeComponent();
        
                CreateMenu();// Создаем меню
        
                // Загружаем в меню пиктограммы опций
                LoadImage();
            }
        
            // Видимые в методах поля-ссылки на элементы меню
            MenuStrip menu;
            ToolStripMenuItem itemFile;
            ToolStripMenuItem itemOpen;
            ToolStripMenuItem itemSaveAs;
            ToolStripMenuItem itemExit;
        
            ToolStripMenuItem itemEdit;
            ToolStripMenuItem itemCut;
            ToolStripMenuItem itemCopy;
            ToolStripMenuItem itemPaste;
            ToolStripMenuItem itemDelete;
        
            void CreateMenu()
            {
                // Создание меню
                menu = new MenuStrip();
                menu.Parent = this;
        
                // Формирование верхней линейки меню
                itemFile = new ToolStripMenuItem("File");
                menu.Items.Add(itemFile);
                itemEdit = new ToolStripMenuItem("Edit");
                menu.Items.Add(itemEdit);
        
                // Сборка узла File
                itemFile.DropDownItems.Add(itemOpen = new ToolStripMenuItem("Open"));
                itemFile.DropDownItems.Add(itemSaveAs = new ToolStripMenuItem("Save As..."));
                itemFile.DropDownItems.Add(new ToolStripSeparator());
                itemFile.DropDownItems.Add(itemExit = new ToolStripMenuItem("Exit"));
        
                // Сборка узла Edit
                itemEdit.DropDownItems.Add(itemCut = new ToolStripMenuItem("Cut"));
                itemEdit.DropDownItems.Add(itemCopy = new ToolStripMenuItem("Copy"));
                itemEdit.DropDownItems.Add(itemPaste = new ToolStripMenuItem("Paste"));
                itemEdit.DropDownItems.Add(itemDelete = new ToolStripMenuItem("Delete"));
            }
        
            // Создаем список для хранения изображений 
            ImageList imageList = new ImageList();
        
            void LoadImage()
            {
                // Присоединяем ранее сформированные ресурсы из файла ресурсов 
                ResourceReader rr = new ResourceReader("ResXForm4.resources");
                IDictionaryEnumerator en = rr.GetEnumerator();
                while (en.MoveNext())
                {
                    switch (en.Key.ToString())
                    {
                        default:
                            imageList.Images.Add(en.Key.ToString(), (Image)en.Value);
                            break;
                        case "icoTitle":
                            this.Icon = (Icon)en.Value;     // Иконка окна
                            break;
                        case "strTitle":
                            this.Text = (String)en.Value;   // Заголовок окна
                            break;
                    }
                }
                rr.Close(); // Закрыли файл ресурсов
        
                // Определяем цвет фона, воспринимаемый прозрачным
                imageList.TransparentColor = Color.Magenta;
        
                // Закрепляем пиктограммы из списка за командами меню с помощью ключей
                itemOpen.Image = imageList.Images["Open"];
                itemSaveAs.Image = imageList.Images["Save"];
                itemCut.Image = imageList.Images["Cut"];
                itemCopy.Image = imageList.Images["Copy"];
                itemPaste.Image = imageList.Images["Paste"];
                itemDelete.Image = imageList.Images["Delete"];
            }
        }
    }
  • Запустите на выполнение Упражнение 4 и убедитесь, что пиктограммы нормально загружаются из файла ресурсов через список изображений, а также успешно подгружаются и другие необходимые приложению ресурсы (пиктограмма и строка заголовка окна)
  • Упражнение 5. Контекстное меню

    Контекстное меню вызывается по щелчку правой кнопки мыши на соответствующем элементе управления и зависит от конкретного состояния этого элемента. Контекстное меню представлено классом System.Windows.Forms.ContextMenuStrip. Любой визуальный элемент управления, производный от класса Control, наследует общедоступное виртуальное свойство ContextMenuStrip, к которому можно привязать объект контекстного меню.

    Элементами контекстного меню ContextMenuStrip, как и в случае с основным меню, порожденным классом MenuStrip, являются объекты класса ToolStripMenuItem. Эти объекты добавляются в коллекцию Items, являющуюся свойством класса контекстного меню ContextMenuStrip. Каждый из объектов-элементов контекстного меню имеет свойство-коллекцию DropDownItems, в которую помещают элементы вложенного меню.

    Мы можем заготовить несколько различных объектов контекстного меню и привязать каждый из них к определенному элементу управления. Но эти же несколько объектов контекстного меню можно поочередно привязывать к одному и тому же элементу управления в динамическом режиме в зависимости от текущего состояния контекста.

    Рассмотрим Упражнение 5, в котором будем использовать объекты контекстного меню. Мы создадим несколько контекстных меню, часть динамическим способом, часть декларативным способом. Объекты контекстных меню будем присоединять к различным элементам управления через свойство этих элементов ContextMenuStrip.

    Создание формы для Упражнения 5

  • В панели Solution Explorer выделите корневой узел проекта StripControls и командой меню оболочки Project/Add Windows Form добавьте к проекту файл Form5.cs
  • Откройте файл Start.cs в режиме View Designer, выделите объект listBoxChoice и добавьте через панель Properties в свойство Items ссылку на Упражнение 5
  • Откройте файл Start.cs в режиме View Em, найдите обработчик listBoxChoice_SelectedIndexChanged() и добавьте в него код запуска формы Form5
  • Form frm;   // Объявили поле
            private void listBoxChoice_SelectedIndexChanged(object sender, EventArgs e)
            {
                // Привязали номер упражнения к индексу списка
                ListBox list = (ListBox)sender;
                string formName = "Form" + (list.SelectedIndex + 1);
        
                ///////////////////////////////////////////////////
                // Следующий код взят из лабораторной работы №47
                ///////////////////////////////////////////////////
                // Читаем коллекцию запущенных форм
                FormCollection fc = Application.OpenForms;
                bool IsRun = false;
                // Перебираем коллекцию
                foreach (Form form in fc)
                {
                    // Имя очередной запущенной формы сравниваем с закрепленным за кнопкой
                    if (form.Name == formName)
                    {
                        IsRun = true;   // Нашли, что форма запущена, поднимаем флаг
                        frm = form;     // Сохраняем ссылку на запущенную форму для фокуса
                        break;          // Выходим из цикла
                    }
                }
        
                // Если форма не запущена - запускаем
                if (!IsRun)
                {
                    switch (formName)
                    {
                        case "Form1":
                            frm = new Form1();
                            break;
                        case "Form2":
                            frm = new Form2();
                            break;
                        case "Form3":
                            frm = new Form3();
                            break;
                        case "Form4":
                            frm = new Form4();
                            break;
                        case "Form5":
                            frm = new Form5();
                            break;
                    }
        
                    this.AddOwnedForm(frm);     // Сделать новую frm подчиненной Start
                    // frm.Owner = this;        // Алтернативный способ назначить владельца
                    frm.ShowInTaskbar = false;  // Не отображать метку окна в панели задач
                    frm.Show();                 // Показать новую форму
                }
                else
                    frm.Focus();                // Передать фокус запущенной форме        
            }
  • Поместите на форму Form5 компонент SplitContainer, присвойте его экземпляру имя splitContainer и установите свойства: Orientation= Horizontal, BorderStyle=Fixed3D, Dock=Fill
  • Создание контекстного меню динамическим способом

  • Откройте файл Form5.cs в режиме View Code и откорректируйте код, чтобы он стал таким
  • using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Reflection;
        
    namespace StripControls
    {
        public partial class Form5 : Form
        {
            // Поле для сохранения выбранного элемента меню
            ToolStripMenuItem itemChecked = new ToolStripMenuItem();
        
            // Конструктор
            public Form5()
            {
                InitializeComponent();
        
                splitContainer.Panel1.BackColor = Color.White;// Начальный цвет верхней панели
        
                // Разделение цветов на алфавитные группы, представленные подменю
                //             A, B, C, D, E, F, G, H, I, J, K, L, M,           
                int[] iMenu ={ 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4,  
                    5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6 };
                //  N, O, P, Q, R, S, T, U, V, W, X, Y, Z
        
                // Создание контекстного меню и присоединение к объекту формы
                System.Windows.Forms.ContextMenuStrip menu =
                    new System.Windows.Forms.ContextMenuStrip();
                // Будет вызываться для верхней панели
                splitContainer.Panel1.ContextMenuStrip = menu;
        
                // Формируем пункты подменю в контекстном меню
                for (int i = 0; i <= 6; i++)
                {
                    // Индекс первого вхождения образца соответствует коду ASCII для буквы 
                    char first = Convert.ToChar(Array.IndexOf(iMenu, i) + 'A');   // Позиция в массиве
                    // Индекс последнего вхождения образца соответствует ASCII латинской буквы 
                    char last = Convert.ToChar(Array.LastIndexOf(iMenu, i) + 'A');// Позиция в массиве
        
                    // Создали, настроили и добавили в коллекцию элемент меню
                    ToolStripMenuItem item = new ToolStripMenuItem();
                    item.Text = String.Format("Цвета от {0} до {1}", first, last);
                    menu.Items.Add(item);
                    // Включили в подменю столбец с флажками в 
                    // дополнение к столбцу с изображениями
                    ((ToolStripDropDownMenu)item.DropDown).ShowCheckMargin = true;
                }
        
                // Применяем рефлексию к структуре, представляющей цвета
                PropertyInfo[] api = typeof(Color).GetProperties();
                // Перебор цветов
                foreach (PropertyInfo pi in api)
                {
                    if (pi.CanRead  pi.PropertyType == typeof(Color)
                         pi.Name!="Transparent")
                    {
                        // Извлекаем очередной цвет
                        Color color = (Color)pi.GetValue(null, null);
        
                        // Создаем очередной элемент подменю
                        ToolStripMenuItem item = new ToolStripMenuItem();
                        // По букве цвета определяем подменю, в котором разместим цвет
                        int i = iMenu[color.Name[0] - 'A'];// Любой букве - своя позиция
                        ((ToolStripMenuItem)menu.Items[i]).DropDownItems.Add(item);
        
                        // Продолжаем настраивать команду
                        item.Text = InsertSpaceOut(color.Name);// Пробелы перед заглавными буквами цвета
                        item.Name = color.Name;// Сохраняем название цвета для обработчика ColorOnClick
                        item.Image = CreateBitmap(color);// Тонкой ссылке присваиваем толстый объект
                        item.Click += new EventHandler(ColorOnClick);// Регистрация обработчика
                        if (color.Equals(this.BackColor))// Отметить флажком текущий цвет и сохранить ссылку
                            (itemChecked = item).Checked = true;
                    }
                }
            }
        
            void ColorOnClick(object sender, EventArgs e)
            {
                // Для выбранного устанавливаем новый флажок
                // и меняем цвет клиентской области формы
                ToolStripMenuItem item = (ToolStripMenuItem)sender;
                itemChecked.Checked = false;// Сбрасываем старое
                (itemChecked = item).Checked = true;// Устанавливаем флажок и запоминаем элемент
                splitContainer.Panel1.BackColor = Color.FromName(item.Name);// Красим верхнюю панель
            }
        
            Bitmap CreateBitmap(Color color)
            {
                // Генерируем изображение в виде цветного прямоугольника
                Bitmap bm = new Bitmap(16, 16);
                Graphics gr = Graphics.FromImage(bm);
                gr.FillRectangle(new SolidBrush(color), 0, 0, 16, 16);
                gr.Dispose();   // Освобождаем контекст устройства
                return bm;
            }
        
            string InsertSpaceOut(string str)
            {
                // Вставляем пробелы перед заглавными буквами названия цвета
                for (int i = str.Length - 1; i > 1; i--)
                    if (Char.IsUpper(str[i]))
                        str = str.Insert(i, " ");
                return str;
            }
        }
    }
  • Запустите приложение и для верхней панели сплиттера испытайте работу контекстного меню
  • Должно получиться нечто, похожее показанному на рисунке с изменением выбранного цвета фона на верхней панели сплиттера

    Создание линейки контекстных меню декларативным способом

    Теперь посмотрим, как присоединяются поочередно контекстные меню к одному и тому же элементу управления в зависимости от некоторых условий.

  • Поместите на нижнюю панель объекта splitContainer еще один экземпляр компонента SplitContainer и оставьте его имя по умолчанию splitContainer1
  • Установите настройки объекта splitContainer1: Dock=Fill, BorderStyle=Fixed3D
  • Поместите на левую нижнюю панель splitContainer1.Panel1 три экземпляра компонента радиокнопки RadioButton с именами radioButton1, radioButton2, radioButton3 и измените их свойство Text на ContextMenu1, ContextMenu2, ContextMenu1
  • Установите для правой нижней панели splitContainer1.Panel2 свойство BackColor=Lime
  • После проведения указанных действий интерфейс формы Form5 должен стать таким

  • Создайте три экземпляра компонента ContextMenuStrip с именами по умолчанию: contextMenuStrip1, contextMenuStrip2, contextMenuStrip3
  • Последовательно выделите каждый объект из только что добавленных контекстных меню и создайте в нем декларативно заготовки элементов MenuItem
  • В результате должны получиться три объекта-заготовки контекстных меню со следующим интерфейсом

    contextMenuStrip1 contextMenuStrip2 contextMenuStrip3
  • Выделите на форме Form5 все три радиокнопки одновременно и через панель Properties в режиме Events для события CheckedChanged создайте обработчик с именем radioButton_CheckedChanged(), который заполните следующим кодом
  • private void radioButton_CheckedChanged(object sender, EventArgs e)
            {
                    if (radioButton1.Checked)
                        splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip1;
                    else if (radioButton2.Checked)
                        splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip2;
                    else
                        splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip3;
            }

    При смене активной радиокнопки в обработчике происходит присоединение к правой нижней панели соответствующего объекта контекстного меню. Вызов контекстного меню выполняется щелчком правой кнопки мыши на правой нижней панели, представленной элементом splitContainer1.Panel2. Для обеспечения подобного механизма работы при первом вызове контекстного меню необходимо задействовать для этого элемента событие BindingContextChanged.

  • Выделите элемент splitContainer1.Panel2 и через панель Properties в режиме Events создайте для события BindingContextChanged обработчик splitContainer1_Panel2_BindingContextChanged(), который заполните так
  • private void splitContainer1_Panel2_BindingContextChanged(object sender, EventArgs e)
            {
                if (radioButton1.Checked)
                    splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip1;
                else if (radioButton2.Checked)
                    splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip2;
                else
                    splitContainer1.Panel2.ContextMenuStrip = contextMenuStrip3;
            }
  • Запустите приложение и убедитесь в работоспособности всех 4-х контекстных меню, первое из которых создано динамически, а три остальных - декларативно
  • Вернуться к учебному плану