ListView_SetView(..,..) not working for a sub classed TlistView is there a way to do this

Viewed 62

Relating to Sub classing a TListView relates also to Prevent action in TListView's context menu when in edit mode

My subclassed ListView captures the ESC key (with Remy's help) and now I managed to capture the Del key when editing, without the side effects of having a menu item's shortcut preventing the Del key from working in the ListView's edit control.

Calling ListView_SetView(ListViewEx1->Handle, LV_VIEW_TILE); works fine on TListView, but not in my TListViewEx. Any ideas would be great. - FIXED

.cpp of TListViewEx

//---------------------------------------------------------------------------

#include <vcl.h>

#pragma hdrstop

#include "TListViewEx.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(TListViewEx *)
{
    new TListViewEx(NULL);
}
//---------------------------------------------------------------------------
__fastcall TListViewEx::TListViewEx(TComponent* Owner)
    : TListView(Owner)
{

}
//---------------------------------------------------------------------------
void __fastcall TListViewEx::Click()
    {
    TListView::Click();
    }
//---------------------------------------------------------------------------
void __fastcall TListViewEx::WMGetDlgCode(TMessage &msg)
    {
    TCustomListView::Dispatch(&msg);
    msg.Result |= DLGC_WANTCHARS;
    }
//---------------------------------------------------------------------------
void __fastcall TListViewEx::CNKeyDown(TMessage &Message)
    {
    if (Message.WParam == VK_DELETE)
        {
        if(FOnWantEditEvent)
            {
            FEdit = ListView_GetEditControl(Handle);
            OnWantEditEvent(this, FKey);
            }
        }
    TListView::Dispatch(&Message);
    }
//---------------------------------------------------------------------------
void __fastcall TListViewEx::SetView(int view)
    {
    ListView_SetView(Handle, view);
    }
//---------------------------------------------------------------------------
void __fastcall TListViewEx::CNNotify(Winapi::Messages::TWMNotify &Message)
    {
//    TListView::Dispatch(&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) && (FOnEditCancel != 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];

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

.h

//---------------------------------------------------------------------------

#ifndef TListViewExH
#define TListViewExH
//---------------------------------------------------------------------------
#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);
typedef void __fastcall (__closure *TOnWantEditEvent)(TObject* Sender, WORD &Key);


class PACKAGE TListViewEx : public TListView
    {
    private:
        TOnEditCancel FOnEditCancel;
        TOnWantEditEvent FOnWantEditEvent;
        HWND FEdit;

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

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


    protected:
        DYNAMIC void __fastcall Click();

    public:
        __property HWND Edit = {read = FEdit};
        __fastcall TListViewEx(TComponent* Owner);
        void __fastcall SetView(int view);

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

TForm implementation of FOnWantEditEvent, based on code by Martin Nijhoff:

.cpp

void __fastcall TForm3::ListViewEx1WantEditEvent(TObject *Sender, Word &Key)
    {
    switch(Key)
        {
        case VK_DELETE:
            {
            if(ListViewEx1->Edit)
                {
                UINT Scan = MapVirtualKey(VK_DELETE, MAPVK_VK_TO_VSC);  // Get scan code of Del key.
    
                SendMessage(ListViewEx1->Edit, WM_KEYDOWN, VK_DELETE, 0x00000001 | Scan << 16);  // Simulate Del keypress in edit window.
                SendMessage(ListViewEx1->Edit, WM_KEYUP,   VK_DELETE, 0xC0000001 | Scan << 16);
                }
            }
            break;
        }
    }
//-------------------------------------------------------------------------    
void __fastcall TForm3::Delete1Click(TObject *Sender)
    {
    if(!ListViewEx1->IsEditing())
        ListViewEx1->DeleteSelected();

    ViewTile();
    }
1 Answers

TListView is not the culprit where pressing Del or any of the other keyboard keys A through Z key in the Edit box of a list view item, it is an unintended consequence in an action by the Aplications processing its message queue.

However, TMenuItem does play a roll in this issue, the TMenuItem ShortCut property list is filled with what appears to be the valid keys and combination of keys allowed but, for some reason, it allows a short cut key consisting of single alpha character keys and other keys not intended to be used without a TShiftState key like Del or Esc and so on.

Popup menu item with single letter shortcut

What appears to be happening is that the Applications message queue receives a CNKeyDown message, it then calls the IsMenuKey which asks the control's popup menu if it wants to handle the key, if it does, IsShortCut method is called, and fires the OnShortcut event which should NOT have been called because the shortcut key assigned was not intended to be used as a valid shortcut key.

I believe this problem exists in all versions of C++ Builder, the only real way around this problem is to make sure that shortcut keys are selected from the TMenuItem's Shortcut property list.

My tests confirm that a single character A as well as Del keys press in a TListView Edit control was intercepted by TAplication which eventually fired the Click() event of the TMenuItem assigned the short cut 'A', the consequences of this can be varied and unintentional and could be devastating in a critical Database type operation especially when the intent of the menu item is to carry out a delete operation without confirmation.

This is surely a bug that needs to be looked at by Embarcadero

Related