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. Мое приложение также является отличным примером альтернативной визуализации данных. Порой надоедают сплошные таблицы и списки, а мое «присутственное искусство» — классный пример слияния искусства и алгоритмов, когда функционально полезные вещи эстетически приятны. Надеюсь, вам понравится мое творение.