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