WPF ํ๋ก์ ํธ์์ Win32 API๋ฅผ ์ด์ฉํ์ฌ Mouse๋ฅผ ์ ์ดํ ์ผ์ด ์๊ฒผ์ต๋๋ค.
MFC๋ฅผ ํด๋ณธ ์ ์ด ์๊ณ , Win32 API์ ๋ํ ์ดํด๋๊ฐ ๋ฎ์๊ธฐ ๋๋ฌธ์ ์กฐ๊ธ ๊ณ ์ํด์, ์ด๋ ฅ์ ๋จ๊ฒจ ๋์ต๋๋ค.
ChatGPT์๊ฒ Win32 API๋ฅผ ์ด์ฉํด์ MouseMove๋ฅผ ํ๋ ๋ฐฉ๋ฒ์ ๋ฌผ์ด๋ณด๋ฉด 3๊ฐ์ง ๋ฐฉ๋ฒ์ ์ถ์ฒํด์ค๋๋ค.
- SendMessage/PostMessage ์ด์ฉํ๋ ๋ฐฉ๋ฒ
- mouse_event ์ด์ฉํ๋ ๋ฐฉ๋ฒ
- SendInput ์ด์ฉํ๋ ๋ฐฉ๋ฒ
์ด ์ค์์ 1๋ฒ์ ์๋ํ๋ค๊ฐ ๋์ํ์ง ์์์ 3๋ฒ์ผ๋ก ๋ฐ๊ฟจ์ต๋๋ค. (์ ๋์ํ์ง ์์๋์ง๋ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.)
mouse_event๋ฅผ ๊ณ ๋ คํ์ง ์์ ์ด์ ๋,
mouse_event ํจ์(winuser.h) - Win32 apps | Microsoft Learn
์ด๊ฒ์ ํ์ธํ๊ธฐ ๋๋ฌธ์ ๋๋ค. Deprecated์ธ์ง๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง์.
Mouse ์ ์ด ์์ ์ด์ง๋ง ์ฝ๋ ์์ฒด๋ Key ์ ์ด์ ์ฝ๋๋ ๊ฐ์ด ์ฌ๋ฆฌ๊ฒ ์ต๋๋ค.
์ค๋น๊ณผ์
Win32 API ์ SendInput์์๋ INPUT์ด๋ผ๋ ๊ณต์ฉ์ฒด(union)์ ์ฌ์ฉํด์ ์ ๋ ฅ์ ์ ์ดํ๊ณ ์์ต๋๋ค.
C#์์๋ ๊ณต์ฉ์ฒด๋ฅผ ์ ์ํ์ง ๋ชปํ๋ฏ๋ก, ChatGPT์๊ฒ ๋ฌผ์ด์ ์๋์ ๊ฐ์ด ์์ฑํ์ต๋๋ค.
INPUT32
[StructLayout(LayoutKind.Explicit)]
public struct INPUT32
{
[FieldOffset(0)]
public uint type; // x86์์ int, uint๋ 4byte์ด๋ค.
[FieldOffset(4)]
public KEYBDINPUT ki;
[FieldOffset(4)]
public MOUSEINPUT mi;
[FieldOffset(4)]
public HARDWAREINPUT hi;
}
INPUT64
[StructLayout(LayoutKind.Explicit)]
public struct INPUT64
{
[FieldOffset(0)]
public uint type; // x64์์ int, uint๋ 8byte์ด๋ค.
[FieldOffset(8)]
public KEYBDINPUT ki;
[FieldOffset(8)]
public MOUSEINPUT mi;
[FieldOffset(8)]
public HARDWAREINPUT hi;
}
KEYBDINPUT
[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
MONITORINFO
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
public int cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}
MOUSEINPUT
[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
์ํคํ ์ณ๊ฐ ๋ค๋ฅผ ๊ฒ์ ๊ณ ๋ คํด์ INPUT ๊ฐ์ฒด๋ฅผ 32bit์ฉ 64bit์ฉ์ผ๋ก 2๊ฐ ๋ง๋ค์์ต๋๋ค.
์ฌ์ค INPUT ํด๋์ค๋ฅผ ์์ฒ๋ผ ๊ณต์ฉ์ฒด ์คํ์ผ๋ก ์ฐ์ง ์๊ณ , ๋ง์ฐ์ค, ํค๋ณด๋์ ๋ํด ๋ฐ๋ก ๋ง๋ค๋ฉด INPUT ํด๋์ค ์์ฒด๋ฅผ [StructLayout(LayoutKind.Sequential)]
์ฒ๋ฆฌํ ์ ์๊ธฐ ๋๋ฌธ์ Sequential ๋ป๋๋ก ์ง๋ ฌํ๋์ด x86, x64 ์ํคํ
์ณ์์ ๋ชจ๋ ๋์ํฉ๋๋ค.
ํ์ง๋ง ์์ฌ์ ๋ด์๊ณต์ฉ์ฒด๋ก ์์ฑํ๋ ค๋ฉด [StructLayout(LayoutKind.Explicit)]
์ ์ฌ์ฉํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ด ํ์
์ ๋ช ๋ฐ์ดํธ๋ผ๋ ๊ฒ์ ๋ช
์ํด์ฃผ์ด์ผ ํ๊ณ ๊ทธ๊ฒ์ด [FieldOffset(4)]
์
๋๋ค.
์ฃผ์์ ๋ณด์๋ฉด ์์๊ฒ ์ง๋ง x86๊ณผ x64์์ int์ ๊ธฐ๋ณธ ์ฌ์ด์ฆ๋ ๋ค๋ฆ ๋๋ค. ์ ํ์ฌ PC๊ฐ x64์ด๊ณ , Explict์ผ๋ก ์ ์ธ๋ ๊ณต์ฉ์ฒด๋ฅผ ์จ์ [FieldOffset(4)]๋ก ์ ์ธ๋ ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ฉด ์๋์ SendInput ๋ฉ์๋๋ ๋์ํ์ง ์์ต๋๋ค.
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, [MarshalAs(UnmanagedType.LPArray), In] INPUT32[] pInputs, int cbSize);
๊ทธ๋์ ํ๋ ๋ ์ ์ธํฉ๋๋ค.
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, [MarshalAs(UnmanagedType.LPArray), In] INPUT32[] pInputs, int cbSize);
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, [MarshalAs(UnmanagedType.LPArray), In] INPUT64[] pInputs, int cbSize);
์ฌ๊ธฐ๊น์ง๊ฐ Win32 API๋ก SendInput์ ์ฌ์ฉํ๊ธฐ ์ํ ์ค๋น๊ณผ์ ์ ๋๋ค.
SendInput์ ์ ๊ทํ
INPUT32์ INPUT64์ ๋ค์ด๊ฐ๋ MOUSEINPUT ๊ฐ์ ์ ๊ทํ๋ฅผ ํด์ผํฉ๋๋ค. ์๋์ ๊ฐ์ ๊ท์น์ด ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
MOUSEINPUT(winuser.h) - Win32 apps | Microsoft Learn
์ ๋ฌธ์๋ฅผ ๋ณด๋ฉด MOUSEEVENTF_ABSOLUTE ๋ฅผ ์ฌ์ฉํ์ง ์์๋ ๋๋ ๊ฒ์ฒ๋ผ ์ค๋ช ์ด ์จ์์ง๋ง, ์ด ์ต์ ์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์ ๋์ขํ๊ฐ ์๋๋ผ ์๋์ขํ ํํ๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ ์คํ๋ ค ์ ์ด๊ฐ ์ด๋ ต์ต๋๋ค. ๋ฐ๋ผ์ ๋ฐ๋์ ์จ์ผํ๋ ์ต์ ์ ๋๋ค. ์ฌ์ฉํ๊ฒ ๋๋ฉด ์๋์ ๊ฐ์ด ์์ฑ์ด ๋ฉ๋๋ค.
private static INPUT32 MouseMove32(int normalized_X, int normalized_Y)
{
// ๋ง์ฐ์ค ์ด๋ ์
๋ ฅ ์์ฑ
var mouseMoveInput = new INPUT32
{
type = INPUT_MOUSE,
mi = new MOUSEINPUT
{
dx = normalized_X,
dy = normalized_Y,
dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE
}
};
return mouseMoveInput;
}
private static INPUT64 MouseMove64(int normalized_X, int normalized_Y)
{
// ๋ง์ฐ์ค ์ด๋ ์
๋ ฅ ์์ฑ
var mouseMoveInput = new INPUT64
{
type = INPUT_MOUSE,
mi = new MOUSEINPUT
{
dx = normalized_X,
dy = normalized_Y,
dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE
}
};
return mouseMoveInput;
}
private static INPUT32 MouseDown32()
{
// ๋ง์ฐ์ค ํด๋ฆญ ์
๋ ฅ ์์ฑ (์ผ์ชฝ ๋ฒํผ ๋๋ฆ)
var mouseDownInput = new INPUT32
{
type = INPUT_MOUSE,
mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_LEFTDOWN }
};
return mouseDownInput;
}
private static INPUT64 MouseDown64()
{
// ๋ง์ฐ์ค ํด๋ฆญ ์
๋ ฅ ์์ฑ (์ผ์ชฝ ๋ฒํผ ๋๋ฆ)
var mouseDownInput = new INPUT64
{
type = INPUT_MOUSE,
mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_LEFTDOWN }
};
return mouseDownInput;
}
private static INPUT32 MouseDown32(int x, int y)
{
// ๋ง์ฐ์ค ํด๋ฆญ ์
๋ ฅ ์์ฑ (์ผ์ชฝ ๋ฒํผ ๋๋ฆ)
var mouseDownInput = new INPUT32
{
type = INPUT_MOUSE,
mi = new MOUSEINPUT { dx = x, dy = y, dwFlags = MOUSEEVENTF_LEFTDOWN }
};
return mouseDownInput;
}
private static INPUT64 MouseDown64(int x, int y)
{
// ๋ง์ฐ์ค ํด๋ฆญ ์
๋ ฅ ์์ฑ (์ผ์ชฝ ๋ฒํผ ๋๋ฆ)
var mouseDownInput = new INPUT64
{
type = INPUT_MOUSE,
mi = new MOUSEINPUT { dx = x, dy = y, dwFlags = MOUSEEVENTF_LEFTDOWN }
};
return mouseDownInput;
}
private static INPUT32 MouseUp32()
{
// ๋ง์ฐ์ค ํด๋ฆญ ์
๋ ฅ ์์ฑ (์ผ์ชฝ ๋ฒํผ ๋)
var mouseUpInput = new INPUT32
{
type = INPUT_MOUSE,
mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_LEFTUP }
};
return mouseUpInput;
}
private static INPUT64 MouseUp64()
{
// ๋ง์ฐ์ค ํด๋ฆญ ์
๋ ฅ ์์ฑ (์ผ์ชฝ ๋ฒํผ ๋)
var mouseUpInput = new INPUT64
{
type = INPUT_MOUSE,
mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_LEFTUP }
};
return mouseUpInput;
}
private static INPUT32 KeyDown32(short vk)
{
var keyDownInput = new INPUT32
{
type = INPUT_KEYBOARD,
ki = new KEYBDINPUT { wVk = (ushort)vk }
};
return keyDownInput;
}
private static INPUT64 KeyDown64(short vk)
{
var keyDownInput = new INPUT64
{
type = INPUT_KEYBOARD,
ki = new KEYBDINPUT { wVk = (ushort)vk }
};
return keyDownInput;
}
private static INPUT32 KeyUp32(short vk)
{
var keyUpInput = new INPUT32
{
type = INPUT_KEYBOARD,
ki = new KEYBDINPUT { wVk = (ushort)vk, dwFlags = KEYEVENTF_KEYUP }
};
return keyUpInput;
}
private static INPUT64 KeyUp64(short vk)
{
var keyUpInput = new INPUT64
{
type = INPUT_KEYBOARD,
ki = new KEYBDINPUT { wVk = (ushort)vk, dwFlags = KEYEVENTF_KEYUP }
};
return keyUpInput;
}
์ ์ด์ WindowHandle ๊ธฐ๋ฐ์ธ SendMessage๋ฅผ ์ฌ์ฉํ๋ ค๊ณ ํ๊ธฐ ๋๋ฌธ์ Handle์ ์ด๋ฏธ ์ป์ ์ํ์ ๋๋ค. ๊ทธ๋ฌ๋ฉด ์๋์ ๊ฐ์ด MouseClick์ ์ํ ์ฝ๋๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
public static void ProcessInMouseClick(IntPtr targetProcessHandle, int x, int y)
{
IntPtr hMonitor = MonitorFromWindow(targetProcessHandle, MONITOR_DEFAULTTONEAREST);
var monitorInfo = new MONITORINFO();
monitorInfo.cbSize = Marshal.SizeOf(monitorInfo);
if (!GetMonitorInfo(hMonitor, ref monitorInfo)) return;
if (!GetWindowRect(new HandleRef(null, targetProcessHandle), out RECT rect)) return;
int offset_window_posX = rect.Left;
int offset_window_posY = rect.Top;
int _x = x + offset_window_posX;
double ratioX = (double)_x / monitorInfo.rcMonitor.Width;
int normalizedX = Convert.ToInt32(Math.Round(ratioX * NORMALIZED_VALUE));
int _y = y + offset_window_posY;
double ratioY = (double)_y / monitorInfo.rcMonitor.Height;
int normalizedY = Convert.ToInt32(Math.Round(ratioY * NORMALIZED_VALUE));
if (Environment.Is64BitProcess)
SendInput(1, new[] { MouseMove64(normalizedX, normalizedY) }, Marshal.SizeOf(typeof(INPUT64)));
else
SendInput(1, new[] { MouseMove32(normalizedX, normalizedY) }, Marshal.SizeOf(typeof(INPUT32)));
if (Environment.Is64BitProcess)
SendInput(1, new[] { MouseDown64() }, Marshal.SizeOf(typeof(INPUT64)));
else
SendInput(1, new[] { MouseDown32() }, Marshal.SizeOf(typeof(INPUT32)));
if (Environment.Is64BitProcess)
SendInput(1, new[] { MouseUp64() }, Marshal.SizeOf(typeof(INPUT64)));
else
SendInput(1, new[] { MouseUp32() }, Marshal.SizeOf(typeof(INPUT32)));
}
โปRECT๋ ๋ฐ๋ก ์ ์ํ ๊ฒ์ ๋๋ค.
์ด ์์
์ ํ ๋๋ Multi Monitor ์ํฉ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
๋ชจ๋ํฐ๊ฐ 1๊ฐ๋ผ๋ฉด ์ ๊ฒฝ์ฐ์ง ์์๋ ๋์ง๋ง, ๋์ผ ๋ชจ๋ํฐ ์ด์์ด๋ผ๋ฉด Windows์ ์ ์ฒด ๊ณต๊ฐ์ ์ขํ๊ฐ์ด ํ์ํ๊ธฐ ๋๋ฌธ์ด๊ณ ๊ทธ ์ขํ๊ณ๋ ์ด์ด ๋ถํ์ ธ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
- ํด๋ฆญํ๊ณ ์ถ์ ๋์์ UI ํธ๋ค์ ์ป์
- ํธ๋ค์ด ์กด์ฌํ๋ ๋ชจ๋ํฐ์ ํธ๋ค์ ์ป์ (ํ๋ก์ธ์ค ํธ๋ค ์๋)
- ๋ชจ๋ํฐ์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ ธ์ด
- UI ํธ๋ค์ด ์กด์ฌํ๋ ๋ชจ๋ํฐ ๋ด๋ถ์์์ Left์ Top ์ขํ๊ฐ์ ํ๋
- ์ขํ๊ฐ์ ๊ณ์ฐ
์ค์ํ ๊ฒ์ 5๋ฒ์ ์ขํ๊ฐ์ ๊ณ์ฐํ ๋์
๋๋ค.
์ขํ๊ฐ์ ๋ชจ๋ํฐ์ ํฌ๊ธฐ์ ๋ฐ๋ผ ๋น์จ์ด ๋ฌ๋ผ์ง๋๋ค. (๋น์ฐํ ์ขํ ์์ฒด๋ ๋ฌ๋ผ์ง์ง ์์ต๋๋ค.)
๋ฌด์จ ๋ง์ด๋๋ฉด, ๋ด๊ฐ (100, 100) ์ด๋ผ๋ ์ขํ๋ฅผ ํด๋ฆญํ๊ณ ์ถ์๋๋ฐ ์ด๊ฒ์ ๋ด ํ์ฌ ๋ชจ๋ํฐ ํด์๋์ธ (1920 * 1080) ์ผ ๋ 100,100 ์ ์ขํ๋ฅผ ์ํ๋ ๊ฒ์ด์ง ์ฌ๋์ด ์ํ๋ ๊ฒ์ ์ขํ๊ฐ ์๋๋ผ ๋ชจ๋ํฐ ์์์ ์ด๋์ ์์นํด ์๋์ง ์
๋๋ค. ๋ฐ๋ผ์ ํด์๋๊ฐ ์ปค์ง๋ฉด ์ฌ๋์ด ์ํ๋ ์์น๋ (100, 100) ๋ณด๋ค ์ปค์ง๊ฒ ๋ฉ๋๋ค.
์ฆ, ratio๋ฅผ ๋ด์ผํ๋ค๋ ๋ง์ ๋๋ค.
๋น์จ์ ๊ตฌํ๊ธฐ ์ํด์๋ ๊ณ ์ ๋ ์ขํ๊ฐ์ธ int๊ฐ์ ๋ฐ๋์ ์ ์ฒด ํฌ๊ธฐ๋ก ๋๋ ์ผํฉ๋๋ค.
์ ์๋ฅผ ๋๋๋ ๊ณผ์ ์์๋ ์์ซ์ ์ด ๋ฐ์ํ ์ ์๋๋ฐ ์ด์ ์ด ์ค์ํฉ๋๋ค.
์๊น ์ ๊ทํ๋ฅผ ํ๊ธฐ ์ํด 0~65535์ ์ขํ๊ณ๋ฅผ ์ฌ์ฉํด์ผ ๋ชจ๋ํฐ์ ๋น์จ์ ๋ง์ถ ์ ์๋ค๊ณ ํ๋๋ฐ์.
ํ์ฌ ๋ด UI ํธ๋ค์ ์ขํ๊ฐ์ ์ ํ์ด๋ ์ด๊ฒ์ ์ ์ฒด ๋ชจ๋ํฐ ํฌ๊ธฐ๋ก ๋๋ ์ ๋น์จ์ ๋ง๋ค๊ณ , ๊ทธ ๊ฐ์ 65536์ ๊ณฑํด์ ์ ๋ ์ขํ๋ก ๋ณํํ ๊ฐ์ ์ฐ์ด์ผ SendInput์ ํตํด MouseMove๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
์ ๋ ์ขํ๋ 65535๊น์ง๋ก ์ ํด์ ธ์๊ธฐ ๋๋ฌธ์ ํด์๋๊ฐ ์ปค์ ธ๋ ์ด ๊ฐ์ด ๋์ด๋์ง ์์ต๋๋ค. ๋์ด๋๋ ๊ฒ์ ์ ๋์ขํ๋ก ๋๋ 65535 Point ๋ค์ 1 Point ๋น ๋ฉด์ ์ด ๋์ด์ง๋๋ค.
๋ฐ๋ผ์ ์์ซ์ ์ ๊ณ์ฐํ๊ธฐ ์ ๋ถํฐ ์ต๋ํ ์ ๊ฐ์ง๊ณ ์๋ค๊ฐ ๋ง์ง๋ง์ ์ ๊ทํํ ๋ ๊ทธ ๋ ๋น๋ก์ ๋ฐ์ฌ๋ฆผ์ํ๊ฑฐ๋ ๋ฒ๋ฆด ๊ฒ์ ๊ฒฐ์ ํด์ผ ํฉ๋๋ค.
์ด์ ๋ถํฐ ์์ซ์ ๋ค ์๋ผ๋จน์ผ๋ฉด์ ํธํ๊ฒ ๊ณ์ฐํ๋ฉด ๋ง์ฐ์ค์ ์ ํํ ์ ์ด๊ฐ ๋ถ๊ฐ๋ฅํฉ๋๋ค.
์ ์์์ ๊ณ์ฐ ์์๋ฅผ ์ ๋ณด๋ฉด ๋์์ด ๋์ค ๊ฒ ๊ฐ์ต๋๋ค.
์ฌ์ค ์ด๋ ๊ฒ ํ์ด๋ ์๋ฒฝํ๊ฒ ์ ๋ฐํ์ง๋ ์์ต๋๋ค.
๋์ฑ ์ ๋ฐํ ๋ง์ฐ์ค ํฌ์ธํฐ ์ ์ด๊ฐ ์๋ค๋ฉด ์๊ฐ ๋ถํ๋๋ฆฝ๋๋ค.