Monthly Archives: October 2017

Messing with Message Loops

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows.Forms;

public class MessageLoop
{
    private Thread thread;
    private SynchronizationContext context;
    private ManualResetEvent initialized;

    public MessageLoop()
    {
        thread = new Thread(Initialize);
        thread.SetApartmentState(ApartmentState.STA);
        initialized = new ManualResetEvent(false);
    }

    private void Initialize()
    {
        Application.Idle += Application_Idle;
        Application.Run();
    }

    private void Application_Idle(object sender, EventArgs e)
    {
        if (Thread.CurrentThread.ManagedThreadId != thread.ManagedThreadId)
        {
            return;
        }
        Application.Idle -= Application_Idle;
        context = SynchronizationContext.Current;
        initialized.Set();
    }

    public void Start()
    {
        thread.Start();
        initialized.WaitOne();
    }

    public void Post(Action action)
    {
        context.Post(obj => action(), null);
    }

    public void Send(Action action)
    {
        context.Send(obj => action(), null);
    }

    public void Stop()
    {
        Send(() => Application.ExitThread());
        thread.Join();
    }
}

internal static class Program
{
    private const string Url = "http://www.stevencwilliams.com/";
    private static readonly string Path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "screenshot.png");
    private static readonly Size Size = new Size(640, 480);
    private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(0.1);

    private static ManualResetEvent done;
    private static Bitmap screenshot;

    private static void Main(string[] args)
    {
        File.Delete(Path);
        done = new ManualResetEvent(false);
        MessageLoop loop = new MessageLoop();
        loop.Start();
        loop.Send(Download);
        Wait();
        loop.Stop();
        if (done.WaitOne(TimeSpan.Zero))
        {
            screenshot.Save(Path);
        }
    }

    private static void Download()
    {
        WebBrowser browser = new WebBrowser()
        {
            Width = Size.Width,
            Height = Size.Height,
            ScriptErrorsSuppressed = true
        };
        browser.DocumentCompleted += (sender, e) =>
        {
            screenshot = new Bitmap(Size.Width, Size.Height);
            browser.DrawToBitmap(screenshot, new Rectangle(0, 0, Size.Width, Size.Height));
            done.Set();
        };
        browser.Navigate(Url);
        Console.WriteLine("Downloading {0}...", Url);
    }

    private static void Wait()
    {
        Console.WriteLine("Press any key to cancel...");
        foreach (char spinner in GetSpinners())
        {
            try
            {
                Console.Write(spinner);
                if (done.WaitOne(Timeout) || Console.KeyAvailable)
                {
                    break;
                }
            }
            finally
            {
                Console.Write('\b');
            }
        }
    }

    private static IEnumerable<char> GetSpinners()
    {
        while (true)
        {
            yield return '|';
            yield return '/';
            yield return '-';
            yield return '\\';
        }
    }
}

Dirty Checks for HTML Forms

$.fn.extend({
    setDirty: function (dirty) {
        this.data("dirty", dirty);
    }
});
var $forms = $("form");
$forms.setDirty(false);
$forms.change(function () {
    $(this).setDirty(true);
});
$forms.submit(function () {
    $(this).setDirty(false);
});
$(window).on("beforeunload", function () {
    var filter = function () {
        var $this = $(this);
        return $this.data("dirty") && !$this.data("ignore-dirty");
    };
    if ($forms.filter(filter).length > 0) {
        return "This page may have unsaved changes.";
    }
});

Usage:

<form>
    <label>
        First name
        <input type="text" />
    </label>
    <label>
        Last name
        <input type="text" />
    </label>
    <button type="submit">Submit</button>
</form>
<form data-ignore-dirty="true">
    <label>
        Search
        <input type="text" />
    </label>
    <button type="submit">Go</button>
</form>