Сайт разработчика Александр Климова

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Программа-шутка: Издеваемся над рабочим столом

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

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

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

BackColor – Fuchsia
FormBorderStyle – None
DoubleBuffered – True
Text - <empty string>
WindowState – Normal
ShowInTaskbar – False
TransparencyKey – Fuchsia
TopMost - True

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

Поддержка нескольких мониторов

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

Для получения информации о каждом мониторе, работающем в системе, нужно воспользоваться свойством Screen.AllScreens. Приложение перечисляет все мониторы и находит максимальные ширину и высоту:

// суммируем ширину и высоту всех мониторов
private Rectangle fullSize;

for(int i = 0; i < Screen.AllScreens.Length; i++)
{
    // находим прямоугольник, который охватывает все мониторы в системе
    // (основной монитор находится в левой верхней позиции!)
    if(Screen.AllScreens[i].Bounds.Left < fullSize.Left)
        fullSize.X = Screen.AllScreens[i].Bounds.Left;
    if(Screen.AllScreens[i].Bounds.Right > fullSize.Right)
        fullSize.Width = Screen.AllScreens[i].Bounds.Right;
    if(Screen.AllScreens[i].Bounds.Top < fullSize.Top)
        fullSize.Y = Screen.AllScreens[i].Bounds.Top;
    if(Screen.AllScreens[i].Bounds.Bottom > fullSize.Bottom)
        fullSize.Height = Screen.AllScreens[i].Bounds.Bottom;
}

Захват экрана

Для создания эффекта анимации необходимо получить изображения со всех мониторов и сохранить их в массиве. Эти изображения будут использоваться для создания эффекта вращения и зума. Осуществить захват изображения с экрана в .NET очень просто:

// массив изображений, по одному с каждого монитора
private Bitmap[] screenBitmap;

for(int i = 0; i < Screen.AllScreens.Length; i++)
{
    // получаем размеры текущего монитора
    Rectangle region = Screen.AllScreens[i].Bounds;

    // создаем картинку такого же размера
    screenBitmap[i] = new Bitmap(region.Width, region.Height, 
PixelFormat.Format32bppArgb);

    // копируем текущую картинку с экрана в bitmap для текущего монитора
    Graphics bitmapGraphics = Graphics.FromImage(screenBitmap[i]);
    bitmapGraphics.CopyFromScreen(region.Left, region.Top, 0, 0, region.Size);
}

Создание эффектов

Рисование эффектов происходит при помощи таймера. Таймер срабатывает каждые 100 милисекунд и вызывает событие Invalidate на форме. При этом форма вызывает обработчик события OnPaint, который мы переопределим.

Приложение поддерживает два эффекта - зум (приближение и удаление рабочего стола) и вращение рабочего стола. Оба эффекта достигаются стандартными вызовами методов GDI+.

Трансформация объектов достигается при помощи методов System.Drawing.Drawing2D. При вращении используются методы Rotate и RotateAt, а масштабирование при помощи Scale. Вот код для решения этой задачи:

// угол вращения
private float angle = 0.0f;

// шкала масшабирования
private float scaleIndex = 0.0f;

// черный фон
this.BackColor = Color.Black;

// выводим эффект для каждого монитора
for(int i = 0; i < screenBitmap.Length; i++)
{
    // получаем размеры текущего монитора
    Rectangle region = Screen.AllScreens[i].Bounds;

    if(screenBitmap[i] != null)
    {
        Matrix m = new Matrix();

        // вращаем картинку вокруг центра
        if(Properties.Settings.Default.Rotate)
            m.RotateAt(angle, new Point((Screen.AllScreens[i].Bounds.Width / 2), (Screen.AllScreens[i].Bounds.Height / 2)));

        // меняем размер картинки, исходя из абсолютного значения косинуса
        // очень удобно для создания эффекта приближения/удаления
        if(Properties.Settings.Default.Zoom)
        {
            scale = Math.Abs((float)Math.Cos(scaleIndex));
            m.Scale(scale, scale);
        }

        m.Translate(Screen.AllScreens[i].Bounds.Left + (Screen.AllScreens[i].Bounds.Width * (1.0f - scale)/2), 
                        Screen.AllScreens[i].Bounds.Top + (Screen.AllScreens[i].Bounds.Height * (1.0f - scale)/2), 
                        MatrixOrder.Append);

        // назначаем наши преобразования матрице
        e.Graphics.Transform = m;

        // рисуем это
        e.Graphics.DrawImage(screenBitmap[i], 0, 0);

        // увеличиваем значения
        if(Properties.Settings.Default.Zoom)
            scaleIndex += 0.07f;

        if(Properties.Settings.Default.Rotate)
            angle += 2.0f;
    }
}

Приведенный код сначала устанавливает фон окна в черный цвет. Затем это применяется к каждому монитору. Если вращение доступно, картинка вращается путем увеличения градуса вокруг центра экрана. Если установлено разрешение на изменение размеров, то изображение сжимается, обсчитывая абсолютное значение косинуса постоянно увеличивающего значения, начиная с 0. Со школьной скамьи вам должно быть известно, что cos(0) - 1.  Начиная с этого места, мы устанавливаем коэффициент масштабирования в 1, чтобы дальше не делать изменений. Увеличивая значение, передаваемое в метод cos, мы уменьшаем значение до 0, после чего мы получаем отрицательные значения. Беря абсолютные значения этих значений, мы получаем серию уменьшающих/увеличивающих значений от 0 до 1.

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

Финальный результат вычислений передается в свойство Graphics.Transform и окончательынй вариант рисуется на экране, после чего фактор изменения и вращения увеличивается.

Планировщик

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

Эти настройки сохраняются при помощи объекта Settings в проекте.

Настройки добавляются через правый щелчок мыши на проекте в Solution Explorer и выбора Properties. Окно настроек приложения выглядит следующим образом:

C помощью настроек мы можем указать время начала показа эффекта и выбрать, какие эффекты использовать.

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

// вызывается каждую минуту для проверки времени - не пора ли показывать шутку
private void tmrScheduler_Tick(object sender, EventArgs e)
{
    // если текущее время больше, чем время, установленное пользователем
    if(DateTime.Now >= Properties.Settings.Default.Time)
    {
        for(int i = 0; i < Screen.AllScreens.Length; i++)
        {
            // получаем размер текущего монитора
            Rectangle region = Screen.AllScreens[i].Bounds;

            // создаем картинку такоже размера
            screenBitmap[i] = new Bitmap(region.Width, region.Height, PixelFormat.Format32bppArgb);

            // копируем текущую картинку с экрана в bitmap для текущего монитора
            Graphics bitmapGraphics = Graphics.FromImage(screenBitmap[i]);
            bitmapGraphics.CopyFromScreen(region.Left, region.Top, 0, 0, region.Size);
        }

        // развертываем окно
        this.WindowState = FormWindowState.Normal;

        // закрываем все мониторы одни гигантским окном
        this.Location = new Point(fullSize.Left, fullSize.Top);
        this.Size = new Size(fullSize.Width, fullSize.Height);

        // останавливаем таймер планировщика
        tmrScheduler.Enabled = false;

        // включаем таймер анимации
        tmrAnim.Enabled = true;

        // выводим поверх всех окон
        this.BringToFront();
    }
}

Распространение

Теперь достаточно просто скопировать исполняемый файл AprilFoolsDay.exe и конфигурационный файл AprilFoolsDay.exe.config на компьютер жертвы и поместить ярлык к программе в папку Автозагрузка или воспользуйтесь реестром (раздел HKCU\Software\Microsoft\Windows\CurrentVersion\Run)

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

Если вы хотите изменить конфигурацию, то просто запустите приложение через командную строку с ключом -config, и вы увидите окно настроек.

Итак, мы написали небольшое приложение, которое требует немного кода и имеет интересный эффект для жертвы. Можете дальше модифицировать код, добавляя новые эффекты. Удачи!

Скачать пример

Перевод: Василий Котов

Реклама