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

Ресурсы в WPF

< Самостоятельная работа 12 || Самостоятельная работа 13: 123456 || Лекция 1 >

Вначале мы создали 6 объектов с настройками и разместили их в коллекции Window.Resources ресурсов окна. Два объекта TextBlock будут отображать заголовки. Два объекта SolidColorBrush будут использованы для определения цвета надписей на кнопках. Два объекта ImageBrush будут задавать фон кнопок. В настройках объектов, помещенных в ресурсы, в качестве обязательного параметра должен присутствовать ключ x:Key, по которому эти ресурсы будут извлечены элементами интерфейса окна.

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

Обязательным при подключении ресурсов к элементу является указание способа их применения с помощью ключевого слова DynamicResource или StaticResource. Отсюда и название - динамические или статические ресурсы. Если ресурс в процедурном коде полностью не подменяется новым объектом, а остается постоянным или только редактируется (адрес объекта не меняется), то каким бы способом он не был подключен к использующим его элементам, внешне эти элементы ведут себя одинаково.

Но если помещенный в ресурс объект в процедурном коде заменить на новый (адрес объекта изменится), то элемент со статическим способом подключения этого не заметит и будет использовать старую копию ресурса. Элемент же с динамическим способом подключения это заметит и сразу использует новый ресурс. В WPF операция смены ресурса преобразуется в вызов метода InvalidateVisual(), означающий, что элемент должен быть немедленно перерисован вызовом виртуального метода OnRender().

Итак, если в коллекции ресурсов полностью не менять адрес объекта ресурса на новый, то элементы с разными способами подключения ведут себя одинаково: замечают все изменения в существующем ресурсе. А объекты ресурсов, соответственно, исправно посылают элементам уведомления о своих изменениях. Обычно необходимость в полной смене ресурса встречается редко, поэтому лучше использовать статический способ подключения. Тем более, что динамический ресурс требует повышенных накладных расходов при обработке.

Для разнообразия, заголовки на форме окна мы тоже определили как ресурсы TextBlock. Но чтобы их присоединить к интерфейсу и отобразить, пришлось использовать элементы Label с универсальным свойством Content типа Object.

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

  • Заполните файл Window1.xaml.cs следующим процедурным кодом
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
    
namespace UseResource
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            // Инициализация интерфейсных элементов
            InitializeComponent();
    
            // Создаем и добавляем программно объекты ресурсов 
            // для будущей замены декларативных ресурсов целиком
            //
            // ... alternateColor
            SolidColorBrush solidColorBrush = new SolidColorBrush();
            solidColorBrush.Color = Colors.Blue;
            this.Resources.Add("alternateColor", solidColorBrush);
            //
            // ... alternateImage
            ImageBrush imageBrush = new ImageBrush();
            imageBrush.ImageSource = new BitmapImage(
                new Uri(@"Images\10440.jpg", UriKind.Relative)
                );
            imageBrush.TileMode = TileMode.Tile;
            imageBrush.ViewportUnits = BrushMappingMode.Absolute;// Не путать с ViewboxUnits
            imageBrush.Viewport = new Rect(0, 0, 32, 32);
            imageBrush.Opacity = 0.5D;// Можно и без D, преобразует неявно из float в double
            this.Resources.Add("alternateImage", imageBrush);
    
            // Регистрируем часть обработчиков программно
            btn1.Click += btn1_Click;
            btn2.Click += btn2_Click;
    
            /*
            // Эти ресурсы не будем сохранять, потому что мы их будем только модифицировать
            resourceDictionary.Add("ForegroundBrush1", this.Resources["ForegroundBrush1"]);
            resourceDictionary.Add("BackgroundBrush1", this.Resources["BackgroundBrush1"]);
            //*/
    
            // Заполняем словарь ресурсов: дублируем ссылки на ресурсы 
            resourceDictionary.Add("originColor", this.Resources["ForegroundBrush2"]);
            resourceDictionary.Add("originImage", this.Resources["BackgroundBrush2"]);
        }
        // Поле-ссылка на объект, может объявляться в любом месте как член класса
        ResourceDictionary resourceDictionary = new ResourceDictionary();
    
        // Просто модифицируем объект ресурса
        bool changeResourceFlag1 = true;
        void btn1_Click(object sender, RoutedEventArgs e)
        {
            // Извлекаем ресурсы из словаря окна и приводим к типу двумя вариантами
            SolidColorBrush colorRes = (SolidColorBrush)this.Resources["ForegroundBrush1"];
            ImageBrush imageRes = this.Resources["BackgroundBrush1"] as ImageBrush; 
    
            if (changeResourceFlag1)// Редактируем существующий объект ресурса
            {
                colorRes.Color = Colors.Red;
                imageRes.Viewport = new Rect(0, 0, 16, 16);
            }
            else                    // Возвращаем к прежнему
            {
                colorRes.Color = Colors.Blue;
                imageRes.Viewport = new Rect(0, 0, 10, 10);
                /*
                // Восстанавливать из словаря нельзя - ссылаются на один и тот же объект
                colorRes = resourceDictionary["ForegroundBrush1"];
                imageRes = resourceDictionary["BackgroundBrush1"];
                //*/
            }
            changeResourceFlag1 = !changeResourceFlag1;// Готовим другой вариант
        }
    
        // Просто модифицируем объект ресурса
        void btn2_Click(object sender, RoutedEventArgs e)
        {
            // Ищем ресурс по ключу, начиная с текущего элемента к корню дерева
            // Метод TryFindResource защищать не нужно, потому что при неудаче он 
            // не генерирует исключение, а просто возвращает нулевую ссылку
            SolidColorBrush colorRes = ((FrameworkElement)sender).
                TryFindResource("ForegroundBrush1") as SolidColorBrush;
            ImageBrush imageRes = (ImageBrush)((FrameworkElement)sender).
                TryFindResource("BackgroundBrush1");
    
            if (colorRes == null || imageRes == null)// Ресурс не найден
                return;
    
            if (changeResourceFlag1)// Редактируем существующий объект ресурса
            {
                colorRes.Color = Colors.Red;
                imageRes.Viewport = new Rect(0, 0, 16, 16);
            }
            else                    // Возвращаем к прежнему
            {
                colorRes.Color = Colors.Blue;
                imageRes.Viewport = new Rect(0, 0, 10, 10);
            }
            changeResourceFlag1 = !changeResourceFlag1;// Готовим другой вариант
        }
    
        // Заменяем ресурс новым объектом
        bool changeResourceFlag2 = true;
        private void btn3_Click(object sender, RoutedEventArgs e)
        {
            if (changeResourceFlag2)// Полностью заменяем объекты ресурсов на новые
            {
                this.Resources["ForegroundBrush2"] = this.Resources["alternateColor"];
                this.Resources["BackgroundBrush2"] = this.Resources["alternateImage"];
            }
            else                    // Восстанавливаем объекты из словаря ресурсов
            {
                this.Resources["ForegroundBrush2"] = resourceDictionary["originColor"];
                this.Resources["BackgroundBrush2"] = resourceDictionary["originImage"];
            }
            changeResourceFlag2 = !changeResourceFlag2;// Готовим другой вариант
        }
    
        // Заменяем ресурс новым объектом
        private void btn4_Click(object sender, RoutedEventArgs e)
        {
            // Повышаем полномочия ссылки, чтобы применить метод FindResource. Можно повысить
            // только до FrameworkElement, там уже есть методы FindResource и TryFindResource 
            Button btn = (Button)sender;
    
            // Объявляем локальные ссылки
            Object colorRes, imageRes;
    
            if (changeResourceFlag2)// Полностью заменяем объекты ресурсов на новые
            {
                // Защищенно ищем ресурс методом FindResource, который 
                // при отсутствии ресурса генерирует исключение
                try
                {
                    colorRes = btn.FindResource("alternateColor");
                    imageRes = btn.FindResource("alternateImage");
                }
                catch { return; }   // Если не нашли, все оставляем как есть
    
                this.Resources["ForegroundBrush2"] = colorRes;
                this.Resources["BackgroundBrush2"] = imageRes;
            }
            else                    // Восстанавливаем объекты из словаря ресурсов
            {
                this.Resources["ForegroundBrush2"] = resourceDictionary["originColor"];
                this.Resources["BackgroundBrush2"] = resourceDictionary["originImage"];
            }
            changeResourceFlag2 = !changeResourceFlag2;// Готовим другой вариант
        }
    }
}
  • Запустите приложение и испытайте работу ресурсов

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



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

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

Мы точно знаем, что наши используемые элементами ресурсы размещены в коллекции ресурсов окна. Поэтому мы жестко адресуемся в коде к этим ресурсам по ссылке this, указывающей на объект окна. Такой способ не совсем гибкий - при переносе разметки ресурсов в коллекцию приложения придется переделывать процедурный код. Использование методов TryFindResource() и FindResource(), наследуемых объектами от класса FrameworkElement, делает код более универсальным. Но метод FindResource() генерирует исключение, если ресурс не будет найден, поэтому его разумнее поместить в контейнер обработки исключений.

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

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

  • Разберитесь с кодом, который снабжен подробными комментариями
< Самостоятельная работа 12 || Самостоятельная работа 13: 123456 || Лекция 1 >
Алексей Бабушкин
Алексей Бабушкин

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

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

Dmitriy Ivanchenko
Dmitriy Ivanchenko
Украина, Кировоград, Виктория-П, 2011
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974