LVM_GETCOLUMNORDERARRAY message liefert Fehlschlag zurück
-
Heyho,
es ist schwer für meine Frage das richtige Unterforum zu wählen, da es eine Mischung aus C# und MFC ist, aber ich dachte ich poste erstmal hier, vielleicht mache ich ja beim P/Invoke (s.u.) einige Probleme, und ggf. müsste der Post dann doch verschoben werden.
Die Situation ist folgende: Ein Kollege auf Arbeit schreibt derzeit mit einem intern erstellten Framework (.NET) einige UI-Tests. Die Applikation die getestet wird basiert allerdings auf C++ mit MFC. Da er heute ziemliche Probleme mit einer ListControl und der Sortierung der Columns hatte, dachte ich ich greife ihm ein wenig unter die Arme und schreibe ihm eine Utility-Assembly mit der er die Spaltensortierung herausfinden kann.
Dazu übergibt er den Windowhandle (den kriegt er ohne Probleme raus) an eine entsprechende Methode, die wiederum über einen SendMessage P/Invoke eine entsprechende WindowMessage schickt die genau das bewerkstelligen soll.
Hier ist der gesamte ungeschnittene Code bisher:
using System; using System.Runtime.InteropServices; namespace PcvSmoketestUtils.CLR.MFC { /// <summary> /// This class provides the means to interact with, or extract information from MFC CListView controls. /// </summary> public class CListView { // Required to send the window messsages [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam); // Used to pass no argument for wParam and lParam on SendMessage() private static readonly IntPtr NO_ARG = new IntPtr(0); // The header view messages used to perform actions on a CHeaderCtrl private const int HDM_FIRST = 0x1200; private const int HDM_GETITEMCOUNT = (HDM_FIRST + 0); private const int HDM_GETORDERARRAY = (HDM_FIRST + 17); // The list view messages used to perform actions on a MFC CListCtrl private const int LVM_FIRST = 0x1000; private const int LVM_GETCOLUMNORDERARRAY = (LVM_FIRST + 59); private const int LVM_GETHEADER = (LVM_FIRST + 31); /// <summary> /// Constructor for a CListView item. CURRENTLY NOT IMPLEMENTED, WILL THROW! /// </summary> public CListView() { throw new NotImplementedException("Creating CListView instances not supported yet! Use available static methods instead."); } /// <summary> /// Retrieves the current number of columns of a MFC CListCtrl control (Class: SysListView32) for /// a given window handle. /// </summary> /// <param name="hWnd">The window handle that represents the CListCtrl control</param> /// <returns>The number of columns the list has.</returns> public static int GetColumnCountFromHandle(IntPtr hWnd) { if(hWnd == null) { throw new ArgumentNullException("hWnd is null!"); } IntPtr hWndHeader = SendMessage(hWnd, LVM_GETHEADER, NO_ARG, NO_ARG); if(hWndHeader.ToInt32() == 0) { throw new Exception(String.Format("Window handle {0} does not have a header control attached to it. It may not be from type CListCtrl.", hWnd)); } IntPtr columns = SendMessage(hWndHeader, HDM_GETITEMCOUNT, NO_ARG, NO_ARG); if (columns.ToInt32() < 0) { // Unfortunately, the internal functions to retrieve the column order do not set the "last error code". throw new Exception(String.Format("Error while retrieving column count from window handle {0} (Column handle {1}). No further error information was provided. The window handle may not be connected to a control of type CHeaderCtrl.", hWnd.ToInt32(), hWndHeader.ToInt32())); } return columns.ToInt32(); } /// <summary> /// Retrieves the current column ordering of a MFC CListCtrl control (Class: SysListView32) for a /// given window handle. /// </summary> /// <param name="hWnd">The window handle that represents the CListCtrl control</param> /// <returns>An array with the left-to-right order of the list's columns.</returns> public static int[] GetColumnOrderArrayFromHandle(IntPtr hWnd) { if(hWnd == null) { throw new ArgumentNullException("hWnd is null!"); } int columns = GetColumnCountFromHandle(hWnd); if(columns < 0) { throw new Exception(String.Format("Window handle {0} does not have any columns!", hWnd.ToInt32())); } IntPtr lParam = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)) * columns); IntPtr code = SendMessage(hWnd, LVM_GETCOLUMNORDERARRAY, new IntPtr(columns), lParam); if (code.ToInt32() == 0) { // Unfortunately, the internal functions to retrieve the column order do not set the "last error code". throw new Exception(String.Format("Error while retrieving column order array from window handle {0}. No further error information was provided. The window handle may not be connected to a control of type CListCtrl.", hWnd.ToInt32())); } int[] order = new int[columns]; Marshal.Copy(lParam, order, 0, columns); Marshal.FreeHGlobal(lParam); return order; } }}
Das vorgehen hierbei ist wie folgt:
1) Zeile 71: Es wird eine Funktion aufgerufen, die die Anzahl der Spalten der Liste, die der Windowhandle repräsentiert, ermittelt. 2a) Zeile 45: Dazu wird eine Windowmessage LVM_GETHEADER abgesetzt, die einen Handle auf das ListHeaderControl zurückliefert. 2b) Zeile 50: Der Handle des ListHeaderControls wird nun per HDM_GETITEMCOUNT message auf die Anzahl der Spalten abgefragt. 3a) Zeile 76: Es wird Speicher für den unmanaged Part der Applikation allokiert. 3b) Zeile 77: Per LVM_GETCOLUMNORDERARRAY message wird nun die Sortierung dieser Spalten abgerufen und in dem vorher allokierten Bereich gelegt. 4) Zeilen 84 - 89: Die Information wird in ein CLR-Array kopiert, der allokierte Speicher released, und das Array mit der Spaltensortierung zurückgegeben.
All das funktioniert bis Punkt 3b) einwandfrei. Es ist möglich den Header auf die Anzahl der Spalten abzufragen (auch die Anzahl die geliefert wird ist jedesmal korrekt, daher gehe ich davon aus, dass das Prinzip mit den Messages auch klappt).
Nur wird bei 3b), beim Auslesen der Spaltensortierung immer ein Errorcode von 0 von SendMessage() returned (was hier "Fehler" bedeutet).
Weiß jemand ob ich hier etwas falsch mache? Entweder auf C# Seite, oder ob ich P/Invokes falsch absetze? Ich habe auch schon probiert den Header mit einer HDM_GETORDERARRAY message auf die Sortierung zu prüfen - selbes Ergebnis.
Für jede Hilfe wäre ich äußerst dankbar! Und wie gesagt, sollte die Frage von der Gewichtung doch zu sehr in die MFC gehen, bitte ich mit gesenktem Haupt den Thread zu verschieben.
Gruß und danke!
PuerNoctis
-
Okay, für alle die es interessiert, ich glaube ich habe die Antwort gefunden: Anscheinend ist es nicht möglich mit einem Windowhandle, der in einem anderen Prozess angelegt wurde, auch alle Funktionen darauf auszuführen (auch wenn ein Success von diesen Funktionen geliefert wird, sie tun dann nur einfach nichts).
So bekam ich bei einer LVM_GETCOLUMNORDERARRAY message und einer unsortierten Liste mit drei Spalten bislang immer ein Array mit den Werten
0 0 0
zurück. Nach meiner unten beschriebenen Lösung wurde daraus
0 1 2
wie ich es erwartet hätte. Was habe ich gemacht? DLL-Injection - damit ich die Meessages innerhalb des Prozesses der den Windowhandle erstellt hat ausführen kann. Der Austausch der Daten ist dann durch Shared Memory erfolgt.
Das ist zwar mit Kanonen auf Spatzen geschossen, aber es funktioniert soweit.
Gruß
PuerNoctis