Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET (non-visual) component

I need to create a non-visual component, FooComponent, that will do some management for all controls of type Bar that resides in its form.

I have the following constraints:

  1. The FooComponent can only be added to forms.
  2. Only one FooComponent per form is allowed.
  3. FooComponent should register to the form closing event, and when it fires and to some function on all Bar's and sent the e.Cancel value based on the returned values.

#1 and #2 above should be enforced on run-time as well as design time. #3 event registration should be made automatically and not by the FooComponent's users.

I searched Google and MSDN for some help and read about Component and ComponentDesigner classes, but I didn't find anything for the rescue.

What should I do?

like image 786
Itay Karo Avatar asked Nov 16 '10 14:11

Itay Karo


3 Answers

(1) To control that the component can only be added to a form, use a FooComponent constructor that is passed a form, and don't define the default constructor. It's called like:

FooComponent component = new FooComponent(this);

where the component is created from within the form itself. By not-defining the default constructor, this:

FooComponent component = new FooComponent();

will not compile.


(2) Expose a FooComponent property on the form itself, and in the constructor of the FooComponent, set the passed form's FooComponent to this.


(3) Same thing, in the constructor for the FooComponent, register with the closing event for the form you passed


Put it all together and you get:

public class MyForm : Form {
    public FooComponent OwnedComponent { get; set; }
}


public class FooComponent {

    public FooComponent (MyForm OwnerForm) {
        OwnerForm.OwnedComponent = this;
        OwnerForm.FormClosing += MyCallback;
    }

    private void MyCallback(object sender, FormClosingEventArgs e) {
        ...
    }

}



EDIT
Unfortunately, if you need the default constructor, and if it has to be a true drop-on-the-form Component, there's no way to enforce that a component is only created on a Form, or that the Form only has one instance of the component (not from within the component, anyway).

The problem is twofold:
(1) Dropping a component doesn't add the component to the form, it adds it to the form's components collection. So even if you could get a handle to the parent/owner, it will never be a form.

(2) As Neil pointed out, dropping a component onto a form calls the default constructor, which passes no parameters, and, of course, none of the component's properties (such as site or container) are populated.


Possibly helpful: A component can be designed to be notified when it is created in a couple of ways:

(1) By implementing a constructor that takes an IContainer parameter. When the component is dropped on a form, the generated code will call this constructor, instead. However, it will only do this at runtime, not design time. But the container will be a handle to the form's components collection.

public FooComponent(IContainer container) {...}

(2) By implementing ISupportInitialize. When the component is dropped on a form, the generated code will additionally call BeginInit() and EndInit(). In EndInit(), you can access properties such as the Site and Container. Again, you'll only get this at runtime, not designtime, and throwing an exception here won't stop the component from being created.

Old, but excellent articles on Components and Controls from MSDN Magazine by Michael Weinhardt and Chris Sells.
April 2003 Building Windows Forms Controls and Components with Rich Design-Time Features
May 2003 Building Windows Forms Controls and Components with Rich Design-Time Features, Part 2

These are now .chm help files. You will need to unblock in the file's property page to enable reading the contents after downloading.

like image 74
James King Avatar answered Sep 28 '22 13:09

James King


I don't think it's possible to define exactly what a contained class can be contained within. I've certainly never seen an instance where I've gotten an error (or even a warning) for setting up a property of one type in another, even in WinForms.

Something you might be able to do is to define a Form-derived ancestor for your forms that contains a reference to your (internally-visible) FooComponent, initializes one on instantiation, and attaches the handlers. For best results it should be parameterless and the only constructor overload, so it forms the base for any constructor your consumers come up with. Then, just make it a house rule that forms derive from your ancestor class and not directly from Form (you might be able to use a code inspection tool like FxCop or similar to enforce this when code is committed to source control). Your users now get a FooComponent in every Form they create, cannot create their own (it's internal and should be in another project with your Form ancestor) and don't have to do anything other than derive from the new class to make their forms behave the way you want.

like image 26
KeithS Avatar answered Sep 28 '22 15:09

KeithS


You are asking for a lot. In general, making components aware of the form they are dropped on is quite difficult. This answer can help you get the event handler implemented. You'll need to implement ISupportInitialize to get the EndInit() call to setup the event handler.

Preventing multiples is quite hard too, I can only think of a custom designer that can step in early enough to prevent the 2nd one from being added.

like image 34
Hans Passant Avatar answered Sep 28 '22 14:09

Hans Passant