Diskuze: Přepis řešení v C++ co C#
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.
Radek Chalupa:10.2.2018 7:28
Přiznám se že nevím o jaký problém se jedná.
Ale ten kód je dll knihovna obsahující tzv "system hook", v tomhle případě
umožňuje sledovat události myši i když myš je v jiném okně/aplikaci.
Podle toho kódu pokud je při pohybu myši stlačené tlačítko a myš je v
listview, vyvolá předkreslení parent okna toho listview.
Pro aktivaci musíš použít LoadLibrary pro načtení té dll, a následně
zavolat SetWindowsHookEx. Tyhle winAPI funkce můžeš v C# vyvolat přes
DllImport.
Tu DLL su vytvoříš jako win32 aplikaci (Visual c++) s uvedeným kódem.
Radek Chalupa
http://www.radekchalupa.cz
HONZ4:10.2.2018 16:35
Díky za reakci.
Myslíš, že by to nešlo zakomponovat přímo do aplikace, tak že by se
vytvořil upravený ListView?
Přiznám se že nevím o jaký problém se jedná
Pokusím se tu chybu vysvětlit:
Chyba se projevuje u ListView s nastavením View=Details, FullRowSelect=true a MultiSelect=true, pokud ListView obsahuje víc položek (tolik aby se objevil vertikální ScrollBar).
- Stačí odrolovat v seznamu dolů a označit některý spodní řádek.
- Odrolovat nahoru
- Pokusit se označit myší více položek (jen pomocí "rectangle selection", bez Ctrl nebo Shift), např. označit od pátého řádku po první
(U starších aplikací (.net fram. 1.1) se chyba objeví rovnou. )
Cbyba spočívá v tom, že při startu označování myší, kurzor odskočí do levého dolního rohu obrazovky, tím se označí většinou všechny řádky pod prvním označeným, přestože uživatel chce označit řádky nad..
Radek Chalupa:12.2.2018 15:11
Z toho kódu (fce MouseProc) je vidět že pokud je při pohybu myši v okně listview stisknuté tlačítko (pravé nebo levé) tak se vyvolá překreslení levého horního rohu parent okna toho listview. Nevím zda a jak se tímhle překreslením vyřeší zmíněný problém, ale ve vlastn íaplikaci můžeš v událostech listview odchytávat mousemove, testovat tlačítka a zavolat Repaint (nebo Refresh? - z hlavy nevím) metodu Formu (resp. okna na kterém je ten listview).
Radek Chalupa
http://www.radekchalupa.cz
HONZ4:13.2.2018 20:14
Díky za popis té funkce, zkusil jsem při pohybu myši překreslovat
pomocí Refresh i zavoláním RedrawWindow Parenta listview, ale z nějakého
důvodu to nefunguje tak jak Tablacusova aplikace.
Sice se mě nedaří zjistit jestli je stlačené tlačítko myši, ale tam ta
chyba asi nebude.
Takto to mám teď:
class ListViewXP : ListView
{
const int WM_ERASEBKGND = 0x0014;
const int WM_MOUSEMOVE = 0x0200;
const int WM_LBUTTONDOWN = 0x0201;
const int RDW_NOERASE_INVALIDATE = 0x0021;
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public RECT(int l, int t, int r, int b)
{
left = l;
top = t;
right = r;
bottom = b;
}
}
[DllImport("user32.dll")]
static extern bool RedrawWindow(IntPtr hWnd, ref RECT lprcUpdate, IntPtr hrgnUpdate, int flags);
[DllImport("USER32.dll")]
static extern short GetKeyState(Keys nVirtKey);
RECT rc = new RECT(0, 0, 1, 1);
public ListViewXP()
{
//proti blikání kresleného listu
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint |
ControlStyles.EnableNotifyMessage, true);
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
}
protected override void OnNotifyMessage(Message m)
{
if(m.Msg == WM_ERASEBKGND)
m.Msg = 0; //proti blikání kresleného listu
base.OnNotifyMessage(m);
}
protected override void OnMouseMove(MouseEventArgs e)
{
//if (e.Button == MouseButtons.Left) nefuguje
//{
RedrawWindow(Parent.Handle, ref rc, IntPtr.Zero, RDW_NOERASE_INVALIDATE);
//Parent.Refresh();
//}
base.OnMouseMove(e);
}
}
Ještě to budu zkoušet, ale kdyby se to někomu podařilo, budu rád.
Nebo kdyby mě někdo poradil, alespoň s tím myším tlačítkem...
Radek Chalupa:14.2.2018 11:02
Ookud jde o mouse button, nevím co myslíš tím "nefunguje", ale ta
property Button jsou bitové flagy, takže bys měl konkrétní tlačítko
testovat nokoliv rovností ale:
if ((e.Button & MouseButton.left) != 0) ....
Ještě pokud se má překreslovat parent toho listview, tak vzhledem k tomu že předpokládám máš tu obsluhu v kódu Formu, ketrý už sám je parentem listview, měl bys mít v parametru RedrawWindow jen Handle.
Zkoušel jsi tu aplikaci s tím systémovým hookem zda funguje? Přijde mi divné že by ten problém vyřešilo pouhé vyvolání překreslení. Až budu mít čas, zkusím to napsat v céčku...
Radek Chalupa
http://www.radekchalupa.cz
Tak jsem to zkusil v céčku a vypadá to že to funguje pouze pokud se ten
kód umístí do hook procedury, může to být i lokální hook (tj. pouze pro
aktuální thread).
Ve WinForms si lze udělat class library v C++ (jako visual c++ CLR projekt typu
class library), kód vypadá takhle (je to test, takže tam není důledné
testováná chyb a pod.).
// ClassLibraryHooks.h
#pragma once
using namespace System;
__declspec(selectany) HHOOK __hhook_mouse = NULL;
__declspec(selectany) WCHAR sz_class[MAX_CLASS_NAME];
inline LRESULT CALLBACK mouse_proc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0 || nCode != HC_NOREMOVE)
{
if (nCode == HC_ACTION)
{
MOUSEHOOKSTRUCT *pmhs = (MOUSEHOOKSTRUCT*)lParam;
switch (wParam)
{
case WM_MOUSEMOVE:
if (GetKeyState(VK_LBUTTON) < 0 || GetKeyState(VK_RBUTTON) < 0)
{
if (GetClassName(pmhs->hwnd, sz_class, MAX_CLASS_NAME))
{
if (PathMatchSpec(sz_class, L"*listview*"))
{
RECT rc = { 0, 0, 1, 1 };
RedrawWindow(GetParent(pmhs->hwnd), &rc, 0, RDW_NOERASE | RDW_INVALIDATE);
}
}
}
break;
}
}
}
return CallNextHookEx(__hhook_mouse, nCode, wParam, lParam);
}
namespace ClassLibraryHooks
{
public ref class hooks_fce
{
public:
static void spustit()
{
__hhook_mouse = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)mouse_proc, NULL, GetCurrentThreadId());
if (NULL == __hhook_mouse)
::MessageBox(NULL, L"Chyba SetWindowsHookEx", L"Je to v prdeli", MB_ICONERROR);
}
static void zastavit()
{
if (__hhook_mouse)
{
UnhookWindowsHookEx(__hhook_mouse);
__hhook_mouse = NULL;
}
}
};
}
v stdafx.h musí být:
#pragma once
#include <Windows.h>
#include <Shlwapi.h>
#pragma comment (lib, "user32.lib")
#pragma comment (lib, "shlwapi.lib")
Pak jsem ve winforms aplikaci přidal tu dll classlibrary do referencí a kód testovacího Formu s jedním listview:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void on_load(object sender, EventArgs e)
{
ListViewItem lvi;
for (int i = 0; i < 100; i++)
{
lvi = lv_test.Items.Add((100 + i).ToString());
lvi.SubItems.Add((200 + i).ToString());
lvi.SubItems.Add((300 + i).ToString());
}
ClassLibraryHooks.hooks_fce.spustit();
}
private void on_closing(object sender, FormClosingEventArgs e)
{
ClassLibraryHooks.hooks_fce.zastavit();
}
}
Radek Chalupa
http://www.radekchalupa.cz
+20 Zkušeností
+2,50 Kč
HONZ4:14.2.2018 19:07
co myslíš tím "nefunguje", ale ta property...
Ono se stisknuté tlačítko detekuje pouze pokud je kurzor nad řádkem, jakmile jste v ose X za posledním sloupcem tak ne.
předpokládám máš tu obsluhu v kódu Formu...
nemám, jeto přepis (nebo jak se tomu v C# nadává) komponenty ListView class ListViewXP : ListView
Tak jsem to zkusil v céčku..
Tvé řešení opravdu funguje. Mockrát díky!
Tvé řešení mi stačí, ale pokud se Ti chce odpovídat, tak jen pro
zajímavost:
Zajímalo by mě proč to vlastně funguje? Protože je to přes hook? Proč
funguje vyvolání (částečné) překreslení na takový problém? Dalo by se
to udělat bez hooku, přibližně jak jsem to dělal já?
takto jsem měl poslední pokus:
http://www51.zippyshare.com/…HP/file.html
je to celý projekt a archivu, heslo je w10bug
Bohužel jsem narazil na problém, aplikace neběží na jiném počítači
než mém. Předpokládám, že bych musel doinstalovávat redistributable
balíček, to se mě moc nelíbí, tak jsem se pokusil kód přepsat do C#, ale
mám tam něco špatně.
Kompilace proběhne bez varování, ale aplikace padá s chybou:
Pomocník spravovaného ladění CallbackOnCollectedDelegate
Bylo provedeno zpětné volání u delegáta typu
FCUFixTest!FCUFix.BugFix+HookProc::Invoke zjištěného pomocí úklidového
modulu. Výsledkem může být havárie aplikace, poškození a ztráta dat.
Při předávání nespravovanému kódu musí být delegáti udržováni
aktivní spravovanou aplikací, dokud není zaručeno, že již nikdy nebudou
voláni.
Co mám špatně?
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace FCUFix
{
public static class BugFix
{
#region definitions
const int MAX_CLASS_NAME = 1024;
const int HC_NOREMOVE = 3;
const int HC_ACTION = 0;
const int WM_MOUSEMOVE = 0x0200;
const int VK_LBUTTON = 1;
const int VK_RBUTTON = 2;
const int RDW_NOERASE_INVALIDATE = 0x0021;
const int WH_MOUSE = 7;
static IntPtr __hhook_mouse = IntPtr.Zero;
static StringBuilder sz_class = new StringBuilder(MAX_CLASS_NAME);
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct MOUSEHOOKSTRUCT
{
public int point_X;
public int point_Y;
public IntPtr hwnd;
public uint hittestcode;
public IntPtr dwExtraInfo;
public uint mouseData;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public RECT(int l, int t, int r, int b)
{
left = l;
top = t;
right = r;
bottom = b;
}
}
delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
static extern bool RedrawWindow(IntPtr hWnd, ref RECT lprcUpdate, IntPtr hrgnUpdate, int flags);
[DllImport("user32.dll")]
static extern short GetKeyState(int key);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(int hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool UnhookWindowsHookEx(IntPtr hhk);
static Type tMOUSEHOOKSTRUCT = typeof(MOUSEHOOKSTRUCT);
#endregion
static RECT rect = new RECT(0, 0, 1, 1);
static IntPtr MouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 || nCode != HC_NOREMOVE)
{
if (nCode == HC_ACTION)
{
MOUSEHOOKSTRUCT pmhs = (MOUSEHOOKSTRUCT)Marshal.PtrToStructure(lParam, tMOUSEHOOKSTRUCT);
int wparam = (int)wParam;
switch (wparam)
{
case WM_MOUSEMOVE:
if (GetKeyState(VK_LBUTTON) < 0 || GetKeyState(VK_RBUTTON) < 0)
{
if (0 != GetClassName(pmhs.hwnd, sz_class, MAX_CLASS_NAME))
{
string cname = sz_class.ToString().ToLower();
if (cname.Contains("listview"))
{
RedrawWindow(GetParent(pmhs.hwnd), ref rect, IntPtr.Zero, RDW_NOERASE_INVALIDATE);
}
}
}
break;
}
}
}
return CallNextHookEx(__hhook_mouse, nCode, wParam, lParam);
}
public static void StartFix()
{
if (__hhook_mouse == IntPtr.Zero)
{
__hhook_mouse = SetWindowsHookEx(WH_MOUSE, MouseProc, IntPtr.Zero, GetCurrentThreadId());
if (null == __hhook_mouse) throw new ExternalException("Chyba SetWindowsHookEx");
}
}
public static void StopFix()
{
if (__hhook_mouse != IntPtr.Zero)
{
UnhookWindowsHookEx(__hhook_mouse);
__hhook_mouse = IntPtr.Zero;
}
}
}
}
Radek Chalupa:15.2.2018 11:25
zkoušel jsi to krokovat na kterém řádku to havaruje?
HONZ4:15.2.2018 12:07
tady: Application.Run(new Form1());
projekt:
http://www74.zippyshare.com/…3v/file.html
heslo: w10bug
v tom Run je bv podstatě celá smyčka aplikace. Zkus dát breakpointry
dovnitř kódu.
Trochu jsem to upravil, dal tu MouseProc jako metodu formu a to spuštění do
události Load a funguje to.
HONZ4:15.2.2018 14:01
Měl sem je tam, ale při té chybě to právě skončilo na řádku s .Run
Můžeš mi prosím sem dát ukázku? K večeru to zkusím.
Radek Chalupa:15.2.2018 15:04
tohle je kod toho Formu:
public partial class Form1 : Form
{
static StringBuilder sz_class = new StringBuilder(winapi.MAX_CLASS_NAME);
static IntPtr __hhook_mouse = IntPtr.Zero;
static winapi.RECT rect = new winapi.RECT(0, 0, 1, 1);
public Form1()
{
InitializeComponent();
}
private void on_load(object sender, EventArgs e)
{
ListViewItem lvi;
for (int i = 0; i < 100; i++)
{
lvi = lv_test.Items.Add((100 + i).ToString());
lvi.SubItems.Add((200 + i).ToString());
lvi.SubItems.Add((300 + i).ToString());
}
if (__hhook_mouse == IntPtr.Zero)
{
__hhook_mouse = winapi.SetWindowsHookEx(winapi.WH_MOUSE, MouseProc, IntPtr.Zero, winapi.GetCurrentThreadId());
if (null == __hhook_mouse) throw new Exception("Chyba SetWindowsHookEx");
}
}
private void on_closing(object sender, FormClosingEventArgs e)
{
if (__hhook_mouse != IntPtr.Zero)
{
winapi.UnhookWindowsHookEx(__hhook_mouse);
__hhook_mouse = IntPtr.Zero;
}
}
static IntPtr MouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 || nCode != winapi.HC_NOREMOVE)
{
if (nCode == winapi.HC_ACTION)
{
winapi.MOUSEHOOKSTRUCT pmhs = (winapi.MOUSEHOOKSTRUCT)Marshal.PtrToStructure(lParam, winapi.tMOUSEHOOKSTRUCT);
switch ((int)wParam)
{
case winapi.WM_MOUSEMOVE:
if (winapi.GetKeyState(winapi.VK_LBUTTON) < 0 || winapi.GetKeyState(winapi.VK_RBUTTON) < 0)
{
if (0 != winapi.GetClassName(pmhs.hWnd, sz_class, winapi.MAX_CLASS_NAME))
{
string cname = sz_class.ToString().ToLower();
if (cname.Contains("listview"))
{
winapi.RedrawWindow(winapi.GetParent(pmhs.hWnd), ref rect, IntPtr.Zero,
winapi.RDW_NOERASE | winapi.RDW_INVALIDATE);
}
}
}
break;
}
}
}
return winapi.CallNextHookEx(__hhook_mouse, nCode, wParam, lParam);
}
}
a deklarace těch api funkcí:
using System;
using System.Runtime.InteropServices;
using System.Text;
public static class winapi
{
public const int MAX_CLASS_NAME = 256;
public const int HC_NOREMOVE = 3;
public const int HC_ACTION = 0;
public const int WM_MOUSEMOVE = 0x0200;
public const int VK_LBUTTON = 0x01;
public const int VK_RBUTTON = 0x02;
public const int RDW_NOERASE = 0x0020;
public const int RDW_INVALIDATE = 0x0001;
public const int WH_MOUSE = 7;
public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
public class MOUSEHOOKSTRUCT
{
// pt was a by-value POINT structure
public int pt_x = 0;
public int pt_y = 0;
public IntPtr hWnd = IntPtr.Zero;
public int wHitTestCode = 0;
public int dwExtraInfo = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public RECT(int l, int t, int r, int b)
{
left = l;
top = t;
right = r;
bottom = b;
}
}
public static Type tMOUSEHOOKSTRUCT = typeof(MOUSEHOOKSTRUCT);
[DllImport("kernel32.dll")]
public static extern uint GetCurrentThreadId();
[DllImport("user32.dll")]
public static extern bool RedrawWindow(IntPtr hWnd, ref RECT lprcUpdate, IntPtr hrgnUpdate, int flags);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int hookid, HookProc pfnhook, IntPtr hinst, uint threadid);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern short GetKeyState(int key);
}
HONZ4:15.2.2018 18:39
Tvůj kód funguje lépe, ale po několika (40 - 80) pokusech přeznačení
řádků to skončí stejnou chybovou hláškou.
Zvláštní je, že pokud je ListView nastaven na VirtualMode a má víc
řádků selže aplikace dřív.
(při testování jsem založil nový projekt a použil tvůj kompletní kód)
Radek Chalupa:15.2.2018 19:46
Možná se te callback funkce musí nastavovat přes proměnnou typu
delegate, viz. tenhle ukázkový příklad: https://gist.github.com/…onix/3181083
Upravil jsem to podle té ukázky takhle:
public partial class Form1 : Form
{
static StringBuilder sz_class = new StringBuilder(winapi.MAX_CLASS_NAME);
static IntPtr __hhook_mouse = IntPtr.Zero;
static winapi.RECT rect = new winapi.RECT(0, 0, 1, 1);
private winapi.HookProc _mouse_proc = Form1.MouseProc;
public Form1()
{
InitializeComponent();
}
private void on_load(object sender, EventArgs e)
{
ListViewItem lvi;
for (int i = 0; i < 100; i++)
{
lvi = lv_test.Items.Add((100 + i).ToString());
lvi.SubItems.Add((200 + i).ToString());
lvi.SubItems.Add((300 + i).ToString());
}
if (__hhook_mouse == IntPtr.Zero)
{
__hhook_mouse = winapi.SetWindowsHookEx(winapi.WH_MOUSE, _mouse_proc, IntPtr.Zero, winapi.GetCurrentThreadId());
if (null == __hhook_mouse) throw new Exception("Chyba SetWindowsHookEx");
}
}
private void on_closing(object sender, FormClosingEventArgs e)
{
if (__hhook_mouse != IntPtr.Zero)
{
winapi.UnhookWindowsHookEx(__hhook_mouse);
__hhook_mouse = IntPtr.Zero;
}
}
static IntPtr MouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 || nCode != winapi.HC_NOREMOVE)
{
if (nCode == winapi.HC_ACTION)
{
winapi.MOUSEHOOKSTRUCT pmhs = (winapi.MOUSEHOOKSTRUCT)Marshal.PtrToStructure(lParam, winapi.tMOUSEHOOKSTRUCT);
switch ((int)wParam)
{
case winapi.WM_MOUSEMOVE:
if (winapi.GetKeyState(winapi.VK_LBUTTON) < 0 || winapi.GetKeyState(winapi.VK_RBUTTON) < 0)
{
if (0 != winapi.GetClassName(pmhs.hWnd, sz_class, winapi.MAX_CLASS_NAME))
{
string cname = sz_class.ToString().ToLower();
if (cname.Contains("listview"))
{
winapi.RedrawWindow(winapi.GetParent(pmhs.hWnd), ref rect, IntPtr.Zero,
winapi.RDW_NOERASE | winapi.RDW_INVALIDATE);
}
}
}
break;
}
}
}
return winapi.CallNextHookEx(__hhook_mouse, nCode, wParam, lParam);
}
}
na pár pokusů to funguje, tak to vyzkoušej nějakým zátěžovým testem.
jj, zlaté céčko, tam se prostě zadá adresa funkce (napíše její název bez závorek) a je to:-)
HONZ4:15.2.2018 20:20
Paráda zdá se že to funguje. Otestoval jsem to na listu s VirtualMode s 10
000 000 položek a asi 500 přeznačeními.
Zítra to ještě otestuji na jiných počítačích, i na W7, 32bit W10, + jak
se to bude chovat po probuzení z režimu spánku apd.
Zatím díky moc za pomoc, sám bych to do hromady nedal.
ps, Budu doufat, že s jarním update to nepůjde do kytek..
Zobrazeno 19 zpráv z 19.