AlwaysLearningNewStuff
AlwaysLearningNewStuff

Reputation: 3031

Populate treeview with recordset

I have a dialog box that behaves as a child of the main window. Dialog box has treeview control. They are created using resource editor and WinAPI.

I am using ADO and C++ to populate treeview with some data from database. Unfortunately, I do not get the expected result.

To understand my problem, I will provide the description of the columns in the database table I get data from:

Here are the relevant values for the above table:

ID | BrojUgovora
-----------------
 1 | qwert
 2 | prvi ugovor
 3 | drugi ugovor

After I run my code, I expect to get following result: drugi ugovor prvi ugovor qwert

but I get this:

drugi ugovor
prvi ugovor
ID           -------> why column name instead of column value???

When I comment out the part where I store ID into treeview node via lParam, I get proper result.

I need to store the ID into lParam, so deleting that part of my code is not an option.


Here is the function I call in WM_INITDIALOG:

/*********** REMARKS ***************
/**** Fills treeview control with the contract number 
/**** In treeview node's LPARAM is stored the value of the primary key
/**** Returns the number of failed attempts to load string/autonumber field
/***********************************/

int InitTreeView(HWND hDlg)
{
    // error result
    int iNumberOfFailedLoads = 0;

    //connect to database 
    ADODB::_ConnectionPtr pConn("ADODB.Connection");
    try
    {
        HRESULT hr = pConn->Open(bstrConnect, username, password, 
            ADODB::adConnectUnspecified);

        if (!SUCCEEDED(hr))
            throw _com_error(hr);

        ADODB::_CommandPtr pCmd("ADODB.Command");

        pCmd->ActiveConnection = pConn;
        pCmd->CommandType = ADODB::adCmdText;
        pCmd->CommandText = L" select ID, BrojUgovora from UGOVORI;";

        ADODB::_RecordsetPtr pRS = pCmd->Execute(NULL, NULL, ADODB::adCmdText);

        ADODB::Fields* pFields = NULL;

        hr = pRS->get_Fields(&pFields);

        if (!SUCCEEDED(hr))
            throw _com_error(hr);

        if (pFields && pFields->GetCount() > 0)
        {
            while (!pRS->AdoNSEOF)
            {
                // load contract values into treeview
                TVINSERTSTRUCT tvis = { 0 };
                tvis.item.mask = TVIF_TEXT | TVIF_PARAM;
                tvis.hInsertAfter = TVI_FIRST;
                tvis.item.pszText = // this is string field
                    pRS->Fields->GetItem(L"BrojUgovora")->Value.bstrVal;
                tvis.item.lParam =  // this is autonumber field
                    (LPARAM)(pRS->Fields->GetItem(L"ID")->Value.lVal);

                HTREEITEM hti = TreeView_InsertItem(GetDlgItem(hDlg, IDC_TREE1), 
                    &tvis);

                if (NULL == hti)
                    iNumberOfFailedLoads++;

                pRS->MoveNext();
            }
            pRS->Close();
        }

        pConn->Close();
    }
    catch (_com_error &e)
    {
        if (pConn->State == ADODB::adStateOpen)
            pConn->Close();

        iNumberOfFailedLoads = -1;
    }

    return iNumberOfFailedLoads;
}

Here is WM_INITDIALOG handler:

case WM_INITDIALOG:
{
    // needed for visual styles, long story
    EnableThemeDialogTexture(hDlg, ETDT_ENABLETAB);
    InitTreeView(hDlg);
}
    return (INT_PTR)FALSE;

DEBUGGING RESULTS:

Here is what I get from the debugger:

/*********** REMARKS ***************
/**** Fills treeview control with the contract number 
/**** In treeview node's LPARAM is stored the value of the primary key
/**** Returns the number of failed attempts to load string/autonumber field
/***********************************/

int InitTreeView(HWND hDlg)
{
    // error result
    int iNumberOfFailedLoads = 0;

    //connect to database 
    ADODB::_ConnectionPtr pConn("ADODB.Connection");
    try
    {
        HRESULT hr = pConn->Open(bstrConnect, username, password, 
            ADODB::adConnectUnspecified);

        if (!SUCCEEDED(hr))
            throw _com_error(hr);

        ADODB::_CommandPtr pCmd("ADODB.Command");

        pCmd->ActiveConnection = pConn;
        pCmd->CommandType = ADODB::adCmdText;
        pCmd->CommandText = L" select ID, BrojUgovora from UGOVORI;";

        ADODB::_RecordsetPtr pRS = pCmd->Execute(NULL, NULL, ADODB::adCmdText);

        ADODB::Fields* pFields = NULL;

        hr = pRS->get_Fields(&pFields);

        if (!SUCCEEDED(hr))
            throw _com_error(hr);

        if (pFields && pFields->GetCount() > 0)
        {
            while (!pRS->AdoNSEOF)
            {
                // load contract values into treeview
                TVINSERTSTRUCT tvis = { 0 };
                tvis.item.mask = TVIF_TEXT | TVIF_PARAM;
                tvis.hInsertAfter = TVI_FIRST;
                tvis.item.pszText = 
                    pRS->Fields->GetItem(L"BrojUgovora")->Value.bstrVal;
                tvis.item.lParam =  // here is first breakpoint
                    (LPARAM)(pRS->Fields->GetItem(L"ID")->Value.lVal);

                // MessageBeep(0) added just so I can put breakpoint there
                MessageBeep(0);  // here is second breakpoint

                HTREEITEM hti = TreeView_InsertItem(GetDlgItem(hDlg, IDC_TREE1), 
                    &tvis);

                if (NULL == hti)
                    iNumberOfFailedLoads++;

                pRS->MoveNext();
            }
            pRS->Close();
        }

        pConn->Close();
    }
    catch (_com_error &e)
    {
        if (pConn->State == ADODB::adStateOpen)
            pConn->Close();

        iNumberOfFailedLoads = -1;
    }

    return iNumberOfFailedLoads;
}

When I reach first breakpoint, string is correctly read from the database.

On second breakpoint, it "magically" changes to ID.

MY EFFORTS TO SOLVE THIS:

After changing the code in the while loop like below, everything works:

while (!pRS->AdoNSEOF)
{
    wchar_t txt[50];
    memset(txt, L'\0', sizeof(txt));
    swprintf_s(txt, 50, L"%s",
        pRS->Fields->GetItem(L"BrojUgovora")->Value.bstrVal);

    //...
    tvis.item.pszText = txt;
    // the rest of the code is the same

I have tried to use dynamic string to store the string from database, but have failed. I get debug assertion error. The same happens when I try to use wstring.

QUESTION:

How should I rewrite my InitTreeView function to avoid bug I described above?

Upvotes: 0

Views: 325

Answers (1)

Jonathan Potter
Jonathan Potter

Reputation: 37192

I suspect that the GetItem method is returning a temporary value which is being lost once the function returns; so the string that pszText ends up pointing to has been freed/overwritten.

Try the following and see if it makes a difference:

            // load contract values into treeview
            _variant_t varText = pRS->Fields->GetItem(L"BrojUgovora")->Value;
            _variant_t varID = pRS->Fields->GetItem(L"ID")->Value;

            TVINSERTSTRUCT tvis = { 0 };
            tvis.item.mask = TVIF_TEXT | TVIF_PARAM;
            tvis.hInsertAfter = TVI_FIRST;
            tvis.item.pszText = varText.bstrVal;
            tvis.item.lParam =  varID.lVal;

Upvotes: 1

Related Questions