共用方式為


Screensaver с использованием WPF

Обзор

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

Эрик Климчак (Erik Klimczak) — Clarity Consulting (EN)

Загрузки:
Введение

Объединенные коммуникации компании Майкрософт (Microsoft Unified Communications, UC) — это активно обсуждаемая в последнее время новая технология. По сути, UC интегрирует обмен текстовыми, голосовыми и видеосообщениями между приложениями и устройствами, применяемыми людьми в повседневной практике. Дополнительные сведения о UC можно найти здесь. Одна из самых крутых возможностей UC — концепция «присутствия» (см. ниже). Индикатор присутствия позволяет пользователям отображать свое присутствие в приложениях, поддерживающих Office Communicator Server (таких, как Outlook, Office communicator, Live Meeting и др.).

Работая над другим проектом (связанным с коммуникациями), я воссоздал эти пузыри-индикаторы присутствия с использованием XAML, так что их размер можно изменять без потери качества изображения (см. ниже). Для воссоздания «пузырей присутствия» я использовал Expression Design и экспортировал их в XAML.

Мне пришла в голову мысль, что эти пузыри – весьма интересный графический материал.

Я переключился на Photoshop и сделал отличный фоновый рисунок для рабочего стола в стиле офисных «пузырей присутствия».

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

Партикль-анимация

Мои познания в графическом дизайне позволяют сказать, что мне требуется плавающий партикль-эффект. Партикль-эффекты применяются для имитации чего угодно – от дождя, снега и огня до пузырей и облаков, – а меняя размер «пузырей присутствия» и их прозрачность можно добиться эффекта трехмерного пространства. В результате некоторых изысканий я обнаружил в WPF обработчик события CompositionTarget.Rendering. Это событие возникает каждый раз, когда WPF решает, что пора очередной раз прорисовать кадр. В самом обработчике этого события вы можете делать что угодно с любыми объектами. Другими словами, событие CompositionTarget.Rendering дает в ваше распоряжение анимационную систему типа «сделай сам». Это событие особенно удобно для реализации анимации, основанной на физических законах, и очень напоминает поход, используемый в ядре визуализации Flash. Описание этого события в MSDN содержится здесь.

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

    1: 1: public partial class Particle : UserControl
    2: 2:    {
    3: 3: 
    4: 4:        public Particle()
    5: 5:        {
    6: 6: 
    7: 7:            InitializeComponent();
    8: 8:        }
    9: 9: 
   10: 0:        public static readonly DependencyProperty StatusProperty = DependencyProperty.Register("Status", typeof(Status), typeof(Particle), new UIPropertyMetadata(Particle.StatusValueChanged));
   11: 1:        public Status Status
   12: 2:        {
   13: 3:            get { return (Status)GetValue(StatusProperty); }
   14: 4:            set { SetValue(StatusProperty, value); }
   15: 5:        }
   16: 6: 
   17: 7:        private static void StatusValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   18: 8:        {
   19: 9:            Particle myclass = (Particle)d;
   20: 0:            myclass.imgStatus.Source = Application.Current.FindResource(((Status)e.NewValue).ToString() + "Png") as BitmapImage;
   21: 1:        }
   22: 2: 
   23: 3:        public double Radius
   24: 4:        {
   25: 5:            get { return this.imgStatus.Height / 2; }
   26: 6:            set { this.imgStatus.Height = this.imgStatus.Width = value * 2; }
   27: 7:        }
   28: 8: 
   29: 9:        private double vy;
   30: 0:        public double VY
   31: 1:        {
   32: 2:            get { return this.vy; }
   33: 3:            set { this.vy = value; }
   34: 4:        }
   35: 5: 
   36: 6:        private double vx;
   37: 7:        public double VX
   38: 8:        {
   39: 9:            get { return this.vx; }
   40: 0:            set { this.vx = value; }
   41: 1:        }
   42: 2: 
   43: 3:        private double mass;
   44: 4:        public double Mass
   45: 5:        {
   46: 6:            get { return this.mass; }
   47: 7:            set { this.mass = value; }
   48: 8:        }
   49: 9:        private double x;
   50: 0:        public double X
   51: 1:        {
   52: 2:            get { return this.x; }
   53: 3:            set { this.x = value; }
   54: 4:        }
   55: 5: 
   56: 6:        private double y;
   57: 7:        public double Y
   58: 8:        {
   59: 9:            get { return this.y; }
   60: 0:            set { this.y = value; }
   61: 1:        }
   62: 2:    }

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

    1: 1: void Spring(Particle a, Particle b)
    2:    2:         {
    3:    3: double dx = b.X - a.X;
    4:    4: double dy = b.Y - a.Y;
    5:    5: 
    6:    6: double dist = Math.Sqrt(dx * dx + dy * dy);
    7:    7: if (dist < _minDist)
    8:    8:             {
    9:    9: double ax = dx * _spring;
   10:   10: double ay = dy * _spring;
   11:   11: 
   12:   12:                 a.VX += ax;
   13:   13:                 a.VY += ay;
   14:   14: 
   15:   15:                 b.VX -= ax;
   16:   16:                 b.VY -= ay;
   17:   17: 
   18:   18: Canvas.SetLeft(a, a.X);
   19:   19: Canvas.SetTop(a, a.Y);
   20:   20: 
   21:   21: Canvas.SetLeft(b, b.X);
   22:   22: Canvas.SetTop(b, b.Y);
   23:   23: 
   24:   24: 
   25:   25:             }
   26:   26: 
   27:   27: Canvas.SetLeft(a, a.X);
   28:   28: Canvas.SetTop(a, a.Y);
   29:   29: 
   30:   30: Canvas.SetLeft(b, b.X);
   31:   31: Canvas.SetTop(b, b.Y);
   32:   32: 
   33:   33: 
   34:   34:         }
   35:   35: 

А это основной цикл анимации:

    1: 73: void CompositionTarget_Rendering(object sender, EventArgs e)
    2: 74:         {
    3: 75: for (int i = 0; i < _numParticles; i++)
    4: 76:             {
    5: 77:                 _particles[i].X += _particles[i].VX;
    6: 78:                 _particles[i].Y += _particles[i].VY;
    7: 79: 
    8: 80: Particle p = _particles[i];
    9: 81: 
   10: 82: if (p.X >this.Width)
   11: 83:                 {
   12: 84:                     p.X = 0;
   13: 85:                 }
   14: 86: 
   15: 87: elseif (p.X < 0)
   16: 88:                 {
   17: 89:                     p.X = this.Width;
   18: 90:                 }
   19: 91: 
   20: 92: if (p.Y >this.Height)
   21: 93:                 {
   22: 94:                     p.Y = 0;
   23: 95:                 }
   24: 96: elseif (p.Y < 0)
   25: 97:                 {
   26: 98:                     p.Y = this.Height;
   27: 99:                 }
   28: 00:             }
   29: 01: 
   30: 02: 
   31: 03: for (int i = 0; i < _numParticles - 1; i++)
   32: 04:             {
   33: 05: var partA = _particles[i];
   34: 06: for (int j = i + 1; j < _numParticles; j++)
   35: 07:                 {
   36: 08: var partB = _particles[j];
   37: 09:                     Spring(partA, partB);
   38: 10: 
   39: 11:                 }
   40: 12:             }
   41: 13:         }
Поддержка нескольких мониторов

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

    1: 1: private void Application_Startup(object sender, StartupEventArgs e)
    2: 2:         {
    3: 3: 
    4: 4: Window1 _window = null;
    5: 5: 
    6: 6:             System.Windows.Forms.Cursor.Hide();
    7: 7: 
    8: 8: foreach (Screen screen inScreen.AllScreens)
    9: 9:             {
   10: 0: if (screen.Primary)
   11: 1:                 {
   12: 2: Rectangle location = screen.Bounds;
   13: 3:                     _window = newWindow1(location.Height, location.Width);
   14: 4:                     _window.Width = location.Width;
   15: 5:                     _window.Height = location.Height;
   16: 6:                     _window.Left = 0;
   17: 7:                     _window.Top = 0;
   18: 8:                     _window.WindowState = WindowState.Maximized;
   19: 9:                     _window.Show();
   20: 0:                 }
   21: 1: 
   22: 2: elseif (!screen.Primary)
   23: 3:                 {
   24: 4: 
   25: 5: Rectangle location = screen.Bounds;
   26: 6:                     _window = newWindow1(location.Height, location.Width);
   27: 7:                     _window.Left = screen.WorkingArea.Left;
   28: 8:                     _window.Top = screen.WorkingArea.Top;
   29: 9:                     _window.Width = location.Width;
   30: 0:                     _window.Height = location.Height;
   31: 1:                     _window.Show();
   32: 2:                 }
   33: 3: 
   34: 4:             }
   35: 5: 
   36: 6:         }
Превращение анимации в экранную заставку

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

    1: 1: MouseMove += newMouseEventHandler(Window2_MouseMove);
    2: 2:   MouseDown += newMouseButtonEventHandler(Window2_MouseDown);
    3: 3:   KeyDown += newKeyEventHandler(Window2_KeyDown);
    4: 4: void Window2_KeyDown(object sender, KeyEventArgs e)
    5: 5:         {
    6: 6: Application.Current.Shutdown();
    7: 7:         }
    8: 8: 
    9: 9: void Window2_MouseDown(object sender, MouseButtonEventArgs e)
   10: 0:         {
   11: 1: Application.Current.Shutdown();
   12: 2:         }
   13: 3: 
   14: 4: void Window2_MouseMove(object sender, MouseEventArgs e)
   15: 5:         {
   16: 6: Point currentPosition = e.MouseDevice.GetPosition(this);
   17: 7: 
   18: 8: if (!isActive)
   19: 9:             {
   20: 0:                 mousePosition = currentPosition;
   21: 1:                 isActive = true;
   22: 2:             }
   23: 3: else
   24: 4:             {
   25: 5: 
   26: 6: if ((Math.Abs(mousePosition.X - currentPosition.X) > 10) ||
   27: 7:                     (Math.Abs(mousePosition.Y - currentPosition.Y) > 10))
   28: 8:                 {
   29: 9: Application.Current.Shutdown();
   30: 0:                 }
   31: 1:             }
   32: 2:         }

В завершение установите режим сборки «Release» и скомпонуйте приложение. В результирующем каталоге найдите созданный исполняемый файл и измените его расширение с .exe на .scr, щелкните его правой кнопкой и выберите в контекстном меню пункт Установить.

Вот и все!

Возможные усовершенствования

Office Communicator Server поставляется со своим SDK, который позволяет создавать собственные приложения Unified Communications.

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

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