Managed C++ and .NET Development: Visual Studio .NET 2003 Edition

This section starts off with a couple of views provided by the .NET Framework: ListView and TreeView. If you have used Windows for any amount of time, then you have seen and used both of these views as they are used quite extensively. The reason is that these views provide, when used correctly, a better way of displaying data to the user.

A point that may not be apparent about views is that they are controls. This means that they are inheritable and derive from both component and control classes. Thus, they have all the benefits provided by both.

ListView

The ListView is a powerful (but slightly complicated) control that displays a list of items. You can see what a ListView control looks like by opening up Windows Explorer. The ListView is the right-hand panel if two panels are being displayed. The items can consist of a combination of a record (array) of text, a large icon, and/or a small icon. I cover working with icons later in the chapter in the "ToolBar" section.

You can display a ListView in one of four different View property modes:

Providing the functionality of the ListView requires a number of properties, many of which you have seen before. Here are some of the common ones unique to the ListView:

Along with these properties, the ListView provides a number of methods. These are some of the common methods unique to ListView:

Listing 10-1 shows a ListView of fruit, their price, and the month when they are available for harvest. (The data was derived using a high-tech research facility. Okay, you caught me—I made it up.) When an item is selected, its price is displayed in a label.

Listing 10-1: A ListView of Fruit

namespace ListView1 { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public _gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); FillListView(); } protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::ColumnHeader * Fruit; private: System::Windows::Forms::ColumnHeader * Price; private: System::Windows::Forms::ColumnHeader * Available; private: System::Windows::Forms::ListView * lView; private: System::Windows::Forms::Label * label; private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->lView = new System::Windows::Forms::ListView(); this->Fruit = new System::Windows::Forms::ColumnHeader(); this->Price = new System::Windows::Forms::ColumnHeader(); this->Available = new System::Windows::Forms::ColumnHeader(); this->label = new System::Windows::Forms::Label(); this->SuspendLayout(); // // lView // this->lView->Anchor = (System::Windows::Forms::AnchorStyles) ((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Left) | System::Windows::Forms::AnchorStyles::Right); System::Windows::Forms::ColumnHeader* __mcTemp__1[] = new System::Windows::Forms::ColumnHeader*[3]; __mcTemp__1[0] = this->Fruit; __mcTemp__1[1] = this->Price; __mcTemp__1[2] = this->Available; this->lView->Columns->AddRange(__mcTemp__1); this->lView->FullRowSelect = true; this->lView->GridLines = true; this->lView->Location = System::Drawing::Point(0, 0); this->lView->MultiSelect = false; this->lView->Name = S"lView"; this->lView->Size = System::Drawing::Size(424, 248); this->lView->TabIndex = 0; this->lView->View = System::Windows::Forms::View::Details; this->lView->SelectedIndexChanged += new System::EventHandler(this, lView_SelectedIndexChanged); // // Fruit // this->Fruit->Text = S"Fruit"; // // Price // this->Price->Text = S"Price"; // // Available // this->Available->Text = S"Available"; this->Available->Width = 100; // // label // this->label->BorderStyle = System::Windows::Forms::BorderStyle::FixedSingle; this->label->Location = System::Drawing::Point(170, 260); this->label->Name = S"label"; this->label->Size = System::Drawing::Size(60, 24); this->label->TabIndex = 1; this->label->TextAlign = System::Drawing::ContentAlignment::MiddleCenter; // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(400, 300); this->Controls->Add(this->label); this->Controls->Add(this->lView); this->Name = S"Form1"; this->Text = S"The List View Control"; this->ResumeLayout(false); } private: void FillListView() { String *itemRec1[] = { S"Apple", S"1.50", S"September" }; lView->Items->Add(new ListViewItem(itemRec1)); String *itemRec2[] = { S"Orange", S"2.50", S"March" }; lView->Items->Add(new ListViewItem(itemRec2)); String *itemRec3[] = { S"Grape", S"1.95", S"November" }; lView->Items->Add(new ListViewItem(itemRec3)); } private: System::Void lView_SelectedIndexChanged(System::Object * sender, System::EventArgs * e) { if (lView->FocusedItem != 0) label->Text = lView->FocusedItem->SubItems->Item[1]->Text; } }; }

Working with the ListView is a little tricky because the GUI designer doesn't place things in the code where you expect them (or at least I don't think so). So I'll group the code together so that you can see what's happening more clearly.

First, like any control, you create the ListView and then configure it using its properties. The example ListView is anchored and uses full row selection, display gridlines, no multiple selections, and the detailed view.

private: System::Windows::Forms::ListView * lView; //... this->lView = new System::Windows::Forms::ListView(); this->lView->Anchor = (System::Windows::Forms::AnchorStyles) ((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Left) | System::Windows::Forms::AnchorStyles::Right); this->lView->FullRowSelect = true; this->lView->GridLines = true; this->lView->MultiSelect = false; this->lView->Size = System::Drawing::Size(424, 248); this->lView->View = System::Windows::Forms::View::Details; this->lView->SelectedIndexChanged += new System::EventHandler(this, lView_SelectedIndexChanged); this->Controls->Add(this->lView);

Next, because the detailed view is used, you need to create headers for the ListView's items. Notice that you add the headers to the ListView control's Column property.

// Create and configure Header this->Available = new System::Windows::Forms::ColumnHeader(); this->Available->Text = S"Available"; this->Available->Width = 100; // Add header to ListView System::Windows::Forms::ColumnHeader* __mcTemp__1[] = new System::Windows::Forms::ColumnHeader*[3]; __mcTemp_1[0] = this->Fruit; __mcTemp__1[1] = this->Price; __mcTemp__1[2] = this->Available; this->lView->Columns->AddRange(__mcTemp__1);

Finally, once the ListView is ready for the world to see, you add the list items to the view. I showed this being done manually, but you could also use the designer to add list items.

// Add an Apple to the listview String *itemRec1[] = { S"Apple", S"1.50", S"September" }; lView->Items->Add(new ListViewItem(itemRec1));

Figure 10-1 shows what ListView.exe looks like when you execute it.

Figure 10-1: A ListView of fruit

TreeView

If you have worked with Visual Studio .NET, then you should be familiar with the TreeView control. It is used in numerous places—Solution Explorer, Server Explorer, and Class View, just to name a few. It is a control that displays a hierarchy of items in a tree format.

The TreeView, like the ListView just covered, can be a little complicated when you first try to develop code for it. Once you get the hang of it, though, you will realize that it is worth the effort of learning. The TreeView is a powerful tool that you will probably use several times in your coding career.

Configuring the TreeView control requires setting properties, just as with every other control. Here are the common properties you will likely use:

The key to working with the TreeView, like any other control, is to know which event to handle (see Table 10-1). All the events of the TreeView have default handlers, but if you want the control to do anything other than expand and contract, you need to handle the events yourself.

Table 10-1: Common TreeView Events

EVENT

DESCRIPTION

AfterCheck

Occurs after a check box is checked

AfterCollapse

Occurs after a node is collapsed

AfterExpand

Occurs after a node is expanded

AfterLabelEdit

Occurs after a label is edited

AfterSelect

Occurs after a node is selected

BeforeCheck

Occurs before a check box is checked

BeforeCollapse

Occurs before a node is collapsed

BeforeExpand

Occurs before a node is expanded

BeforeLabelEdit

Occurs before a label is edited

BeforeSelect

Occurs before a node is selected

The basic building block of a tree hierarchy is the TreeNode. There is always at least one root node and from it sprouts (possibly many) subnodes. A subnode in turn is also a TreeNode, which can spout its own TreeNodes.

There are several constructors for the TreeNode, but you'll probably deal mostly with two of them, unless you create the tree at design time (then you won't have to deal with them at all). The first constructor takes as a parameter a String as the label for the TreeNode and the second constructor also takes a String label but also an array of child TreeNodes. The second constructor allows for a node to have one or more child nodes. To make a node with only one child, you need to assign to the second parameter an array of child TreeNodes containing only one node.

// Constructor for a node with no children TreeNode *rtnA = new TreeNode(S"Root Node A"); // Constructor for a node with children TreeNode *tnodes[] = { new TreeNode(S"Node A"), new TreeNode(S"Node B") }; TreeNode *rtnB = new TreeNode(S"Root Node A", tnodes);

The TreeNode has a number of properties to handle its functionality. Many of the properties are used in navigating the tree. Here are some of the more common TreeNode properties:

Listing 10-2 shows how to build a tree hierarchy at runtime as opposed to prebuilding it statically. This example builds a new tree hierarchy every time it runs as it generates its node information randomly.

Listing 10-2: Random Tree Builder

namespace TreeView1 { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) //... protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::TreeView * tView; private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->tView = new TreeView(); this->SuspendLayout(); // // tView // this->tView->Dock = System::Windows::Forms::DockStyle::Fill; this->tView->LabelEdit = true; this->tView->Name = S"tView"; TreeNode* __mcTemp__1[] = new TreeNode*[2]; TreeNode* __mcTemp__2[] = new TreeNode*[1]; __mcTemp__2[0] = new TreeNode(S"<Holder>"); __mcTemp__1[0] = new TreeNode(S"Root Node A", __mcTemp__2); TreeNode* _mcTemp_3[] = TreeNode*[1]; __mcTemp__3[0] = new TreeNode(S"<Holder>"); __mcTemp__1[1] = new TreeNode(S"Root Node B", __mcTemp__3); this->tView->Nodes->AddRange(__mcTemp__1); this->tView->Size = System::Drawing::Size(200, 450); this->tView->BeforeExpand += new TreeViewCancelEventHandler(this, tView_BeforeExpand); // // Forml // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(200, 450); this->Controls->Add(this->tView); this->Name = S"Form1"; this->Text = S"The Tree View"; this->ResumeLayout(false); } private: System::Void tView_BeforeExpand(System::Object * sender, System::Windows::Forms::TreeViewCancelEventArgs * e) { // Already expanded before? if (e->Node->Nodes->Count > 1) return; // Already expanded else if (e->Node->Nodes->Count == 1) { if (e->Node->Nodes->Item[0]->Text->Equals(S"<Holder>")) e->Node->Nodes->RemoveAt(0); // node ready for expanding else return; // Already expanded but only one subnode } // Randomly expand the Node Random *rand = new Random(); Int32 rnd = rand->Next(1,5); for (Int32 i = 0; i < rnd; i++) // Random number of subnodes { TreeNode *stn = new TreeNode(String::Format(S"Sub Node {0}", _box(i+1))); e->Node->Nodes->Add(stn); if (rand->Next(2) == 1) // Has sub subnodes stn->Nodes->Add(new TreeNode(S"<Holder>")); } } }; }

The first steps, as with every other control, are to create the TreeView, configure it using properties, and then add it to the Form.

this->tView = new TreeView(); this->tView->Dock = System::Windows::Forms::DockStyle::Fill; this->tView->LabelEdit = true; this->tView->Size = System::Drawing::Size(200, 450); this->tView->BeforeExpand += new TreeViewCancelEventHandler(this, tView_BeforeExpand); this->Controls->Add(this->tView);

Because in this example you're building a tree hierarchy on the fly, you need to handle an event that occurs just before the tree node is expanded. The BeforeExpand event meets the bill. The BeforeExpand event takes as a handler TreeViewCancelEventHandler. You might note that the handler has the word "Cancel" in it, which means that it's triggered before the expansion of the node and it's possible to have the code cancel the expansion.

Now that you have a tree you need to add one or more root TreeNodes. You also have to add a holder sub-TreeNode or the expansion box will not be generated. The following code was autogenerated and is hardly pretty:

TreeNode* __mcTemp_1[] = new TreeNode*[2]; TreeNode* __mcTemp_2[] = new TreeNode*[1]; __mcTemp__2[0] = new TreeNode(S"<Holder>"); __mcTemp__1[0] = new TreeNode(S"Root Node A", __mcTemp__2); this->tView->Nodes->AddRange(__mcTemp__1);

If I were to code this by hand, the code would look more like this:

TreeNode *rtnA = new TreeNode(S"Root Node A"); tView->Nodes->Add(rtnA); rtnA->Nodes->Add(new TreeNode(S"<Holder>"));

At this point, if you were to execute the program (assuming you created a stub for the BeforeExpand event handler) you would get a TreeView with a root TreeNode and a sub-TreeNode. The sub-TreeNode would have the label <Holder>.

The last thing you need to do is replace the holder TreeNode when the expansion box is clicked with its own, randomly generated TreeNode hierarchy. Before you replace the holder TreeNode, you need to make sure that this is the first time the node has been expanded. You do this by looking for the holder TreeNode in the first child (and it should be the only child) of the selected expanded TreeNode. You can find all child nodes in the Nodes property in the Node property. (Look at the code—this is easier to code than explain.)

if (e->Node->Nodes->Count > 1) return; // Already expanded else if (e->Node->Nodes->Count == 1) { if (e->Node->Nodes->Item[0]->Text->Equals(S"<Holder>")) e->Node->Nodes->RemoveAt(0); // Holder node ready for expanding else return; // Already expanded but only one subnode }

If the node has been expanded previously, just jump out of the handler and let the TreeView re-expand the node with its original tree. If this is the first time the node has been expanded, then remove the holder and randomly create a new sub-TreeNode. The code to create the sub-TreeNode is virtually the same as that of the root TreeNode, except now you add it to the selected to-be-expanded TreeNode.

Random *rand = new Random(); Int32 rnd = rand->Next(1,5); for (Int32 i = 0; i < rnd; i++) // Random number of subnodes { TreeNode *stn = new TreeNode(String::Format(S"Sub Node {0}", __box(i+1))); e->Node->Nodes->Add(stn); if (rand->Next(2) == 1) // Has sub subnodes stn->Nodes->Add(new TreeNode(S"<Holder>")); }

Figure 10-2 shows a sample of what TreeView.exe looks like when you execute it.

Figure 10-2: Randomly generated and editable TreeView

Категории