Home > Blockchain >  Sub classing a TListView relating to question asked 'Prevent action in TListView's context
Sub classing a TListView relating to question asked 'Prevent action in TListView's context

Time:09-14

Relating to Prevent action in TListView's context menu when in edit mode, I'm having an issue reading the value of plvdi->item.pszText in a CNNotify() event. This value should be nil if the edit is cancelled. I tried a few conversions, but no luck. I must be doing something wrong. See the code below.

Everything works, except for comparing the pszText value.

.cpp

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "TCustomListView.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------
// ValidCtrCheck is used to assure that the components created do not have
// any pure virtual functions.
//

static inline void ValidCtrCheck(TCustomListView1 *)
{
    new TCustomListView1(NULL);
}
//---------------------------------------------------------------------------
__fastcall TCustomListView1::TCustomListView1(TComponent* Owner)
    : TListView(Owner)
{
    cancel = false;
}
//---------------------------------------------------------------------------
void __fastcall TCustomListView1::WMGetDlgCode(TMessage &msg)
{
    TCustomListView::Dispatch(&msg);
    msg.Result |= WM_CHAR;
    //To Do
}
//---------------------------------------------------------------------------
void __fastcall TCustomListView1::CNNotify(Winapi::Messages::TWMNotify &Message)
{
    TListView::Dispatch(&Message);
    Message.Result |= LVN_ENDLABELEDIT;

    NMLVDISPINFO* plvdi = (NMLVDISPINFO*)Message.NMHdr;

    if(plvdi->item.pszText == NULL  ) ----->> ??? what am i doing wrong here
    {
        if(FOnEditCancel && IsEditing())
        {
            cancel = true;
            FOnEditCancel(this, this->Selected, cancel);
            cancel = false;
        }
    }
}
//---------------------------------------------------------------------------
namespace Tcustomlistview
{
    void __fastcall PACKAGE Register()
    {
        TComponentClass classes[1] = {__classid(TCustomListView1)};
        RegisterComponents(L"Samples", classes, 0);
    }
}
//---------------------------------------------------------------------------

.h

//---------------------------------------------------------------------------
#ifndef TCustomListViewH
#define TCustomListViewH
//---------------------------------------------------------------------------
#include <System.SysUtils.hpp>
#include <System.Classes.hpp>
#include <Vcl.ComCtrls.hpp>
#include <Vcl.Controls.hpp>

//---------------------------------------------------------------------------
typedef void __fastcall (__closure *TOnEditCancel)(TObject* Sender, TListItem* item, bool cancelled);

class PACKAGE TCustomListView1 : public TListView
{
private:
    TOnEditCancel FOnEditCancel;
    bool cancel;

    MESSAGE void __fastcall WMGetDlgCode(TMessage &msg);
    MESSAGE void __fastcall CNNotify(Winapi::Messages::TWMNotify &Message);

    BEGIN_MESSAGE_MAP
        VCL_MESSAGE_HANDLER(WM_GETDLGCODE, TMessage, WMGetDlgCode)
        VCL_MESSAGE_HANDLER(WM_NOTIFY, TWMNotify, CNNotify);
    END_MESSAGE_MAP(inherited);

protected:

public:
    __fastcall TCustomListView1(TComponent* Owner);

__published:
    __property TOnEditCancel OnEditCancel = {read = FOnEditCancel, write = FOnEditCancel};
};
//---------------------------------------------------------------------------
#endif

CodePudding user response:

There are a lot of issues with your code.

  • TCustomListView1 is not a good name for your component. Use something more meaningful.

  • Your component doesn't need the cancel member at all, so get rid of it. You could just hard-code the cancelled parameter of the OnEditCancel event instead. However, that event really should not have a cancelled parameter to begin with. In which case, removing that parameter, you can then make use of one of the existing ListView event types that matches your remaining parameters, such as TLVNotifyEvent.

  • WM_CHAR is a window message identifier, it is not a valid flag that you can add to the return value of the WM_GETDLGCODE message. You meant to use the DLGC_WANTCHARS flag instead, which will enable your control to receive WM_CHAR messages.

  • LVN_ENDLABELEDIT is a window message identifier, it is not a valid flag that you can add to the return value of the CN_NOTIFY message. You need to check if the Message.NMHdr->code field matches LVN_ENDLABELEDIT and then process the Message.NMHdr field accordingly.

  • you are handling the wrong message in your MESSAGE_MAP. You are catching WM_NOTIFY messages that are sent from the ListView's header control to the ListView. You need to instead catch CN_NOTIFY messages, which are WM_NOTIFY messages that the ListView sends to its parent window and are then reflected back to the ListView as CN_NOTIFY. The VCL does this reflection to allow components to be self-contained and process their own notifications.

With that said, try something more like this instead:

TMyListViewEx.cpp

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "MyListViewEx.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------
// ValidCtrCheck is used to assure that the components created do not have
// any pure virtual functions.
//

static inline void ValidCtrCheck(TMyListViewEx *)
{
    new TMyListViewEx(NULL);
}
//---------------------------------------------------------------------------
__fastcall TMyListViewEx::TMyListViewEx(TComponent* Owner)
    : TListView(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TMyListViewEx::WMGetDlgCode(TMessage &msg)
{
    TListView::Dispatch(&msg);
    msg.Result |= DLGC_WANTCHARS; // TODO
}
//---------------------------------------------------------------------------
void __fastcall TMyListViewEx::CNNotify(Winapi::Messages::TWMNotify &Message)
{
    TListView::Dispatch(&Message);

    if (Message.NMHdr->code == LVN_ENDLABELEDITA ||
        Message.NMHdr->code == LVN_ENDLABELEDITW)
    {
        NMLVDISPINFO *plvdi = reinterpret_cast<NMLVDISPINFO*>(Message.NMHdr);

        if ((plvdi->item.pszText == NULL) &&
            (plvdi->item.iItem != -1) &&
            (FOnCancelEdit != NULL))
        {
            // ideally, you should be using TCustomListView::GetItem(LVITEM)
            // to determine the TListItem affected, but that method is private
            // and not accessible to descendants, which is all the more reason
            // why Embarcadero needs to fix this in the native TListView instead...

            TListItem *item;
            if (plvdi->item.mask & LVIF_PARAM)
                item = reinterpret_cast<TListItem*>(plvdi->item.lParam);
            else // TODO: handle OwnerData=true ...    
                item = this->Items->Item[plvdi->item.iItem];

            FOnCancelEdit(this, item);
        }
    }
}
//---------------------------------------------------------------------------
namespace Tmylistviewex
{
    void __fastcall PACKAGE Register()
    {
        TComponentClass classes[1] = {__classid(TMyListViewEx)};
        RegisterComponents(L"Samples", classes, 0);
    }
}
//---------------------------------------------------------------------------

TMyListViewEx.h

//---------------------------------------------------------------------------
#ifndef TMyListViewExH
#define TMyListViewExH
//---------------------------------------------------------------------------
#include <System.SysUtils.hpp>
#include <System.Classes.hpp>
#include <Vcl.ComCtrls.hpp>
#include <Vcl.Controls.hpp>

//---------------------------------------------------------------------------
class PACKAGE TMyListViewEx : public TListView
{
private:
    TLVNotifyEvent FOnCancelEdit;

    MESSAGE void __fastcall WMGetDlgCode(TMessage &msg);
    MESSAGE void __fastcall CNNotify(Winapi::Messages::TWMNotify &Message);

    BEGIN_MESSAGE_MAP
        VCL_MESSAGE_HANDLER(WM_GETDLGCODE, TMessage, WMGetDlgCode)
        VCL_MESSAGE_HANDLER(CN_NOTIFY, TWMNotify, CNNotify)
    END_MESSAGE_MAP(TListView);

protected:

public:
    __fastcall TMyListViewEx(TComponent* Owner);

__published:
    __property TLVNotifyEvent OnCancelEdit = {read = FOnCancelEdit, write = FOnCancelEdit};
};
//---------------------------------------------------------------------------
#endif
  • Related