views:

604

answers:

3

Is there a way in C# to check if an object is suspend? I have a TreeView that I need to know if it is still suspend.

 myTreeView.BeginUpdate();
 myTreeView.SuspendLayout();

 // Do Stuff.

 myTreeView.EndUpdate();
 myTreeView.ResumeLayout();

Because i have this code in a recursive function I want to know if the TreeView is already been suspended.

A: 

A quick look in Reflector at the SuspendLayout method from System.Windows.Forms.Control shows the following:

public void SuspendLayout()
{
    this.layoutSuspendCount = (byte) (this.layoutSuspendCount + 1);
    if (this.layoutSuspendCount == 1)
    {
        this.OnLayoutSuspended();
    }
}

Since it doesn't set any public flags, and the OnLayoutSuspended() method is internal there doesn't appear to be anyway to find out when the control is suspended.

You could create a subclass of the treeview with new Suspend and ResumeLayout methods, but it would be difficult to guarantee that they would be called in all circumstances since the base methods are not virtual.

Martin Harris
why couldn't you just use reflection to get the layoutSuspendCount member?
Jacob Adams
You could, and ShuggyCoUk's extremely complete answer includes code for it. However there is no guarantee that private fields will still exist in different versions of the framework so it would leave you with fragile code.
Martin Harris
+7  A: 

Following on from verminity's answer you do have one option:

Use the following class

public class SuspendAwareTreeView : TreeView    
{
    public readonly T RealControl;
    private int suspendCount;

    public bool IsSuspended 
    { 
        get { return suspendCount > 0; }
    }

    public Suspendable(T real) { this.RealControl = real; }

    public void SuspendLayout() 
    { 
        this.suspendCount++;
        this.RealControl.SuspendLayout();
    }

    public void ResumeLayout() 
    { 
        this.RealControl.ResumeLayout();
        this.suspendCount--;
    }
}

Then use this class for everything internally where you need to suspend it.

Obviously this won't work if you ever pass the class around to something that only expects a control or if something else outside your control sets it.

If this is the case you would be forced to go with a variety of less than pleasant solutions:

  • Write a new User control which wraps the TreeView and defers all calls to it but maintains the suspended state.
    • the resulting instance is no longer "is-a TreeView" which will cause problems.
    • maintenance effort possibly high.
    • if for some reason the treeview ever decided to suspend itself this will break.
    • new version of the runtime unlikely to break anything, you simply won't gain new functionality without effort.
  • Implement an entirely new TreeViewEx which exposes this state
    • the resulting instance is no longer "is-a TreeView" which will cause problems.
    • maintenance effort possibly high
    • can never break since you have total control, can diverge from original though
    • new version of the runtime unlikely to break anything, you simply won't gain new functionality without significant effort (possibly in violation of the law/EULA).
  • Violate Encapsulation
    • No alteration fo the type system, everything else continues to work.
    • Maintenance effort potentially high on runtime change
    • apps will break if the runtime changes underneath them

For your needs if and only if you control the runtime versions this operates on entirely (i.e. a controlled corporate environment) the following evil but effective hack is appropriate. So long as you test any time you upgrade it may well keep working with little effort.

public class ControlInvader
{
  private static readonly System.Reflection.FieldInfo layoutSuspendCount = 
      typeof(Control).GetField("layoutSuspendCount",
          System.Reflection.BindingFlags.Instance | 
          System.Reflection.BindingFlags.NonPublic);

  private readonly Control control;        

  public bool IsSuspended 
  {
    get 
    {
      return 0 != (byte)layoutSuspendCount.GetValue(this.control);
    }
  }

  public Suspendable(Control control) { this.control = control; }     
}

Attach this to your TreeView and then you can inspect the value whenever you like.

To reiterate this is fragile and entirely inappropriate for an environment where the version of the underlying runtime is not strictly controlled and where you can handle possible significant efforts to fix this on a breaking change. You would do well to include a static initializer which checks if the field actually existed and was the right type and aborted if not.

ShuggyCoUk
Minor bug in the second example. layoutSuspendCount is a byte not an int. Should be: `return 0 == (int)(byte)layoutSuspendCount.GetValue(this.control);`
Daniel Stutzbach
@Daniel thanks for the catch, just using 0 == (byte) should be fine, edited accordingly.
ShuggyCoUk
+1  A: 

Well, it's kind of late answer, but internally, the control is tracking the count and will only resume in the out most resume statement. So why do you care about it in the first place, you just make sure that you call the suspend and resume it in finally block:

void Recursive(Control c)
{
  c.SuspendLayout();
  try
  {
    if (existCondition) return;
    // do stuff
    Recursive(c);
  }
  finally
  {
    c.ResumeLayout(true);
  }
}

This works because below is how Control internally reacts to your call in below order:

c.SuspendLayout() // 1st call suspends layout
c.SuspendLayout() // 2nd and subsequent call only increase the count and does nothing.
....
c.ResumeLayout(true) // this decrease the count to 1 and does NOT actually resumes layout.
c.ResumeLayout(true) // this set the count to 0 and REALLY resumes layout.

HTH

Kenneth Xu