Microsoft & .NETVisual C#Composite Custom Web Controls in Managed C++

Composite Custom Web Controls in Managed C++

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

The first two installments of this series got your feet wet in custom Web control development. The first covered some of the prep work required for setting up a Managed C++ custom Web control development environment. The second showed how to create a superclassed custom Web control, the simplest of the three types of custom Web controls.

This installment examines the more advanced—and far more powerful—composite custom Web control. Because the rendered custom Web control is such a meaty topic, I’ll save that for the fourth and final installment.

Composite Custom Web Controls

A composite custom Web control is exactly what its name suggests: a composite of one or more server or HTML controls within a single control. The composite custom control is functionally very similar to the user control. The big difference is that it resides in its own assembly (or one shared with other controls), can be put in the toolbar, and provides WYSIWYG views of the controls it contains when implemented.

On the other hand, composite custom Web controls are more difficult to create than user controls because the Visual Studio.NET designer offers no facility to create them visually. So, the question is: Why use composite controls instead of user controls? You’re better off using a composite custom Web control when distributing the control to multiple Web applications or systems. User controls are better when you are not focusing on re-use (for example, if you are going to use the control only within your site, sticking with the user control makes more sense). Basically, you have to weigh the extra effort it takes to create it versus the amount of re-use you are going to get from it. Also, because a composite custom Web control resides in its own assembly, you will need only one copy of it per machine. User controls are placed within the Web’s assembly and thus are stored in each Web site that uses it.

Creating a composite custom Web control

The steps for creating a composite custom Web control are virtually identical to those for creating a superclassed custom Web control, but that is why you do all that prep work upfront.

  1. Add a new class to a class library. (I’m just using the one created during prep work, but you can create a new class library if you like.) In this case, I called the class SearchControl , but of course you can call it anything you like.
  2. Paste the .h and .cpp code, which you save in the toolbox to the appropriate class files.
  3. Search and replace all instances of the default class name to the name of the new class in both the .h and .cpp files.
  4. In the .cpp file, rename the include file to point to the new .h file you created. I also added a .bmp file of binoculars to use as an icon on the toolbar, and I updated the linker input property.
  5. Compile, remove all old references to the original control library (if you are continuing from an old library like I am), and then add the reference to the new created assembly and a control (or set of controls, in my case) to the toolbar. You should now be able to drag and drop the default control to your designer.

Now, you are ready to create your new composite custom Web control.

The appearance

The first thing you should do is design the look of the control and then create the look and feel without any functionality behind it. When finished, I envision my control will look like Figure 1, which is the control as seen in the designer.

Figure 1. The Control as Seen in the Designer

SearchControl, as you can see, is made up of three server controls (but there are actually four, as you will see in a bit):

  • A Label
  • A TextBox
  • A Button

As I stated previously, the tricky part of composite custom Web controls is that they don’t have a nice drag-and-drop design tool with which to create controls. Instead, you have to do it the old-fashioned way—write the code from scratch. Well, that’s not exactly true. You don’t have to write your server or HTML controls from scratch. (That is what you do with a rendered custom Web control, by the way.) So, how do you go about creating the appearance of the SearchControl?

First, create definitions for the three server controls within the SearchControl class:

Label     *label;
TextBox   *textbox;
Button    *button;

Next, create instances of them in the class constructor:

SearchControl::SearchControl()
{
   label   = new Label();
   textbox = new TextBox();
   button  = new Button();
}

Finally, add them to a composite custom Web control’s child control collection within the class’s CreateChildControls() method:

void SearchControl::CreateChildControls()
{
   Controls->Add(label);
   Controls->Add(textbox);
   Controls->Add(button);
}

The CreateChildControls() method is a virtual method inherited from the Control class, which WebControl inherits.

Note: You don’t need the Render() method because the server and HTML control that make up the composite control can render themselves. Therefore, you can either leave out the method entirely or have the Render() method call the base class:

void SearchControl::Render(HtmlTextWriter *output)
{
   __super::Render(output);
}

Now that you have the basic appearance, you can add some functionality. First, you need some properties to update the labels and the value within the textbox. The previous installment covered properties, so I won’t go into the details again. But, here are the definitions for the properties:

[Bindable(true), Category("Appearance"), DefaultValue("")]
__property void set_Value(String *value);
__property String *get_Value();

[Bindable(true), Category("Appearance"), DefaultValue("")]
__property void set_LabelText(String *value);
__property String *get_LabelText();

[Bindable(true), Category("Appearance"), DefaultValue("")]
__property void set_ButtonText(String *value);
__property String *get_ButtonText();

And here is the code:

String *SearchControl::get_LabelText()
{
   this->EnsureChildControls();
   return label->Text;
}

void SearchControl::set_LabelText(String *value)
{
   this->EnsureChildControls();
   label->Text = value;
}

String *SearchControl::get_Value()
{
   this->EnsureChildControls();
   return textbox->Text;
}

void SearchControl::set_Value(String *value)
{
   this->EnsureChildControls();
   textbox->Text = value;
}

String *SearchControl::get_ButtonText()
{
   this->EnsureChildControls();
   return button->Text;
}

void SearchControl::set_ButtonText(String *value)
{
   this->EnsureChildControls();
   button->Text = value;
}

The only tricky part of the preceding code is calling the EnsureChildControls() method, which ensures that child controls have been previously created. If you don’t add this, the designer shows you a blank custom control.

When you run the preceding code, you will find some design flaws. First, the textbox is always the same size and you can’t line up the labels of more than one instance of the SearchControl (without some major fudging). To fix this, add the fourth server control, the Table, to the composite custom Web control (I find that the Table is the best way to handle almost all layout issues with controls):

void SearchControl::CreateChildControls()
{
   System::Web::UI::WebControls::Table *table = new Table();
   TableRow *row    = new TableRow();
   TableCell *cell1 = new TableCell();
   TableCell *cell2 = new TableCell();
   TableCell *cell3 = new TableCell();

   cell1->Controls->Add(label);
   cell2->Controls->Add(textbox);
   cell3->Controls->Add(button);

   row->Cells->Add(cell1);
   row->Cells->Add(cell2);
   row->Cells->Add(cell3);

   table->Rows->Add(row);

   Controls->Add(table);
}

To handle the aligning issue, add two more properties: LabelWidth and LabelAlign. LabelWidth ensures that the label of the control is a specific Unit size, and LabelAlign allows the label to be aligned using the HorizontalAlign enum:

[Bindable(true), Category("Appearance")]
__property void set_LabelWidth(Unit value);
__property Unit get_LabelWidth();

[Bindable(true), Category("Appearance")]
__property void set_LabelAlign(HorizontalAlign value);
__property HorizontalAlign get_LabelAlign();

Now comes the tricky part. To specify the width and alignment of the label, you change the attributes of the table cell in which it is contained and not the label itself. To simplify things, create a private class variable called cellLabel that replaces cell1 in the CreateChildControls() method. Here are the implementations of the properties LabelWidth and LabelAlign:

Unit SearchControl::get_LabelWidth()
{
   this->EnsureChildControls();
   return cellLabel->Width;
}

void SearchControl::set_LabelWidth(Unit value)
{
   this->EnsureChildControls();
   cellLabel->Width = value;
}

HorizontalAlign SearchControl::get_LabelAlign()
{
   this->EnsureChildControls();
   return cellLabel->HorizontalAlign;
}

void SearchControl::set_LabelAlign(HorizontalAlign value)
{
   this->EnsureChildControls();
   cellLabel->HorizontalAlign = value;
}

To handle the resizing of the textbox, I make it fill up 100 percent of the cell in which it is contained. Thus, when the table is resized, so is the textbox. One neat feature of the method I used is that it allows the label to be a fixed size, say 100px. Then, when the control is resized, the textbox grows with the control or you can specify that the label is a percent of the control—say 30 percent. Thus, when you resize the control, both the label and the textbox are readjusted based on the percentage. Here is a revamped CreateChildControls() method:

void SearchControl::CreateChildControls()
{
   System::Web::UI::WebControls::Table *table = new Table();
   TableRow *row    = new TableRow();
   TableCell *cell2 = new TableCell();
   TableCell *cell3 = new TableCell();

   cellLabel->Controls->Add(label);

   textbox->Width =  Unit::Percentage(100);
   cell2->Controls->Add(textbox);

   cell3->Controls->Add(button);

   row->Cells->Add(cellLabel);
   row->Cells->Add(cell2);

   cell3->Width =  Unit::Percentage(1);
   row->Cells->Add(cell3);

   table->Rows->Add(row);
   table->Width =  Unit::Percentage(100);

   Controls->Add(table);
}

Don’t forget to create an instance of labelCell in the constructor (as I did) or you may spend a while trying to figure out why the designer gives you a big error box.

Handling events

Now that you have everything set in the appearance, it’s time to give the control some actual functionality. The first thing you may want is the ability to handle the clicking of the control’s button. You do this by using an event handler, just as you would for a Windows application or Web application. Add the event handler to the Click event:

button->Click += new EventHandler(this, buttonClicked);

Then, create a method to handle the event:

void SearchControl::buttonClicked(Object *sender, EventArgs *e)
{
   OnClick(e);
}

No rocket science here.

Raising events

What happens if you want the users of your control to have access to the click event? To provide for this, you need to create a public __event on which they can provide their own event handlers:

__event EventHandler* Click;

You might also want to provide a protected method of OnClick() so that, if someone were to inherit from SearchControl, they could provide additional functionality to the Click event before (or after) passing control back to the SearchControl composite custom Web control. If you were paying attention, you may have noticed that the buttonClick() event handler actually calls the OnClick() method, which in effect passed control from the button to any event handlers that may be attached to the Click event of the control:

void SearchControl::OnClick(EventArgs *e)
{
   if (Click != 0)
   Click(this, e);
}

Because the Click event is the default (and only) event, it might be a good idea to add a DefaultEvent(“Click”) property attribute specifying Click as the default event for the control:

[DefaultProperty("Value"), DefaultEvent("Click"),
                           ToolboxData("<{0}:SearchControl
                           runat=server></{0}:SearchControl>")]
public __gc class SearchControl :
   public System::Web::UI::WebControls::WebControl
{
}

Remember, if you change metadata (attributes), you need to delete and re-refer the assembly for the metadata to be recognized. Once you add the property attribute, you simply have to double-click on the SearchControl control in the designer to have an event handler created for you. (The following code assumes that you use the C# Web application created back in the first installment of this series.)

private void SearchControl1_Click(object sender, System.EventArgs e)
{
   WebCustomControl1.Text = SearchControl1.Value;
}

Because the complete code is pretty lengthy, I will not place it here. Instead, simply download it at this link.

Lessons in Composite Custom Web Controls

This installment on custom Web control development in Managed C++ covered some interesting stuff. First, it created a composite custom Web control made up of four server controls. It demonstrated how to use properties to manipulate the control. It then showed how to handle events generated by the control. Finally, it demonstrated how to pass the events on to the Web application that uses the composite custom Web control.

All that is left is the rendered custom Web control, which the fourth and final installment will cover.

About the Author

Stephen Fraser has 15 years of IT experience working for a number of consulting companies. He has worked at large consulting firms such as of EDS and Andersen Consulting (Accenture) as well as in startup e-business and medical companies. His IT experience covers all aspects of application and Web development and management, ranging from initial concept all the way through to deployment.

Stephen is also the author of several books, including Managed C++ and .NET Development, Real World ASP.NET: Building a Content Management System, and the forthcoming Pro C++/CLI and .NET Version 2.0 Development.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories