// AddIxDlg.cpp : implementation file for dialog that lets user add // indexes to tabledefs // // This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992-1998 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and related // electronic documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include "stdafx.h" #include "DAOTable.h" #include "listctrl.h" #include "AddIxDlg.h" #include "tabledef.h" #include "index.h" #include "field.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CAddIndexDlg dialog // default constructor CAddIndexDlg::CAddIndexDlg(CWnd* pParent /*=NULL*/) : CDialog(CAddIndexDlg::IDD, pParent) { // call centralized initialization function initializer(); } // constructor that will generally be used to create // the dialog--parameters are needed to do any useful // database operations // // IN: pDatabase--pointer to the open database object // IN: strTableName--name of the tabledef to which to add indexes // IN: pParent--pointer to parent of this dialog CAddIndexDlg::CAddIndexDlg(CDaoDatabase *pDatabase, CString strTableName, CWnd* pParent) : CDialog(CAddIndexDlg::IDD, pParent) { // call centralized initialization function initializer(); // initiliaze and set members to incoming parameters m_pTableDef = NULL; m_pDatabase = pDatabase; m_strTableName = strTableName; } // centralized initialization function void CAddIndexDlg::initializer() { // initialize the index info struct indexInitializer(); // by default, DDV functions are called in the DoDataExchange m_bCheckDDV = TRUE; // collection index starts at zero m_nIndexIndex = 0; } // initialize the index info struct members and equivalent // class members void CAddIndexDlg::indexInitializer() { //{{AFX_DATA_INIT(CAddIndexDlg) //}}AFX_DATA_INIT // index info struct m_II.m_strName = _T(""); m_II.m_bPrimary = FALSE; m_II.m_bIgnoreNulls = FALSE; m_II.m_bRequired = FALSE; m_II.m_bUnique = FALSE; // clear out the list of fields in case it is not empty-- // this is an array of index field info structs that are // the fields that make up the index if (m_II.m_pFieldInfos != NULL) { delete [] m_II.m_pFieldInfos; m_II.m_pFieldInfos = NULL; m_II.m_nFields = 0; } } void CAddIndexDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAddIndexDlg) DDX_Control(pDX, IDC_UNIQUE, m_UniqueControl); DDX_Control(pDX, IDC_REQUIRED, m_RequiredControl); DDX_Control(pDX, IDC_IGNORE_NULLS, m_IgnoreNullControl); DDX_Control(pDX, IDC_PRIMARY, m_PrimaryControl); DDX_Text(pDX, IDC_TABLE_NAME, m_strTableName); //}}AFX_DATA_MAP // these have to be moved outside of the wizard block // since they directly map to members of the index info // struct DDX_Check(pDX, IDC_PRIMARY, m_II.m_bPrimary); DDX_Check(pDX, IDC_IGNORE_NULLS, m_II.m_bIgnoreNulls); DDX_Check(pDX, IDC_REQUIRED, m_II.m_bRequired); DDX_Check(pDX, IDC_UNIQUE, m_II.m_bUnique); DDX_Text(pDX, IDC_INDEX_NAME, m_II.m_strName); // moved outside of wizard block since DDV is used DDX_Control(pDX, IDC_FIELD_LIST, m_FieldListListControl); // conditionally check validity of input if (m_bCheckDDV) { // user must select at least one field for the index DDV_NoSel(pDX, &m_FieldListListControl); } } // check if a field selection was made--at least one field must be // selected. Selected fields contain a non-null string in their // first sub-item (i.e. "ascending" or "descending") void CAddIndexDlg::DDV_NoSel(CDataExchange* pDX, CListCtrl *theControl) { // only process if transferring to member from control. If // no selection has been made, this constitutes an error condition if (pDX->m_bSaveAndValidate) { // check if any items selected--initialize a counter int count = 0; // for as many items in the list view, keep checking until one is // found that has been selected for (int i = 0; (count == 0) && (i < theControl->GetItemCount()); i++) { // the contents of the subitem determines selection state if (theControl->GetItemText(i, 1) != _T("")) { // up the count by one to end the loop count += 1; } } // if no selection, then error! if (count == 0) { AfxMessageBox(_T("You must select a field."), MB_ICONEXCLAMATION); pDX->m_hWndLastControl = theControl->m_hWnd; pDX->Fail(); } } } BEGIN_MESSAGE_MAP(CAddIndexDlg, CDialog) //{{AFX_MSG_MAP(CAddIndexDlg) ON_BN_CLICKED(IDOK, OnDone) ON_WM_CLOSE() ON_BN_CLICKED(IDC_ADD_INDEX, OnAddIndex) ON_BN_CLICKED(IDC_DELETE_INDEX, OnDeleteIndex) ON_BN_CLICKED(IDC_NEXT_INDEX, OnNextIndex) ON_BN_CLICKED(IDC_PREVIOUS_INDEX, OnPreviousIndex) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CAddIndexDlg message handlers BOOL CAddIndexDlg::OnInitDialog() { CDialog::OnInitDialog(); // do some initialization for the list control // // set style so that list view will show its selections even when the // control loses focus LONG style = ::GetWindowLong(m_FieldListListControl.m_hWnd,GWL_STYLE); ::SetWindowLong(m_FieldListListControl.m_hWnd, GWL_STYLE,style | LVS_SHOWSELALWAYS); // get size of control to help set the column widths CRect controlRect; m_FieldListListControl.GetClientRect(controlRect); // // get width of control, width of potential scrollbar, width needed for sub-item // string int controlWidth = controlRect.Width(); int scrollThumbWidth = ::GetSystemMetrics(SM_CXHTHUMB); int strWidth = m_FieldListListControl.GetStringWidth(_T("descending")); // factor the border width into the string width strWidth += 12 * ::GetSystemMetrics(SM_CXBORDER); // // set up columns--1st leaves room for second and scroll bar m_FieldListListControl.InsertColumn(1, _T("Field"), LVCFMT_LEFT, controlWidth - scrollThumbWidth - strWidth, 1); m_FieldListListControl.InsertColumn(2, _T("Sort"), LVCFMT_LEFT, strWidth + scrollThumbWidth, 2); // open the tabledef if possible if(openTableDef(m_pDatabase, &m_pTableDef, m_strTableName)) { // now populate the list of fields list control with fields populateFieldList(); // get the information for the first index in the collection of indexes // last parameter specifies no error reporting--run silently since // there may be no indexes yet if(getIndexInfo(m_pTableDef, &m_II, m_nIndexIndex, FALSE)) { // most properties are readonly once an index is in a collection disableControlsForExisting(TRUE); // list control should reflect the fields in the index setFieldListSelections(); UpdateData(FALSE); } } // set focus to index name control unless there is some problem (unlikely) CEdit *pEdit = (CEdit *)GetDlgItem(IDC_INDEX_NAME); if (pEdit != NULL) { pEdit->SetFocus(); return FALSE; } else return TRUE; // return TRUE unless you set the focus to a control } // user selected to move to the next index in the collection void CAddIndexDlg::OnNextIndex() { // get values from control -- no validity checking m_bCheckDDV = FALSE; // turn validity checking off UpdateData(TRUE); m_bCheckDDV = TRUE; // turn checking back on--don't forget this! // action depends on if this is an existing index or not // if this is a new index (based on name) see if user wants to save it // or not--if not, then try to move to next index if ((m_II.m_strName != _T("")) && (!IsExistentIndex(m_pTableDef, m_II.m_strName))) if (IDYES != AfxMessageBox(_T("Current index will be ignored unless added. Continue anyway?"), MB_YESNO)) return; // can only move next if there is a item at the current index in the // collection--else you keep indexing into unused parts of the index. // So, check for existence of current indexed item and only increment // the index if there is an item (no error reporting) if (getIndexInfo(m_pTableDef, &m_II, m_nIndexIndex, FALSE)) // move to next field index m_nIndexIndex += 1; // get the information for the next index if possible-- // last parameter indicates no failure reporting if (getIndexInfo(m_pTableDef, &m_II, m_nIndexIndex, FALSE)) { // list control should be cleared and then set to new values for this index setFieldListSelections(FALSE); // most properties are readonly once a field is in a collection disableControlsForExisting(TRUE); } else { // initialize field property values for new field indexInitializer(); // list box should be cleared setFieldListSelections(TRUE); // new field have read/write properties disableControlsForExisting(FALSE); } UpdateData(FALSE); // set focus to name of field edit box CEdit *pEdit = (CEdit *)GetDlgItem(IDC_INDEX_NAME); pEdit->SetFocus(); } // user selected to more to the previous index in the collection void CAddIndexDlg::OnPreviousIndex() { // can't go previous if at 0th index if (m_nIndexIndex >= 1) { // by default, we will move previous int retCode = IDYES; // if user has entered an index name, then warn them they will lose // it if it is not explicitly added--we don't want to do an auto-add // since user may have entered name without meaning to add another index // // see if there is a index name specified CString name; CEdit *pEdit = (CEdit *)GetDlgItem(IDC_INDEX_NAME); pEdit->GetWindowText(name); // if there is a name, then warn the user if (name.GetLength() != 0) { // only an issue if this is not an existing index if (!IsExistentIndex(m_pTableDef, name)) retCode = AfxMessageBox(_T("Current index will be ignored unless added. Continue anyway?"), MB_YESNO); } // either there never was a field name specified or the user has // chosen not to add the field if (retCode == IDYES) { // move previous m_nIndexIndex -= 1; // check for this item by index, no error reporting // if found, display info, if not treat as new index if (getIndexInfo(m_pTableDef, &m_II, m_nIndexIndex, FALSE)) { // list ctrl should be cleared and then set to new values for this index setFieldListSelections(FALSE); // most properties are readonly once a field is in a collection disableControlsForExisting(TRUE); } else { // initialize field property values for new field indexInitializer(); // list box should be cleared setFieldListSelections(TRUE); // new field have read/write properties disableControlsForExisting(FALSE); } // update the dialog controls with new values UpdateData(FALSE); } } // set focus to name of field edit box CEdit *pEdit = (CEdit *)GetDlgItem(IDC_INDEX_NAME); pEdit->SetFocus(); } // user selected the "done" button--no more indexes to add/view. The only // risk is losing an index that is under construction--check for that void CAddIndexDlg::OnDone() { // set default to "ready to exit dialog" int retCode = IDYES; // if user has entered an index name, then warn them they will lose // it if it is not explicitly added--we don't want to do an auto-add // since user may have entered name without meaning to add another index // // see if there is a index name specified CString name; CEdit *pEdit = (CEdit *)GetDlgItem(IDC_INDEX_NAME); pEdit->GetWindowText(name); // if there is a name, then warn the user if (name.GetLength() != 0) { // only an issue if this is not an existing index if (!IsExistentIndex(m_pTableDef, name)) retCode = AfxMessageBox(_T("Current index will be ignored unless added. Continue anyway?"), MB_YESNO); } // either there never was a index name specified or the user has // chosen not to add the index if (retCode == IDYES) { // done with tabledef object delete m_pTableDef; // end the dialog CDialog::EndDialog(0); } } // user selected to exit the dialog--don't do any // checking for loss of newly specified indexes void CAddIndexDlg::OnClose() { // close the tabledef if it is open if (m_pTableDef->IsOpen()) m_pTableDef->Close(); // clean up delete m_pTableDef; CDialog::OnClose(); } // user wants to add the specified index to the collection void CAddIndexDlg::OnAddIndex() { // get values from control -- don't continue if failure if (!UpdateData(TRUE)) return; // don't do anything if this is an existing index (except // say so) if (!IsExistentIndex(m_pTableDef, m_II.m_strName)) { // must create the fields that are in the index createFieldArray(&(m_II.m_pFieldInfos), &(m_II.m_nFields)); // try to create the index with error checking--may fail if a // duplicate named index already exists. Note: creating an index // also appends it to the tabledef's index collection if (!createNewIndex(m_pTableDef, &m_II)) return; // clean out all properties indexInitializer(); // list constrol should be cleared setFieldListSelections(TRUE); // performs visible clearing of controls that are initialized UpdateData(FALSE); // move to next field index m_nIndexIndex += 1; } else { AfxMessageBox(_T("Can't add index--it already exists in the TableDef.")); } // set focus to name of index edit box CEdit *pEdit = (CEdit *)GetDlgItem(IDC_INDEX_NAME); pEdit->SetFocus(); } // user selected to delete the current index--prompt for acceptance void CAddIndexDlg::OnDeleteIndex() { // get values from control -- don't continue if failure if (!UpdateData(TRUE)) return; // can only delete existing indexes if (IsExistentIndex(m_pTableDef, m_II.m_strName)) { // is user sure? if (IDYES == AfxMessageBox(_T("Delete current index?"), MB_YESNO)) { // only react if field is deleted! if (deleteIndex(m_pTableDef, m_II.m_strName)) { // index into collection is unchanged, so // get the information for this index if there is one // (no error reporting) if (getIndexInfo(m_pTableDef, &m_II, m_nIndexIndex, FALSE)) { // list control should be cleared and then set to reflect new index setFieldListSelections(FALSE); // disable user selections disableControlsForExisting(TRUE); } else { // there is no index in collection following the // deletion at this collection index, so // set the index info to initial state indexInitializer(); // list control should be cleared setFieldListSelections(TRUE); // enable user selections disableControlsForExisting(FALSE); } // update the dialog controls to erase deleted field UpdateData(FALSE); } } } // set focus to name of field edit box CEdit *pEdit = (CEdit *)GetDlgItem(IDC_INDEX_NAME); pEdit->SetFocus(); } // since most index properties become read-only once the index // is added to a collection, manage the controls on the dialog // appropriate to whether this is an existing index or not void CAddIndexDlg::disableControlsForExisting(BOOL bDisable/* = TRUE*/) { m_UniqueControl.EnableWindow(!bDisable); m_RequiredControl.EnableWindow(!bDisable); m_IgnoreNullControl.EnableWindow(!bDisable); m_PrimaryControl.EnableWindow(!bDisable); // set the name edit to read-only CEdit *pEdit = (CEdit *)GetDlgItem(IDC_INDEX_NAME); pEdit->SetReadOnly(bDisable); } // populate the list control with the fields in the field collection of // the current tabledef void CAddIndexDlg::populateFieldList() { // struct for getting field information CDaoFieldInfo fieldInfo; // loop controls and index into the field collection BOOL bContinue = TRUE; int itemIndex = 0; // until we run out of items in the field collection, keep adding items for (int i = 0; bContinue; i++) { // try to get info on the ith field -- no error reporting as specified by last // parameter being FALSE if (getFieldInfo(m_pTableDef, &fieldInfo, i, FALSE)) { // if this is an indexable field type, then display it if ((fieldInfo.m_nType != dbLongBinary) && (fieldInfo.m_nType != dbMemo)) { // insert the name into the list view item, clear the subitem text m_FieldListListControl.InsertItem(itemIndex, fieldInfo.m_strName); m_FieldListListControl.SetItemText(itemIndex, 1, _T("")); // move to next index into the collection itemIndex += 1; } } else // once we fail to get info on an item in the collection, stop the loop bContinue = FALSE; } } // for every item selected in the list control, create an index field info // struct with the pertinent info and add it to the list of such // structures void CAddIndexDlg::createFieldArray(CDaoIndexFieldInfo **ppFields, short *pnFields) { // get maximum count of items in the list control int limit = m_FieldListListControl.GetItemCount(); // if array isn't initialized, delete it, then allocate a new one if ((*ppFields) != NULL) delete [] (*ppFields); // allocate at maximum size although we probably won't use all // just for simplicity's sake! (*ppFields) = new CDaoIndexFieldInfo[limit]; // keep track of how many items selected and loop through all items (*pnFields) = 0; for (int i = 0; i < limit; i++) { // if selected, get the name of the field (selection indicated by // non-empty subitem string) if (m_FieldListListControl.GetItemText(i, 1) != _T("")) { // put the name into the array (*ppFields)[(*pnFields)].m_strName = m_FieldListListControl.GetItemText(i, 0); // put the sort order into the array as a boolean (*ppFields)[(*pnFields)].m_bDescending = (m_FieldListListControl.GetItemText(i, 1) == _T("descending")); // up the count by one (*pnFields) += 1; } } } // make the list control reflect which fields make up the current index // // IN: bJustClear-by default, clear AND make selection in the list control, but // you can specify to just clear the control of all selections // void CAddIndexDlg::setFieldListSelections(BOOL bJustClear /* = FALSE*/) { // get maximum number of items in list int numStrings = m_FieldListListControl.GetItemCount(); int selection; // reset any current selections for (int i = 0; i < numStrings; i++) { m_FieldListListControl.SetItemState(i, 0, LVIS_SELECTED); m_FieldListListControl.SetItemText(i, 1, _T("")); } // this function can be called to simply clear the list control // of selections or you can reflect the current index selections too if (!bJustClear) { // select the appropriate items--set up the finder struct LV_FINDINFO lvfi; lvfi.flags = LVFI_STRING; // searching for strings CString strSort; // used to create string to indicate sort direction // for all items in the index info struct, find and set the items for (int i = 0; i < m_II.m_nFields; i++) { // find the item in the already populated list control--start from the top lvfi.psz = m_II.m_pFieldInfos[i].m_strName; selection = m_FieldListListControl.FindItem(&lvfi, -1); // select the item m_FieldListListControl.SetItemState(selection, LVIS_SELECTED, LVIS_SELECTED); // set the sort order subitem text appropriately strSort = m_II.m_pFieldInfos[i].m_bDescending ? _T("descending") : _T("ascending"); m_FieldListListControl.SetItemText(selection, 1, strSort); } UpdateData(FALSE); } }