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

Основы WPF

Аннотация: В данной лабораторной работе на ряде упражнений мы продолжем знакомство с различными сторонами WPF.

Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.

Упражнение 1. Кисти в WPF

WPF имеет возможность очень тонко настраивать внешний вид объектов. Внешний вид объектов зависит от свойств, которые задаются как при помощи прямых настроек, так и при помощи кистей (Brushes). Список свойств включает следующие:

  • прозрачность (Opacity)
  • видимость (Visibility)
  • заливка (Fill)
  • штриховка (Stroke)
  • фон (Background)
  • передний план (Foreground)
  • граница (BorderBrush)
  • маска прозрачности (Opacity masks).

Кисти используются для задания внешнего вида объектов и могут быть следующих типов:

  • одноцветная кисть (Solid color brush)
  • линейный градиент (Linear gradient brush)
  • радиальный градиент (Radial gradient brush)
  • кисть растрового изображения (Image brush)
  • кисть векторного изображения (Drawing brush)
  • кисть визуальных эффектов (Visual brush).

Кисти можно конвертировать в ресурсы и многократно применять к различным объектам.

В WPF есть средства достигать специальных растровых эффектов (Bitmap effects):

  • размытие (Blur)
  • внешнее свечение (Outer glow)
  • тень (Drop shadow)
  • фаска (Bevel)
  • рельеф (Emboss)

Создание заготовки приложения

  • Создайте новый пустой проект ( Empty Project ) с именем WpfApp1 в новом решении WpfApp. Проследите, чтобы обязательно использовалась версия библиотеки не ниже .NET Framework 3.0

  • Вызовите контекстное меню проекта и через команду Properties установите во вкладке Application параметр Output type в значение Windows Application
  • Добавьте к узлу References проекта ссылки на следующие библиотеки .NET Framework, составляющие основу платформы WPF
    • PresentationCore
    • PresentationFramework
    • WindowsBase
    • System - эта общая библиотека для запуска приложения


  • Добавьте к проекту новый файл с именем CreateBrushes.cs

  • Заполните файл следующим кодом
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Controls;
using System.Reflection;
    
namespace WpfApp1
{
    class CreateBrushes : NavigationWindow
    {
        // Точка входа
        [STAThread] // Атрибут для однопоточного приложения
        public static void Main()
        {
            Application app = new Application();
            app.Run(new CreateBrushes());
        }
    
        // Конструктор
        public CreateBrushes()
        {
            this.Width = 300;
            this.Height = 300;
            this.Title = "Каркас приложения Упражнения 1";
            //this.ShowsNavigationUI = false;// Скрыть навигационный интерфейс
        }
    }
}

Мы решили построить многостраничное приложение для многих примеров упражнения и содали каркас для страниц содержимого с навигационным узлом. Навигационный узел можно отключить через свойство ShowsNavigationUI. Мы создали каркас с нуля и пока будем использовать только чистый код C#, не применяя разметку XAML. Страничный каркас создается классом NavigationWindow, производным от класса Window. Мы расширяем библиотечный класс NavigationWindow пользовательским классом CreateBrushes, следовательно все наследуемые им от базового класса Window члены будут доступны в коде и нашего класса-расширения.

  • Запустите приложение - появится мертвый каркас


Страница1. Использование однородной кисти SolidColorBrush

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

Начнем с создания первой страницы, на которой по всей клиентской области будет расположена кнопка. Кнопку будем закрашивать однородным цветом градаций серого, в зависимости от удаления курсора от центра кнопки. Страницу построим вручную как класс-расширение на базе библиотечного класса Page.

  • Дополните файл CreateBrushes.cs кодом создания страницы Page1
namespace WpfApp1
{
    // Класс-расширение страницы Page1
    class Page1 : Page
    {
        // Создаем и инициализируем поле кисти белым цветом
        SolidColorBrush brush = new SolidColorBrush(Colors.White);
        Button btnPage1 = new Button();
    
        public Page1()
        {
            this.WindowTitle = "Page1: Кисть SolidColorBrush";
    
            // Создали кнопку
            btnPage1.Content = "Кнопка";
            btnPage1.Background = brush;
            // Зарегистрировали обработчики
            btnPage1.Click += new RoutedEventHandler(btnPage1_Click);
            btnPage1.MouseMove += new MouseEventHandler(btnPage1_MouseMove);
            this.Content = btnPage1;    // Поместили кнопку в страницу
        }
    
        // Меняет цвет фона кнопки на Page1
        void btnPage1_MouseMove(object sender, MouseEventArgs e)
        {
            // Чистая ширина клиентской области без рамки окна
            double width = btnPage1.ActualWidth;
            // Чистая высота клиентской области без рамки и заголовка окна
            double height = btnPage1.ActualHeight;
    
            Point ptMouse = e.GetPosition(btnPage1);// Точка курсора на клиентской области окна
            Point ptCenter = new Point(width / 2, height / 2);// Центр клиентской области
            Vector vectMouse = ptMouse - ptCenter;// Отклонение курсора от центра
            double angle = Math.Atan2(vectMouse.Y, vectMouse.X);// Угол нахождения курсора
            Vector vectEllipse = new Vector(width / 2 * Math.Cos(angle),
                height / 2 * Math.Sin(angle));// Вписанный эллипс
    
            // Изоклина (в центре кнопки черная), округляется до одного байта 
            Byte byLevel = (byte)(255 * (Math.Min(1, vectMouse.Length / vectEllipse.Length)));
            Color color = brush.Color;// Привязываем к полю
            color.R = color.G = color.B = byLevel;// Цвета, пропорциональные изоклине
            brush.Color = color;// Меняем цвет фона окна изоклиной от равномерного черного цвета
        }
    
        // Переход на следующую страницу
        void btnPage1_Click(object sender, RoutedEventArgs e)
        {
        }
    }
}
  • Добавьте код создания объекта начальной страницы в конструктор класса каркаса приложения
namespace WpfApp1
{
    class CreateBrushes : NavigationWindow
    {
        // Точка входа
        [STAThread] // Атрибут для однопоточного приложения
        public static void Main()
        {
            Application app = new Application();
            app.Run(new CreateBrushes());
        }
    
        // Конструктор
        public CreateBrushes()
        {
            this.Width = 300;
            this.Height = 300;
            //this.Title = "Каркас приложения Упражнения 1";
            //this.ShowsNavigationUI = false;// Скрыть навигационный интерфейс
    
            // Создали начальную страницу
            Page1 page1 = new Page1();
            this.Content = page1;   // Поместили страницу в каркас
        }
    }
}
  • Запустите приложение и вы увидите первую страницу внутри каркаса, на которой расположена кнопка, меняющая градации серого цвета при движении над ней курсора мыши по направлению к центру кнопки


Кнопка нажимается, но ничего не делает, только меняет цвет.

Страница 2. Использование цветов класса Brushes

  • Создайте новую страницу Page2, на которую пользователь перейдет по щелчку на кнопке
namespace WpfApp1
{
    // Класс-расширение страницы Page2
    class Page2 : Page
    {
        int index = 0;// Номер цвета
        PropertyInfo[] props;// Массив свойств
    
        public Page2()
        {
            // Применяем рефлексию для чтения свойств класса Brushes
            props = typeof(Brushes).GetProperties(
                BindingFlags.Public | BindingFlags.Static);
    
            // Компоновочная панель
            StackPanel stackPanel = new StackPanel();// Создаем
            this.Content = stackPanel;// Присоединяем к странице
    
            Button btn = new Button();
            btn.Name = "ButtonNextColor";
            btn.Content = "NextColor >";
            btn.Click += new RoutedEventHandler(btn_Click);
            stackPanel.Children.Add(btn);// Добавляем в панель
    
            btn = new Button();
            btn.Content = "< PreviousColor";
            btn.Click += new RoutedEventHandler(btn_Click);
            stackPanel.Children.Add(btn);
    
            btn = new Button();
            btn.Content = "Next Page3";
            btn.Click += new RoutedEventHandler(btnPage2_Click);
            stackPanel.Children.Add(btn);
    
            // Возбуждается при каждом отображении страницы
            this.Loaded += new RoutedEventHandler(Page2_Loaded);
        }
    
        void Page2_Loaded(object sender, RoutedEventArgs e)
        {
            // Вариант
            //this.NavigationService.LoadCompleted += NavigationService_LoadCompleted;
            SetTitleAndBackground();
        }
    
        void NavigationService_LoadCompleted(object sender, NavigationEventArgs e)
        {
            // Вариант
            //SetTitleAndBackground();
        }
    
        // Обработчики кнопок смены заголовка и цвета страницы
        void btn_Click(object sender, RoutedEventArgs e)
        {
            // Распознаем кнопку по имени и корректируем индекс
            if (((Button)sender).Name == "ButtonNextColor")
                index += 1;
            else
                index += props.Length - 1;
    
            index %= props.Length;// Деление по модулю
            SetTitleAndBackground();
        }
    
        // Установка заголовка и цвета фона страницы
        void SetTitleAndBackground()
        {
            this.WindowTitle = "Page2: Имя цвета кисти - " + props[index].Name;
            this.Background = (Brush)props[index].GetValue(null, null);
        }
    
        // Переход на следующую страницу
        void btnPage2_Click(object sender, RoutedEventArgs e)
        {
        }
    }
}

Мы разработали новый класс, определяющий функциональность страницы Page2. Осталось добавить в обработчик щелчка кнопки первой страницы код для перехода на эту новую страницу.

  • Добавьте в обработчик btnPage1_Click() класса Page1 следующий код
// Переход на следующую страницу
        Page2 page2;
        void btnPage1_Click(object sender, RoutedEventArgs e)
        {
            if (!this.NavigationService.CanGoForward)
                page2 = new Page2();// Создаем только один раз 
            this.NavigationService.Navigate(page2);
        }

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

  • Запустите приложение и испытайте работу класса Page2 в составе навигационного каркаса


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

При выполнении в лабораторной работе упражнения №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