Pull to refresh

И снова скриншоты в один клик (C#)

Reading time 4 min
Views 28K

Синопсис


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

Суть


Итак, что моя программа умеет?
  • Снимает скриншоты в один клик: по нажатию «горячих клавиш» (Ctrl+Print Screen) либо по клику на иконке в трее.
  • Сохраняет изображения (очевидно) с именами файлов, содержащими дату и время.
  • Тут же автоматически загружает их на Imgur.com и выдает ссылку во всплывающем окне.
  • При нажатии на всплывающее окно загруженная картинка открывается в браузере.
  • Есть возможность скопировать ссылку в буфер обмена (через контекстное меню программы).
  • Можно, тоже из контекстного меню, открыть папку с сохраненными скриншотами в Проводнике.
  • И наконец, легко и быстро включаемый автозапуск приложения при загрузке системы.

Трудности


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

Загрузка изображения на хостинг

Первым, с чем я столкнулся, была ошибка с использованием API хостинга: изображение загружалось, но куда-то девались последние несколько килобайт файла. Вначале использовал следующий код, найденный среди примеров использования API на самом хостинге:
using System;
using System.IO;
using System.Net;
using System.Text;

namespace ImgurExample
{
    class Program
    {
        static void Main(string[] args)
        {
            PostToImgur(@"C:\Users\ashwin\Desktop\image.jpg", IMGUR_ANONYMOUS_API_KEY);
        }

        public static void PostToImgur(string imagFilePath, string apiKey)
        {
            byte[] imageData;

            FileStream fileStream = File.OpenRead(imagFilePath);
            imageData = new byte[fileStream.Length];
            fileStream.Read(imageData, 0, imageData.Length);
            fileStream.Close();

            string uploadRequestString = "image=" + Uri.EscapeDataString(System.Convert.ToBase64String(imageData)) + "&key=" + apiKey;

            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create("http://api.imgur.com/2/upload");
            webRequest.Method = "POST";
            webRequest.ContentType = "application/x-www-form-urlencoded";
            webRequest.ServicePoint.Expect100Continue = false;

            StreamWriter streamWriter = new StreamWriter(webRequest.GetRequestStream());
            streamWriter.Write(uploadRequestString);
            streamWriter.Close();

            WebResponse response = webRequest.GetResponse();
            Stream responseStream = response.GetResponseStream();
            StreamReader responseReader = new StreamReader(responseStream);

            string responseString = responseReader.ReadToEnd();
        }
    }

В коде были ошибки, но даже после их исправления проблема не исчезла. После долгих и мучительных поисков ошибки и множества перепробованных вариантов я пришел к гипотезе, что файл может обрезаться после кодирования из-за слешей, используемых в base64. Возможно, они интерпретировались как escape-символы, но точно выяснять это было уже лень: пришлось бы сниффером ловить, что именно уходит в сеть (в отладчике все было отлично вплоть до вызова streamWriter.Write(uploadRequestString)), сравнивать base64-последовательности и т.п. В интернете эта проблема конкретно не освещалась, но попалась пара постов, в которых тоже были подозрения на слеши.

В итоге я использовал второй вариант кода, найденный на StackOverflow:
using (var w = new WebClient())
{
    var values = new NameValueCollection
    {
        { "key", "433a1bf4743dd8d7845629b95b5ca1b4" },
        { "image", Convert.ToBase64String(File.ReadAllBytes(@"hello.png")) }
    };

    byte[] response = w.UploadValues("http://imgur.com/api/upload.xml", values);

    Console.WriteLine(XDocument.Load(new MemoryStream(response)));
}">

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

Горячие клавиши

Вторая сложность возникла с горячими клавишами. Везде предлагается для перехвата глобальных горячих клавиш вешать хук функцией RegisterHotKey, а затем переопределять WndProc и в ней уже воспринимать сообщение. Казалось бы, в чем может быть сложность здесь? Сложность возникла в том, что мое приложение безоконное (хоть и WinForms), стало быть, нет ни дескриптора окна для передачи в RegisterHotKey, ни самого окна, в котором можно было бы переопределить WndProc. После напряженного курения мануалов выяснилось, что дескриптор передавать необязательно — в этом случае сообщения будут передавать не окну, а вызвавшему RegisterHotKey потоку. Первая часть проблемы была решена, но ловить само сообщение все еще было нечем. После еще пары стаканов кофе нашел решение и для этой проблемы: Application.AddMessageFilter(). Для того, чтобы ею воспользоваться, необходимо реализовать «фильтр» сообщений, в моем случае, он ловил WM_HOTKEY и вызывал соответствующую процедуру для обратки нажатия в контексте приложения. Больше препятствий для разработки не было.

Fin


Приложение, разумеется, freeware и исходники я тоже решил сделать открытыми. Комментарии оставлял в тех местах кода, которые, по моему мнению, неочевидны сами по себе. Поддержки локализаций нет, язык интерфейса английский. Приложение написано в VS2010 под .NET 4.0, никаких сторонних библиотек использовано не было. Иконка была найдена на iconfinder.com. Я не претендую на уникальность идеи, программа была написана для набивания руки, в процессе которого я столкнулся с определенными задачами и описал их решение в статье.

UPD. Залил бинарник отдельно, чтобы можно было пользоваться, не качая весь репозиторий: ScreenPaste.exe
Tags:
Hubs:
+20
Comments 39
Comments Comments 39

Articles