Programmieren mit C++

Visual C++

ListView Control

Spaltenzugriffe in ListView Controls

Wie kann man die Spalte einer ListView Control ermitteln, in der ein Element angeklickt wurde, da die Methode HitTest() offenbar nicht immer zum Ergebnis führt?

Frage

Die Methode HitTest() der Klasse CListCtrl liefert lediglich den Zeilenindex des Eintrags unter dem Mauscursor, sofern der Punkt auf einem Eintrag liegt. Allerdings schlägt diese Funktion fehl, wenn der zu testende Punkt nicht direkt über der ersten Spalte liegt.

Eine sichere Methode zum Ermitteln der korrekten Spalte besteht darin, über GetItemRect() das umhüllende Rechteck jeder sichtbaren Zeile der ListView Control zu bestimmen, bis die Zeile gefunden ist, über der der Y-Wert des zu testenden Punkts liegt, was sich mittels PtInRect() prüfen läßt.

Ist die betroffene Zeile bekannt, kann die Spalte gefunden werden, indem für jede Spalte über GetColumnWidth() die Breite ermittelt und zusammen mit den Höhenkoordinaten der Zeile zu einem Rechteck zusammengefaßt wird, das dann als Grundlage für den Test des Klickpunkts verwendet werden kann.

Die hier vorgestellte Methode HitTestEx() bestimmt zu einem gegebenen Punkt den zugehörigen Zeilen- und Spaltenindex, wobei der Zeilenindex als Funktionsergebnis und der Spaltenindex im Parameter col returniert wird. Liegt der Punkt nicht über einer Zeile, resultiert der Fehlerwert „-1“ und die Spalte „0“.

Lösung

int CMyListCtrl::HitTestEx(CPoint &point, 
                           int *col) const
{
 int colnum = 0;
 int row = HitTest( point, NULL );
 
 if( col ) *col = 0;

Voraussetzung ist, daß sich die ListView im Report-Modus befindet. Ist dies nicht der Fall, returniert die Funktion per Default das Ergebnis der Methode HitTest().

  if ((GetWindowLong(m_hWnd, GWL_STYLE) & 
                     LVS_TYPEMASK) != LVS_REPORT )
    return row;

Die obere und untere sichtbare Zeile der ListView Control ergibt sich über die Aufrufe:

  row = GetTopIndex();
  int bottom = row + GetCountPerPage();
  if( bottom > GetItemCount() )
    bottom = GetItemCount();

Die Anzahl der Spalten ergibt sich anhand:

  CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
  int nColumnCount = pHeader->GetItemCount();

Über die Zeilen und Spalten kann nun in einer geschachtelten Schleife jeder Eintrag der ListView Control daraufhin untersucht werden, ob der zu testende Punkt im Bereich des Eintrags liegt.

  for( ;row <=bottom;row++)
  {
    CRect rect;
    GetItemRect( row, &rect, LVIR_BOUNDS );
    if( rect.PtInRect(point) )
    {
      for (colnum = 0; 
           colnum < nColumnCount; 
           colnum++ )
      {
       int colwidth = GetColumnWidth(colnum); 
       if (point.x >= rect.left  && 
           point.x <= (rect.left + colwidth))
       {
         if ( col ) 
           *col = colnum;
         return row;
       }
       rect.left += colwidth;
      }
    }
  }
  return -1;
}

Die Funktion HitTestEx() kann nur dann eine gültige Spalte returnieren, wenn auch die Zeile gültig ist. Aus diesem Grund genügt es, zunächst das Funktionsergebnis auf ungleich „-1“ zu testen. Liegt eine gültige Zeile vor, ist auch die Spalte gültig.

Methode HitTestEx

Wie kann das Anklicken von Spaltenköpfen einer ListView Control unterdrückt werden, deren zugehörige Spalten nicht sortierbar sind?

Frage

Die nachfolgend aufgezeigte Vorgehensweise ist eine von mehreren möglichen. Zunächst wird im Körper der Klassendeklaration ein Prototyp und eine Variable für eine Callback-Funktion deklariert, deren Aufgabe darin besteht zu prüfen, ob die entsprechende Spalte des Header angeklickt werden darf oder nicht.

public:
  typedef BOOL (*ColumnCheckF)(void *,int,LPARAM);
  void SetCallback(ColumnCheckF checkf=NULL, 
                   void * cbdata=NULL)
  {
    m_fColumnCheck=checkf;
    m_pCBData=cbdata;
  }
 
private:
  ColumnCheckF m_fColumnCheck;
  void *m_pCBData;

Nach einem Klick auf den Header der ListView Control wird die betroffene Spalte ermittelt und über die Callback-Funktion abgefragt, ob der Klick erlaubt ist oder nicht. Im Header der Klassendeklaration ist hinzuzufügen:

afx_msg void OnLButtonDown(UINT,CPoint);
DECLARE_MESSAGE_MAP()

Analog dazu wird in der CPP-Datei hinzugefügt:

BEGIN_MESSAGE_MAP(CCustHeaderCtrl, CHeaderCtrl) 
   ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

Die eigentliche Implementation erfolgt in der Behandlungsmethode des Ereignisses OnLButtonDown. Hier ist insbesondere auch sicherzustellen, daß neben dem Klick nicht auch das Ändern der Spaltenbreite unterdrückt wird.

void CCustHeaderCtrl::OnLButtonDown(UINT flags, CPoint pt)
{
 if (m_fColumnCheck!=NULL)
 {
  HD_ITEM hditem;
  CClientDC dc(this);
  int offset=dc.GetTextExtent(_T(" "),1).cx*2;
  int x_coord=pt.x;
 
  int column=0;
  hditem.mask=HDI_WIDTH;
  while (x_coord>((column==0)?0:offset))
  {
   GetItem(column,&hditem);

Liegt die X-Koordinate des Klickpunkts in der Spalte, wird die Callback-Funktion aufgerufen, um festzustellen, ob der Klick erlaubt ist oder nicht. Ist der Klick nicht erlaubt, erfolgt über return ein Rücksprung aus dem Handler, so daß insbesondere das OnLButtonDown-Ereignis der Header Control nicht mehr aufgerufen wird.

   if (x_coord

Wurde der Klick erlaubt oder keine betroffene Spalte gefunden, erfolgt abschließend der Aufruf des OnLButtonDow-Handlers der Header Control.

 CHeaderCtrl::OnLButtonDown(flags,pt);
}

Als Nebeneffekt bringt dieser Code mit sich, daß die für das Klicken deaktivierten Spalten nicht mehr verschoben werden können, da die Header Control das OnLButtonDown-Ereignis nicht mehr erhält.

Lösung

Was ist zu tun, damit ein Eintrag einer ListView Control editiert werden kann?

Frage

Damit ein Eintrag einer ListView Control editierbar ist, muß der Stil LVS_EDITLABELS gesetzt sein. Dies kann bereits beim Erzeugen der Control erfolgen, aber auch später über die Funktion ModifyStyle().

Ist der LVS_EDITLABELS gesetzt, kann der Anwender einen Eintrag fokusieren und editieren. Allerdings werden damit Änderungen nicht automatisch übernommen, denn das Standardverhalten der Control ignoriert den Abschluß von Änderungen. Um trotzdem Änderungen zu ermöglichen, muß die Benachrichtigung LVN_ENDLABELEDIT bearbeitet werden. Dies kann beispielsweise in einer abgeleiteten Klasse der ListView Control erfolgen, indem der Handler immer TRUE returniert.

void CMyListCtrl::OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
{
     *pResult = TRUE;
}

Returniert die Behandlungsmethode als Funktionsergebnis FALSE, werden Änderungen ignoriert.

Die hier gezeigte Vorgehensweise funktioniert jedoch nur für Änderungen der Haupteinträge. Das Ändern von Untereinträgen erfordert ein komplexeres Handling.

Lösung





Sachgebiet


© 2009-2012 by Alojado Publishing. Alle Rechte vorbehalten. Ausgewiesene Marken gehören ihren jeweiligen Eigentümern.
Mit der Benutzung dieser Seite erkennen Sie die Nutzungsbedingungen und die Datenschutzerklärung an. Der Betreiber übernimmt keine Haftung für den Inhalt verlinkter externer Internetseiten.
Seite erzeugt 2012-05-20 03:15:43 von textarchiv.alojado.de