Слежение за участком экрана на C#
На днях написал небольшую программу на заказ. Суть приложения заключалась в слежении за определённым участком экрана. Необходимо было следить за выбранным участком, раз в секунду проверяя его изменения. Что-то типа бота, но гораздо проще. Программа должна была реагировать в на любые изменения на экране, то есть если мы перейдём со страницы сайта, откроем другую вкладку, то сразу же выскакивает окно и звуковое оповещение. Программа получилась простая, но с парой интересных моментов, о которых я и расскажу ниже.
Сначала алгоритм решения задачи – создания программы. Он прост:
- Выделяем кусок экрана и сохраняем его в битмап
- Запускаем таймер и сравниваем его раз в секунду с такими же кусками
- Если получилось расхождение – звук на полную громкость, окно на передний план
Первый момент – как выделить кусок экрана? Первое, что приходит в голову – это рисовать курсором рамку. Вообще, да, это можно сделать. Но потребуется высчитывать координаты курсора, рисовать линии на рабочем столе (или стороннем приложении) – это все реализуемо, но, честно говоря, довольно муторно и нестабильно. Благо заказчик попался нетребовательный, интерфейс я сам рисовал, так что упростил себе задачу.
Я просто сделал прозрачное окно (форму) и закинул на него picturebox (прозрачный, с рамкой) и кнопку (не прозрачную):
private void Form1_Load(object sender, EventArgs e)
{
this.AllowTransparency = true;
this.BackColor = Color.AliceBlue;
this.TransparencyKey = this.BackColor;
}
Высчитать координаты и размеры внутри picturebox также несложно:
Point location = pictureBox1.PointToScreen(Point.Empty);
x = location.X+5;
y = location.Y+5;
w = pictureBox1.Width - 5;
h = pictureBox1.Height - 5;
player.SoundLocation = "Sound_15750.wav";
Получаем скриншот данного куска экрана:
bmp1 = new Bitmap(w, h);
using (Graphics g = Graphics.FromImage(bmp1))
{
g.CopyFromScreen(x, y, 0, 0, new Size(w, h));
}
bmp1.Save("scrstart.png", System.Drawing.Imaging.ImageFormat.Png);
Дальше надо запустить таймер (не забыв кинуть на форму данный элемент) и свернуть окно на панель задач:
InitializeTimer(); this.WindowState = FormWindowState.Minimized; А вот что делает таймер у нас? Он раз в секунду берет этот же кусок экрана и сравнивает его битмап с первым изображением:
private void Timer1_Tick(object Sender, EventArgs e)
{
bmp1 = new Bitmap(Image.FromFile("scrstart.png"));
Bitmap bmp2 = new Bitmap(w, h);
using (Graphics g = Graphics.FromImage(bmp2))
{
g.CopyFromScreen(x, y, 0, 0, new Size(w, h));
}
pt = GetMismatchPoint(bmp1, bmp2);
if (pt != null) {
timer1.Enabled = false;
player.Play();
this.WindowState = FormWindowState.Normal;
this.TopMost = true;
}
}
И если оно отличается, то останавливает таймер, запускает музыку, а окно выскаивает на передний план. Тут интересный момент: сравнивать попиксельно нерационально и ресурсоемко. Поэтому возьмем готовый класс оболочку ImageWrapper и используем вот такой метод:
Point? GetMismatchPoint(Bitmap bmp1, Bitmap bmp2)
{
using (var wr1 = new ImageWrapper(bmp1))
using (var wr2 = new ImageWrapper(bmp2))
foreach (var p in wr1)
if (wr1[p] != wr2[p])
return p;
return null;
}
Работает очень шустро и не напрягает процессор: загрузка процессора 0 процентов, а паямти - 7 МБ. Не так уж и много, не правда ли? Еще один момент. Заказчик попросил, чтобы программа воспроизводила звук на полную громкость. Я не придумал ничего лучшего, как просто воспользоваться аппаратными методами:
private const int APPCOMMAND_VOLUME_MUTE = 0x80000;
private const int APPCOMMAND_VOLUME_UP = 0xA0000;
private const int APPCOMMAND_VOLUME_DOWN = 0x90000;
private const int WM_APPCOMMAND = 0x319;
[DllImport("user32.dll")]
public static extern IntPtr SendMessageW(IntPtr hWnd, int Msg,
IntPtr wParam, IntPtr lParam);
private void VolUp()
{
for (int i=0; i<50; i++) {
SendMessageW(this.Handle, WM_APPCOMMAND, this.Handle,
(IntPtr)APPCOMMAND_VOLUME_UP);
}
}
Работает? Ура, все работает. Задаем окном некий участок экрана и заставляем програму следить за ним. Отправляем заказчику приложение и ... и не работает.
Оказывается, на компьютере заказчика программа сразу же выдавала алерт - как будто что-то на экране изменилось. Но не изменялось ничего. В чем может быть проблема?
На первый взгляд ничего не должно глючить - просто нечему. Никакой привязки к железу, системе или разрешению экрана нет. Первая мысль была такой: возможно, что программа не успевает прочитать и занести в битмап первое изображение и соответственно, сравнивает разные вещи. Однако, сделал задержку таймера до 10 секунд - когда точно уже все должно успеть обработаться даже на самых слабых машинах - результат был одинаковый. То есть не работает так, как надо.
Продолжаем копать дальше. Переносим программу на другой ноутбук – чтобы понять, почему все происходит – там тоже не работает! Срабатывание оповещения в программе происходит из-за того, что картинки кажутся её разными. Давайте попробуем сравнить их, то есть заставим сохранить и второе изображение на диск. Реализуем и…видим кое-что интересное:

Слева – первоначальный кусок экрана, справа – полученный через одну секунду. Как видим, тут есть тень от picturebox – вот поэтому и приложение не признает изображения одинаковыми. Как решить проблему? Все просто: убираем по 10 пикселей справа и снизу – чтобы не захватывать тень при скриншоте. И ура – все заработало!
Итак, если вам требуется написать программу на заказ для личного пользования, создать простого бота или что-то еще, то вы всегда можете написать мне. Мои расценки невысоки.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.
Программы на заказ
Отзывы
Контакты