Home

Visual C++/CLI Collections: Dictionary-Based Collection Classes

 

The Hashtable/SortedList and the Dictionary/SortedList Difference

If you use the System::Collections::Hashtable or the System::Collections::Generic::Dictionary class to create your list, the items are cumulatively added to the collection every time you call the Add() method or when you use the indexed property to add an item. In our introduction, we saw that the optional third rule of a dictionary type of list is that the list be sorted based on the key.

To spare you the hassle of manually taking care of this, the alternative is to use the SortedList class.

Whenever a new item is added to a System::Collections::SortedList variable, a System::Collections::Generic::SortedDictionary variable, or a System::Collections::Generic::SortedList variable, the list is rearranged so the collection can be sorted in either alphabetical or chronological order based on the keys. This means that, if you want your list to be logically arranged by the keys, use one of these Sorted classes to create the collection.

Practical LearningPractical Learning: Sorting a Dictionary the Members of a Collection

  1. On the main form, click the Employees button
  2. Create a few employees as follows:
     
    Employee # First Name Last Name Title Hourly Salary
    62-845 Patricia Katts General Manager 42.25
    92-303 Henry Larson Sales Representative 12.50
    25-947 Gertrude Monay Sales Representative 14.05
    73-947 Helene Sandt Intern 8.85
    40-508 Melanie Karron Sales Representative 12.75
    22-580 Ernest Chisen Sales Manager 22.95
    20-308 Melissa Roberts Administrative Assistant 15.45
    Employee
    Bethesda Car Rental: Employees
  3. Notice that the list of employees is automatically sorted based on the employees numbers.
    Close the forms and return to your programming environment

The Keys and the Values of a Collection

After adding one or more items to the list, they are stored in two collections. The keys are stored in a collection represented by a property named Keys. The values are stored in the Values property. In the System::Collections classes, the Keys and the Values properties are of type ICollection.

To assist you with managing the keys of their collections, the System::Collections::Generic::Dictionary, the System::Collections::Generic::SortedDictionary, and the System::Collections::Generic::SortedList classes are equipped with a nested class named KeyCollection. KeyCollection is a serializable generic class that implements the ICollection<> interface. The only real functionalities of the nested KeyCollection class are its ability to know the current number of items in the list and the ability to enumerate the members of the collection through a for each loop.

To assist you with the values of their lists, the System::Collections::Generic::Dictionary, the System::Collections::Generic::SortedDictionary, and the System::Collections::Generic::SortedList classes are equipped with the nested ValueCollection. The ValueCollection serializable class implements the ICollection<> and the IEnumerable interfaces. The functionality of the ValueCollection class is the same as its counterpart of the System::Collections namespace.

Practical LearningPractical Learning: Using the Keys of a Collection

  1. Display the Order Processing form and double-click the New Rental Order/Reset button
  2. Implement the event as follows:
     
    System::Void btnNewRentalOrder_Click(System::Object^  sender, 
    				     System::EventArgs^  e)
    {
        txtEmployeeNumber->Text = L"";
        txtEmployeeName->Text = L"";
        txtDrvLicNumber->Text = L"";
        txtCustomerName->Text = L"";
        txtCustomerAddress->Text = L"";
        txtCustomerCity->Text = L"";
        cbxCustomerStates->Text = L"";
        txtCustomerZIPCode->Text = L"";
        txtTagNumber->Text = L"";
        cbxCarConditions->Text = L"";
        txtMake->Text = L"";
        txtModel->Text = L"";
        txtCarYear->Text = L"";
        cbxTankLevels->Text = L"Empty";
        txtMileageStart->Text = L"0";
        txtMileageEnd->Text = L"0";
        dtpStartDate->Value = DateTime::Today;
        dtpEndDate->Value = DateTime::Today;
        txtDays->Text = L"";
        cbxOrderStatus->Text = L"";
        txtRateApplied->Text = L"0.00";
        txtSubTotal->Text = L"0.00";
        txtTaxRate->Text = L"7.75";
        txtTaxAmount->Text = L"0.00";
        txtOrderTotal->Text = L"0.00";
    
        BinaryFormatter ^ bfmRentalOrders = gcnew BinaryFormatter;
    
        String ^ strFilename = L"C:\\Bethesda Car Rental\\RentalOrders.cro";
    
        if( File::Exists(strFilename))
        {
            FileStream ^ stmRentalOrders = gcnew FileStream(strFilename,
                                                        FileMode::Open,
                                                        FileAccess::Read,
                                                        FileShare::Read);
    
            try
            {
    	    lstRentalOrders = 
    		dynamic_cast<Generic::SortedList<int, CRentalOrder ^> ^>
    		    (bfmRentalOrders->Deserialize(stmRentalOrders));
                ReceiptNumber =
    		lstRentalOrders->Keys[lstRentalOrders->Count - 1] + 1;
            }
            finally
            {
                stmRentalOrders->Close();
            }
        }
        else
        {
            ReceiptNumber = 100001;
            lstRentalOrders = gcnew Generic::SortedList<int, CRentalOrder ^>;
        }
    
        txtReceiptNumber->Text = ReceiptNumber.ToString();
    }
  3. Return to the Order Processing form and double-click an unoccupied area of its body
  4. Implement the Load event as follows:
     
    System::Void OrderProcessing_Load(System::Object^  sender, 
    				  System::EventArgs^  e)
    {
        btnNewRentalOrder_Click(sender, e);
    }
  5. Return to the Order Processing form

Checking the Existence of an Item

Locating an item in a dictionary type of list consists of looking for either a key, a value, or a combination of Key=Value. The Hashtable, the Dictionary, and the SortedList classes are equipped to handle these operations with little effort on your part. If you know the key of an item but want to find a value, you can use the indexed property because it produces it. Here is an example:

void CExercise::Start(Object ^ sender, EventArgs ^ e)
{
    Hashtable ^ Students = gcnew Hashtable;

    Students->Add(L"Hermine", "Tolston");
    Students->Add(L"Patrick", "Donley");
    Students->Add(L"Chrissie", "Hannovers");
    Students->Add(L"Patricia", "Herzog");
    Students[L"Michael"] = L"Herlander";

    for each(DictionaryEntry ^ entry in Students)
        lbxStudents->Items->Add(entry->Key + L" " + entry->Value);

    String ^ Value = static_cast<String ^>(Students[L"Chrissie"]);
    MessageBox::Show(L"The value of the Chrissie key is " + Value);
}

This would produce:

Message Box

To find out whether a Key/Value item exists in the list, if you are using one of the classes from the System::Collections namespace, you can call the System::Collections::Hashtable::Contains() or the System::Collections::SortedList::Contains() method. Its syntax is:

public:
    virtual bool Contains(Object^ key);

To look for an item, you pass its key as argument to this method. Here is an example:

void CExercise::Start(Object ^ sender, EventArgs ^ e)
{
    Hashtable ^ Students = gcnew Hashtable;

    Students->Add(L"Hermine", "Tolston");
    Students->Add(L"Patrick", "Donley");
    Students->Add(L"Chrissie", "Hannovers");
    Students->Add(L"Patricia", "Herzog");
    Students[L"Michael"] = L"Herlander";

    for each(DictionaryEntry ^ entry in Students)
        lbxStudents->Items->Add(entry->Key + L" " + entry->Value);

     bool found = Students->Contains(L"Chrissie");

    if (found == true)
        MessageBox::Show(L"The list contains an item " 
                         L"whose key is Chrissie");
    else
        MessageBox::Show(L"The list doesn't contain an " 
                         L"item whose key is Chrissie");

    found = Students->Contains(L"James");

    if (found == true)
        MessageBox::Show(L"The list contains an item " 
                         L"whose key is James");
    else
        MessageBox::Show(L"The list doesn't contain an " 
                         L"item whose key is James");
}

This would produce:

Message Box
 
Message Box
 

 

 

Checking the Existence of a Key

We have seen that the System::Collections::Hashtable::Contains() and the System::Collections::SortedList::Contains() methods allow you to find out whether a collection contains a certain specific key. An alternative is to call a method named ContainsKey.

The syntax of the System::Collections::Hashtable::ContainsKey() and the System::Collections::SortedList::ContainsKey() method is:

public:
    virtual bool ContainsKey(Object^ key);

The syntax of the System::Collections::Generic::Dictionary::ContainsKey() and the System::Collections::Generic::SortedList::ContainsKey() method is:

public:
    virtual bool ContainsKey(TKey key) sealed;

Practical LearningPractical Learning: Checking the Existence of a Key

  1. On the Order Processing form, double-click the Save button and implement its event as follows:
     
    System::Void btnSave_Click(System::Object^  sender, System::EventArgs^  e)
    {
        if(txtReceiptNumber->Text == L"")
        {
            MessageBox::Show(L"The receipt number is missing");
            return;
        }
    
        // Don't save this rental order if we don't
        // know who processed it
        if(txtEmployeeNumber->Text == L"")
        {
            MessageBox::Show(L"You must enter the employee number or the " 
                             L"clerk who processed this order.");
            return;
        }
    
        // Don't save this rental order if we don't
        // know who is renting the car
        if(txtDrvLicNumber->Text == L"")
        {
            MessageBox::Show(L"You must specify the driver's license number " 
                             L"of the customer who is renting the car");
            return;
        }
    
        // Don't save the rental order if we don't
        // know what car is being rented
        if(txtTagNumber->Text == L"")
        {
            MessageBox::Show(L"You must enter the tag number " 
                             L"of the car that is being rented");
            return;
        }
    
        // Create a rental order based on the information on the form
        CRentalOrder ^ CurrentOrder = gcnew CRentalOrder;
    
        CurrentOrder->EmployeeNumber = txtEmployeeNumber->Text;
        CurrentOrder->OrderStatus = cbxOrderStatus->Text;
        CurrentOrder->CarTagNumber = txtTagNumber->Text;
        CurrentOrder->CustomerDrvLicNbr = txtDrvLicNumber->Text;
        CurrentOrder->CustomerName = txtCustomerName->Text;
        CurrentOrder->CustomerAddress = txtCustomerAddress->Text;
        CurrentOrder->CustomerCity = txtCustomerCity->Text;
        CurrentOrder->CustomerState = cbxCustomerStates->Text;
        CurrentOrder->CustomerZIPCode = txtCustomerZIPCode->Text;
        CurrentOrder->CarCondition = cbxCarConditions->Text;
        CurrentOrder->TankLevel = cbxTankLevels->Text;
    
        try
        {
            CurrentOrder->MileageStart = int::Parse(txtMileageStart->Text);
        }
        catch(FormatException ^)
        {
            MessageBox::Show(L"Invalid mileage start value");
        }
    
        try
        {
            CurrentOrder->MileageEnd = int::Parse(txtMileageEnd->Text);
        }
        catch(FormatException ^)
        {
            MessageBox::Show(L"Invalid mileage end value");
        }
    
        try
        {
            CurrentOrder->DateStart = dtpStartDate->Value;
        }
        catch(FormatException ^)
        {
            MessageBox::Show(L"Invalid start date");
        }
    
        try {
                    CurrentOrder->DateEnd = dtpEndDate->Value;
        }
        catch(FormatException ^)
        {
                    MessageBox::Show(L"Invalid end date");
        }
    
        try {
                    CurrentOrder->Days = int::Parse(txtDays->Text);
        }
        catch(FormatException ^)
        {
                    MessageBox::Show(L"Invalid number of days");
        }
    
        try {
            CurrentOrder->RateApplied = double::Parse(txtRateApplied->Text);
        }
        catch(FormatException ^)
        {
                    MessageBox::Show(L"Invalid rate value");
        }
    
        CurrentOrder->SubTotal = double::Parse(txtSubTotal->Text);
    
        try {
            CurrentOrder->TaxRate = double::Parse(txtTaxRate->Text);
        }
        catch(FormatException ^)
        {
            MessageBox::Show(L"Inavlid tax rate");
        }
    
        CurrentOrder->TaxAmount = double::Parse(txtTaxAmount->Text);
        CurrentOrder->OrderTotal = double::Parse(txtOrderTotal->Text);
        // The rental order is ready
    
        // Get the receipt number from its text box
        try {
            ReceiptNumber = int::Parse(txtReceiptNumber->Text);
        }
        catch(FormatException ^)
        {
            MessageBox::Show(L"You must provide a receipt number");
        }
    
        // Get the list of rental orders and 
        // check if there is already one with the current receipt number
        // If there is already a receipt number like that...
        if (lstRentalOrders->ContainsKey(ReceiptNumber) == true)
        {
            // Simply update its value
            lstRentalOrders[ReceiptNumber] = CurrentOrder;
        }
        else
        {
            // If there is no order with that receipt,
            // then create a new rental order
            lstRentalOrders->Add(ReceiptNumber, CurrentOrder);
        }
        
        // The list of rental orders
        String ^ strFilename = L"C:\\Bethesda Car Rental\\RentalOrders.cro";
        FileStream ^ bcrStream = gcnew FileStream(strFilename,
                                              FileMode::Create,
                                              FileAccess::Write,
                                              FileShare::Write);
        BinaryFormatter ^ bcrBinary = gcnew BinaryFormatter;
    
        try
        {
            bcrBinary->Serialize(bcrStream, lstRentalOrders);
        }
        finally
        {
            bcrStream->Close();
        }
    }
  2. Save the file

Checking the Existence of a Value

To find out whether a particular value exists in the list, you can call the ContainsValue() method. The syntax of the System::Collections::Hashtable::ContainsValue() and the System::Collections::SortedList::ContainsValue() method is::

public:
    virtual bool ContainsValue(Object^ value);

The syntax of the System::Collections::Generic::Dictionary::ContainsValue() and the System::Collections::Generic::SortedList::ContainsValue() method is:

public:
    bool ContainsValue(TValue value);

Getting the Value of a Key

The ContainsKey() method allows you to only find out whether a dictionary-based collection contains a certain key. It does not identify that key and it does not give any significant information about that key, except its existence. In some operations, first you may want to find out if the collection contains a certain key. Second, if that key exists, you may want to get its corresponding value.

To assist you with both checking the existence of a key and getting its corresponding value, the generic Dictionary, SortedDictionary, and SortedList classes are equipped with a method named TryGetValue. Its syntax is:

public:
    virtual bool TryGetValue(TKey key, 
		             [OutAttribute] TValue% value) sealed;

When calling this method, the first argument must be the key to look for. If that key is found, the method returns its corresponding value as the second argument passed as an out reference.

Practical LearningPractical Learning: Getting the Value of a Key

  1. On the Order Processing form, click the Employee Number text box
  2. In the Events section of the Properties window, double-click Leave and implement the event as follows:
     
    System::Void txtEmployeeNumber_Leave(System::Object^  sender, 
    					System::EventArgs^  e)
    		 {
        CEmployee ^ clerk;
        String ^ strEmployeeNumber = txtEmployeeNumber->Text;
    
        if (strEmployeeNumber->Length == 0)
        {
            MessageBox::Show(L"You must enter the employee's number");
                    txtEmployeeNumber->Focus();
                    return;
        }
    
        SortedDictionary<String ^, CEmployee ^> ^ lstEmployees =
                    gcnew SortedDictionary<String ^, CEmployee ^>;
        BinaryFormatter ^ bfmEmployees = gcnew BinaryFormatter;
    
        String ^ strFilename = L"C:\\Bethesda Car Rental\\Employees.cre";
    
        if( File::Exists(strFilename))
        {
            FileStream ^ stmEmployees = gcnew FileStream(strFilename,
                                                     FileMode::Open,
                                                     FileAccess::Read,
                                                     FileShare::Read);
            try {
               // Retrieve the list of employees
                lstEmployees = 
    		dynamic_cast<SortedDictionary<String ^, CEmployee ^> ^>
    		    (bfmEmployees->Deserialize(stmEmployees));
                if (lstEmployees->TryGetValue(strEmployeeNumber, clerk))
                {
                    txtEmployeeName->Text = clerk->FullName;
                }
                else
                {
                    txtEmployeeName->Text = L"";
                    MessageBox::Show(L"There is no employee with that number");
                    return;
                }
            }
            finally
            {
                stmEmployees->Close();
            }
        }
    }
  3. Return to the Order Processing form and click the Tag Number text box
  4. In the Events section of the Properties window, double-click Leave and implement the event as follows:
     
    System::Void txtTagNumber_Leave(System::Object^  sender, 
    				System::EventArgs^  e)
    {
        CCar ^ selected;
        String ^ strTagNumber = txtTagNumber->Text;
    
        if (strTagNumber->Length == 0)
        {
            MessageBox::Show(L"You must enter the car's tag number");
            txtTagNumber->Focus();
            return;
        }
    
        Dictionary<String ^, CCar ^> ^ lstCars = 
    	gcnew Dictionary<String ^, CCar ^>;
        BinaryFormatter ^ bfmCars = gcnew BinaryFormatter;
    
        String ^ strFilename = L"C:\\Bethesda Car Rental\\Cars.crs";
    
        if( File::Exists(strFilename))
        {
            FileStream ^ stmCars = gcnew FileStream(strFilename,
                                                FileMode::Open,
                                                FileAccess::Read,
                                                FileShare::Read);
            try
            {
                // Retrieve the list of employees from file
                lstCars = 
    		dynamic_cast<Dictionary<String ^, CCar ^> ^>
    		    (bfmCars->Deserialize(stmCars));
                if (lstCars->TryGetValue(strTagNumber, selected))
                {
                    txtMake->Text = selected->Make;
                    txtModel->Text = selected->Model;
                    txtCarYear->Text = selected->Year.ToString();
                }
                else
                {
                    txtMake->Text = L"";
                    txtModel->Text = L"";
                    txtCarYear->Text = L"";
                    MessageBox::Show(L"There is no car with that tag " 
                                     L"number in our database");
                    return;
                }
            }
            finally
            {
                stmCars->Close();
            }
        }
    }
  5. Display the Order Processing form and double-click the Open button
  6. Implement its event as follows:
      
    System::Void btnOpen_Click(System::Object^  sender, System::EventArgs^  e)
    {
        CRentalOrder ^ order = nullptr;
    
        if (txtReceiptNumber->Text == L"")
        {
            MessageBox::Show(L"You must enter a receipt number");
            return;
        }
    
        ReceiptNumber = int::Parse( txtReceiptNumber->Text);
    
        if (lstRentalOrders->TryGetValue(ReceiptNumber, order))
        {
            txtEmployeeNumber->Text = order->EmployeeNumber;
            txtEmployeeNumber_Leave(sender, e);
            cbxOrderStatus->Text = order->OrderStatus;
            txtTagNumber->Text = order->CarTagNumber;
            txtTagNumber_Leave(sender, e);
            txtDrvLicNumber->Text = order->CustomerDrvLicNbr;
            txtCustomerName->Text = order->CustomerName;
            txtCustomerAddress->Text = order->CustomerAddress;
            txtCustomerCity->Text = order->CustomerCity;
            cbxCustomerStates->Text = order->CustomerState;
            txtCustomerZIPCode->Text = order->CustomerZIPCode;
            cbxCarConditions->Text = order->CarCondition;
            cbxTankLevels->Text = order->TankLevel;
            txtMileageStart->Text = order->MileageStart.ToString();
            txtMileageEnd->Text = order->MileageEnd.ToString();
            dtpStartDate->Value = order->DateStart;
            dtpEndDate->Value = order->DateEnd;
            txtDays->Text = order->Days.ToString();
            txtRateApplied->Text = order->RateApplied.ToString(L"F");
            txtSubTotal->Text = order->SubTotal.ToString(L"F");
            txtTaxRate->Text = order->TaxRate.ToString(L"F");
            txtTaxAmount->Text = order->TaxAmount.ToString(L"F");
            txtOrderTotal->Text = order->OrderTotal.ToString(L"F");
        }
        else
        {
            MessageBox::Show(L"There is no rental order with that receipt number");
            return;
        }
    }
  7. Return to the Order Processing form
  8. Double-click the Close button and implement the event as follows:
     
    System::Void btnClose_Click(System::Object^  sender, System::EventArgs^  e)
    {
        Close();
    }
  9. Execute the application
  10. Create a few rental orders and print them
     
    Bethesda Car Rental: Rental Order
    Rental Orders
  11. Close the forms and return to your programming environment
  12. Execute the application again
  13. Open a previously created order to simulate a customer returning a car and change some values on the form such as the return date, the mileage end, and the order status
  14. Also click the Calculate button to get the final evaluation
     
    Bethesda Car Rental: Rental Order
    Bethesda Car Rental - Rental Orders
  15. Print the rental orders
  16. Close the forms and return to your programming environment

Removing Items From a Dictionary Type of List

To delete one item from the list, you can call the Remove() method. Its syntax is:

public:
    virtual void Remove(Object^ key);

Here is an example:

#pragma once

#include <windows.h>

#using <System.dll>
#using <System.Drawing.dll>
#using <System.Windows.Forms.dll>

using namespace System;
using namespace System::Drawing;
using namespace System::Collections;
using namespace System::Windows::Forms;

public ref class CExercise : public Form
{
private:
    ListBox   ^ lbxStudents;
    Button    ^ btnRemove;
    Hashtable ^ Students;

    void InitializeComponent();
    void Start(Object ^ sender, EventArgs ^ e);
	void RemoverClick(Object ^ sender, EventArgs ^ e);

public:
    CExercise();
};

CExercise::CExercise()
{
    InitializeComponent();
}

void CExercise::InitializeComponent()
{
    Text = L"Students";
    Size = Drawing::Size(150, 175);
    StartPosition = FormStartPosition::CenterScreen;
    Load += gcnew EventHandler(this, &CExercise::Start);
	
    lbxStudents = gcnew ListBox;
    lbxStudents->Location = Point(12, 12);
    Controls->Add(lbxStudents);

    btnRemove = gcnew Button();
    btnRemove->Text = L"&Remove";
    btnRemove->Location = Point(12, lbxStudents->Top + lbxStudents->Height + 10);
    btnRemove->Click += gcnew EventHandler(this, &CExercise::RemoverClick);
    Controls->Add(btnRemove);
}

void CExercise::Start(Object ^ sender, EventArgs ^ e)
{
    Students = gcnew Hashtable;

    Students->Add(L"Hermine", "Tolston");
    Students->Add(L"Patrick", "Donley");
    Students->Add(L"Chrissie", "Hannovers");
    Students->Add(L"Patricia", "Herzog");
    Students[L"Michael"] = L"Herlander";
    Students[L"Philemon"] = L"Jacobs";
    Students[L"Antoinette"] = L"Malhoun";

    for each(DictionaryEntry ^ entry in Students)
        lbxStudents->Items->Add(entry->Key + L" " + entry->Value);
}

void CExercise::RemoverClick(Object ^ sender, EventArgs ^ e)
{
    Students->Remove(L"Chrissie");

    lbxStudents->Items->Clear();

    for each(DictionaryEntry ^ entry in Students)
        lbxStudents->Items->Add(entry->Key + L" " + entry->Value);
}

int APIENTRY WinMain(HINSTANCE hInstance,
		     HINSTANCE hPrevInstance,
		     LPSTR lpCmdLine,
		     int nCmdShow)
{
    Application::Run(gcnew CExercise());
    return 0;
}

This would produce:

Hash Table

To delete all items from the list, you can call the Clear() method. Its syntax is:

public:
    virtual void Clear();

Exercises

 

Bethesda Car Rental

  1. Create a Windows Application named BethesdaCarRental1a
    When writing any code that has to do with lists, use a class from the System::Collections namespace
  2. Create a class named Employee that will hold the following pieces of information about each employee: the employee number, the date hired, the first name, the middle name, the last name, the home address, the city, the state, the ZIP (or postal) code, the marital status, the title, the email address, the home telephone number, the cell phone number, the work telephone number, the extension, and the hourly salary
  3. Create a form named EmployeeEditor and design it so that it includes:
    1. A text box for the employee number, the date hired, the first name, the middle name, the last name, the home address, the city, the ZIP (or postal) code, the title, the email address, the home telephone number, the cell phone number, the work telephone number, the extension, and the hourly salary
    2. A combo box for the state (the items must include all US states and all Canadian provinces in 2-letter abbreviation each), the marital status (the items will include Single no Children, Single Parent, Married no Children, Married With Children, Separated, Widow, Other)
  4. Create a form named Employees and that will be used to show some information about each employee of the company, including the employee number, the date hired, the full name (it will consist of the last name, followed by a comma, followed by the first name), the marital status, the title, and the hourly salary.
 
 
   
 

Previous Copyright © 2009-2010 FunctionX, Inc. Home