На нашем сайте мы используем cookie для сбора информации технического характера и обрабатываем IP-адрес вашего местоположения. Продолжая использовать этот сайт, вы даете согласие на использование файлов cookies. Здесь вы можете узнать, как мы пользуемся файлами cookies.
Я согласен
логотип upread.ru

Слежение за участком экрана на C#


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



Сначала алгоритм решения задачи – создания программы. Он прост:

  1. Выделяем кусок экрана и сохраняем его в битмап
  2. Запускаем таймер и сравниваем его раз в секунду с такими же кусками
  3. Если получилось расхождение – звук на полную громкость, окно на передний план
Выглядит просто, не правда ли? На самом деле все просто и есть.

Первый момент – как выделить кусок экрана? Первое, что приходит в голову – это рисовать курсором рамку. Вообще, да, это можно сделать. Но потребуется высчитывать координаты курсора, рисовать линии на рабочем столе (или стороннем приложении) – это все реализуемо, но, честно говоря, довольно муторно и нестабильно. Благо заказчик попался нетребовательный, интерфейс я сам рисовал, так что упростил себе задачу.

Я просто сделал прозрачное окно (форму) и закинул на него 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 - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.



тегизаметки, си шарп, личное, услуги, программы





Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.




Как позвонить с чужого номера
Магия закончилась: часть 2


© upread.ru 2013-2020
При перепечатке активная ссылка на сайт обязательна.
Задать вопрос
письмо
Здравствуйте! Вы можете задать мне любой вопрос. Если не получается отправить сообщение через эту форму, то пишите на почу up777up@yandex.ru
Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.