Опубликован: 05.08.2010 | Уровень: специалист | Доступ: свободно
Самостоятельная работа 11:

Графика в WPF

  • Запустите приложение - первое окно будет выглядеть так

Обратите внимание, что окно не имеет системных кнопок минимизации и максимизации, а только кнопку закрытия. Это поведение обеспечивается настройкой в разметке окна с помощью атрибута ResizeMode="NoResize".

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

  • Добавьте к текущему (выделенному в панели Solution Explorer ) проекту WpfApp3 командой Project/Add Window новое окно WPF с именем Window2.xaml
  • Наполните файл Window2.xaml следующей разметкой
<Window x:Class="WpfApp3.Window2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Дочернее окно Window2: Работа объекта ImageDrawing через разметку"
    SizeToContent="WidthAndHeight"
    ResizeMode="NoResize"
    Background="Green"
        >
    <Border BorderBrush="White" BorderThickness="2" 
            Margin="10"
            Background="White"
        >
        <Image Stretch="None">
            <Image.Source>
                <DrawingImage>
                    <DrawingImage.Drawing>
                        <DrawingGroup>
    
                            <!-- Рис.1 - левый верхний (x, y, width, height) -->
                            <ImageDrawing Rect="0,0,348,232" ImageSource="Images\market 031.jpg"/>
    
                            <!-- Рис.2 - правый верхний (x, y, width, height) -->
                            <ImageDrawing Rect="350,0,348,232" ImageSource="Images\market 034.jpg"/>
    
                            <!-- Рис.3 - левый нижний (x, y, width, height) -->
                            <ImageDrawing Rect="0,234,348,232" ImageSource="Images\market 039.jpg"/>
    
                            <!-- Рис.4 - правый нижний (x, y, width, height) -->
                            <ImageDrawing Rect="350,234,348,232" ImageSource="Images\market 040.jpg"/>
    
                        </DrawingGroup>
                    </DrawingImage.Drawing>
                </DrawingImage>
            </Image.Source>
        </Image>
    </Border>
</Window>
  • Дополните конструктор класса Window2 следующим кодом
public Window2()
{
    // Инициализация разметочной части
    InitializeComponent();

    // Корректировка заголовка окна
    this.Title += "=\"Так голодают буржуины!\"";

    // Дочернее окно не отображать в панели задач ОС 
    this.ShowInTaskbar = false;
}
  • Заполните обработчики щелчка и контекстного меню в классе Window1 следующим кодом
Window wnd2;
private void Show_Window2(object sender, MouseButtonEventArgs e)
{
    wnd2 = new Window2();
    wnd2.Show();
}

private void Create_Window2(object sender, RoutedEventArgs e)
{
    wnd2 = new Window2();
    wnd2.Show();
}
  • Запустите приложение и вызовите окно Window2 через окно Window1, как напоминает всплывающая подсказка
  • Получится следующий результат

Здесь есть несколько существенных недостатков, которые следует устранить:

  1. Второе окно можно создавать во многих экземплярах
  2. Первое окно со вторым никак не связано и каждое из них закрывается самостоятельно
  3. Пиктограмма вторичных окон не отображается в панели задач (мы специально ввели в конструктор окна настройку this.ShowInTaskbar = false; )

Во первых, зачем пользователю много экземпляров одного и того же окна. Во вторых, хоть окна не имеют системных кнопок минимизации, все равно их можно минимизировать все сразу через операционную систему командой "Показать рабочий стол". А в этом случае вновь сделать видимыми вторичные окна не удасться и придется их закрывать через Диспетчер задач (либо через оболочку Visual Studio останавливать процесс - работу приложения).

Для устранения указанных недостатков нужно выполнить следующее:

  1. Назначить первое окно главным, чтобы при его закрытии закрывалось приложение в целом
  2. Блокировать создание вторичных окон, если одно из них уже существует, и попутно активировать вторичное окно, если оно заслонено другими окнами или скрыто
  • Для назначения окна Window1 главным добавьте в его конструктор следующий код
public Window1()
{
    // Инициализация разметочной части
    InitializeComponent();

    // Корректировка заголовка окна
    this.Title += "=\"Так голодают буржуины!\"";

    // Всплывающая подсказка
    this.ToolTip = "Вызывайте дочернее окно\n"
        + "двойным щелчком мыши\n"
        + "или контекстным меню...";

    // Сделать главным окном приложения
    Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
    Application.Current.MainWindow = this;
}
  • Запустите приложение и убедитесь, что теперь все дочерние окна при закрытии первичного окна тоже закрываются вместе с приложением

Для блокирования повторного создания дочерних окон применим два способа:

  1. В первом способе обернем в классе Window1 ссылку wnd2 на дочернее окно в свойство Wnd2 и будем ее обнулять в обработчике Closed окна Window2. В первом окне перед созданием дочернего будем эту ссылку проверять - если ненулевая, значит окно существует и новое создавать нельзя
  2. Второй способ намного проще, так как не требует жесткой зависимости кода между окнами введением дополнительного общего поля или свойства. Можно просто воспользоваться свойством Application.Current.Windows типа WindowCollection приложения и в первом окне перед созданием дочернего проверять, отсутствует ли в коллекции существующих окон приложения интересующее нас окно. Любое созданное окно приложения сразу заносится в эту коллекцию, а при закрытии между событиями Closing и Closed немедленно удаляется из нее. Единственное, что потребуется определить в дочернем окне, так это свойство Name, по которому мы будем искать это окно в коллекции приложения

Итак, реализуем сказанное...

Способ 1

  • В классе Window1 найдите объявление поля wnd2 (строка: Window wnd2; ), щелкните на поле wnd2 правой кнопкой мыши и выполните команду контекстного меню Refactor/Encapsulate Field

Появится окно Encapsulate Field, предлагающее обернуть поле в общедоступное свойство с именем Wnd2


  • Скажите OK и оболочка создаст следующий код свойства
public static Window Wnd2
{
    get { return Window1.wnd2; }
    set { Window1.wnd2 = value; }
}

Аксессор get нам не нужен, поскольку в клиенте Window2 при закрытии окна мы будем поле wnd2 только обнулять через свойство Wnd2, поэтому get следует удалить.

  • Внесите в класс Window1 следующий окончательный код реализации первого способа
// Предотвращение повторного открытия окна: Способ 1
static Window wnd2;
public static Window Wnd2
{
    set { Window1.wnd2 = value; }
}
// Обработчик двойного щелчка
private void Show_Window2(object sender, MouseButtonEventArgs e)
{
    if (wnd2 == null)
    {
        wnd2 = new Window2();
        wnd2.Show();
    }
    else
        wnd2.Activate();// Сдвинуть на передний план
}
  • Модифицируйте класс Window2 следующим образом
public partial class Window2 : Window
{
    public Window2()
    {
        // Инициализация разметочной части
        InitializeComponent();

        // Корректировка заголовка окна
        this.Title += "=\"Так голодают буржуины!\"";

        // Дочернее окно не отображать в панели задач ОС 
        this.ShowInTaskbar = false;

        // Регистрация обработчика
        this.Closed += new EventHandler(Window2_Closed);
    }

    private void Window2_Closed(object sender, EventArgs e)
    {
        Window1.Wnd2 = null;        // Для предотвращения повторного запуска
        GC.WaitForFullGCComplete(); // Ждать завершения сборки мусора
        GC.Collect();               // Начать сборку мусора
    }
}

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

  • Запустите приложение и убедитесь, что обработчик создания дочернего окна по двойному щелчку теперь работает правильно

Способ 2

  • В разметку файла Window2.xaml добавьте определение свойства Name окна Window2 для его идентификации в коллекции приложения
<Window x:Class="WpfApp3.Window2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Name="Window_2"
    Title="Дочернее окно Window2: Работа объекта ImageDrawing через разметку"
    SizeToContent="WidthAndHeight"
    ResizeMode="NoResize"
    Background="Green"
        >
    ...................................
</Window>
  • Модифицируйте обработчик контекстного меню в файле Window1.xaml.cs следующим образом
// Предотвращение повторного открытия окна: Способ 2
// Обработчик контекстного меню
private void Create_Window2(object sender, RoutedEventArgs e)
{
    bool windowExists = false;
    foreach (Window window in Application.Current.Windows)
    {
        if (window.Name == "Window_2")
        {
            windowExists = true;
            window.Activate();// Сдвинуть на передний план
            break;
        }
    }

    if (!windowExists)
    {
        wnd2 = new Window2();
        wnd2.Show();
    }
}
  • Запустите приложение и убедитесь, что и этот обработчик блокирования повторного запуска теперь тоже работает правильно
  • Разберитесь с кодом

В будущих упражнениях мы продолжим знакомство с графическими возможностями WPF.


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

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

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

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

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

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

Юрий Макушин
Юрий Макушин
Россия, Москва, РЭА им. Плеханова, 2004