I've found a few places where control updates take longer than the redraw cycle.
I started off surrounding them with Suspend
and ResumeLayout
:
container.SuspendLayout();
updateControls();
container.ResumeLayout(true);
After doing that a few times, I realized it could be refactored into a context:
/// <summary>
/// Context manager to suspend and resume layout engine while performing
/// updates that take longer than a refresh cycle
/// </summary>
/// <example>using (new SuspendLayoutContext(container)
/// DoSlowUpdate(container.Controls);</example>
class SuspendLayoutContext : IDisposable
{
private readonly Control _control;
private readonly bool _performLayoutAfter;
/// <summary>
/// Constructs the context manager to suspend and resume layout.
/// </summary>
/// <param name="container"></param>
/// <param name="performLayout"></param>
public SuspendLayoutContext(Control container, bool performLayoutAfter=true)
{
_control = container;
_performLayoutAfter = performLayoutAfter;
_control.SuspendLayout();
}
/// <summary>Calls ResumeLayout on the container.</summary>
/// <remarks>If you call this manually, you are probably using this class wrong.</remarks>
public void Dispose()
{
_control.ResumeLayout(_performLayoutAfter);
}
}
Then, the above code would look like
using (new SuspendLayoutContext(container))
{
updateControls();
}
I then decided I didn't like having to call the constructor every time, so I created an extension method (///
comments elided for brevity):
static class ControlExtensions
{
public static SuspendLayoutContext SuspendLayoutContext(this Control container, bool performLayoutAfter=true)
{
return new SuspendLayoutContext(container, performLayoutAfter);
}
}
The example then looks like
using (container.SuspendLayoutContext())
{
updateControls();
}
Questions
Is there any reason not to use an
IDisposable
context here? I'm more familiar with python, where I wouldn't hesitate to create new context managers - but the method nameDispose()
raises red flags. (In python, it's__exit__
.)I debated between the extension method on
Control
and a static method onSuspendLayoutContext
:public static SuspendLayoutContext For(Control container, bool performLayoutAfter=true) { return new SuspendLayoutContext(container, performLayoutAfter) }
While I'm a big fan of fluent interfaces, the extension method felt cleaner. What alternatives might be better?
I always miss something when I do this, what is it this time?
1 Answer 1
I think there is nothing wrong with using IDisposable
whenever a class requires some sort of clean-up. It might have been originally designed to release unmanaged resources, but it is definitely no longer the case. Nowadays people use IDisposable
to do all sort of things: unsubscribe from events, release pooled objects, flush buffers... this list goes on and on.
I also would prefer extension method over other options. It looks cleaner IMHO. What I don't like is the Context
word. Not only because the method name should be a verb, but also because its somewhat misleading. I think a lot of people will assume that ...Context
class is the class that implements context object (anti-)pattern. But your class doesn't. For example, class LayoutSuspender
both sounds funny and describes what the class does. Win-win.
The thing you should be careful about is using this class with nested methods. For example, this:
public void UpdateAllControls()
{
using (container.SuspendLayoutContext())
{
UpdateControlA();
UpdateControlB();
}
}
public void UpdateControlA()
{
using (container.SuspendLayoutContext())
{
UpdateControlA();
}
}
won't work as you want it to. So eventually you might want to implement some sort of reference counting.