Home .NET One-click screenshots again (C#)

One-click screenshots again (C#)

by admin

Synopsis

Not so long ago I started to learn C# and very soon my experiments grew into desire to write some light, simple, but useful and usable application. Gradually, the idea was born of a program, designed for taking screenshots quickly and automatically uploading them to a hosting service. I have found no suitable analogue, so I decided to make it myself. And after that one of the good people gave me an idea to write an article about it.

The gist of it

So, what can my program do?

  • Takes one-click screenshots:by pressing "hotkeys" (Ctrl+Print Screen) or by clicking on the tray icon.
  • Saves images (obviously) with filenames containing the date and time.
  • Then automatically uploads them to Imgur.com. and gives you a link in a popup window.
  • When you click on the popup, the downloaded picture opens in the browser.
  • There is an option to copy the link to the clipboard (through the context menu of the program).
  • You can also use the context menu to open the folder with the saved screenshots in Explorer.
  • And finally, an easy and fast auto-run application when the system starts up.

Difficulties

And now the part that may seem obvious and trivial to some, but may help some. Even in developing such a small application, as it turns out, it’s impossible to avoid the hard parts.

Uploading image to hosting

The first thing I encountered was an error with the hosting API: the image was uploaded, but the last few kilobytes of the file disappeared. At first I used the following code found among examples using the API on the hosting itself :

using System;using System.IO;using System.Net;using System.Text;namespace ImgurExample{class Program{static void Main(string[] args){PostToImgur(@"C:UsersashwinDesktopimage.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();}}

There were errors in the code, but even after fixing them the problem did not disappear. After a long and painful search for the error and a lot of tried variants I came to a hypothesis, that the file can be truncated after encoding because of the slashes used in base64. Maybe they were interpreted as escape-characters, but I was too lazy to find this out for sure: I had to use sniffer to catch what exactly goes online (in the debugger everything was fine until streamWriter.Write(uploadRequestString) was called), compare base64 sequences and so on. This problem wasn’t specifically covered on the Internet, but I came across a couple of posts which also suspected slashes.
I ended up using the second version of the code found on 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)));}">

It already worked like clockwork, you just had to adapt it to your needs – the function was only supposed to return a reference, not the whole response.

Hotkeys

The second difficulty is with the hotkeys. It is suggested everywhere to hook global hotkeys with RegisterHotKey and then override WndProc and read the message in it. What seems to be the problem here? The complexity came from the fact that my application is windowless (although it is WinForms), so there is neither a window handle to pass to RegisterHotKey, nor a window itself where we could override WndProc. After a bit of intensive reading of manuals we found out that it is not necessary to pass the descriptor – in this case messages will be passed not to the window but to the thread which calls RegisterHotKey. The first part of the problem was solved, but there was still nothing to catch the message itself. After a couple more cups of coffee I found a solution to this problem too: Application.AddMessageFilter() In order to use it, you need to implement a message "filter", in my case, it would catch WM_HOTKEY and call the appropriate procedure to reverse the click in the application context. There were no other development obstacles.

Fin

The application is, of course, freeware and I also decided to make the source code open The comments I left in those places of the code, which in my opinion are not obvious by themselves. There is no localization support, the interface language is English. The application was written in VS2010 under .NET 4.0, no third party libraries were used. The icon was found at iconfinder.com I do not claim to be a unique idea, the program was written to get my hands on it, in the process of which I encountered certain problems and described their solution in the article.
UPD. Uploaded the binary separately, so you can use it without downloading the whole repository: ScreenPaste.exe

You may also like