WPFを使った拡大鏡を目指して
WPFを使った拡大鏡を作ってみた。
一番手っ取り早く作れそうなのがVisualBrushでVisualにデスクトップを指定する方法だが、XAMLとしてのオブジェクトでデスクトップを指定することができない。
しかも前回検証したようにWPFのAPIを使用した描画でなければVisualBrushは描画を拾ってくれない。
そこで今回はWindows FormsのBitmapクラスにあるCopyFromScreenを使うこととした。
当然、Windows FormsのBitmapクラスとXAMLのImageには互換性がない。そこで今回はWin32APIのBitmapハンドルを介してWindows FormsとXAML間でBitmapイメージを引き渡した。
また、.NET Frameworkにはグローバルフックがない。これにより自分のWindow外でのマウスを監視ができない。Mouseキャプチャなどもあるが、XAMLには存在していたなかった。
今回は不本意ながらWin32APIのSetWindowsHookExを使用した。ここまでくると、もうWPFのアプリケーションではないような気がするが、今後それ以外のところでWPFの色を出していきたい。
今回SetWindowsHookExを使って非常に苦戦した。単に私が.NET Framework上での使い方で知識不足なだけであったが、折角はまったのだから、その内容を紹介したい。
SetWindowsHookExは本来DLLを作成し、そのインスタンスを指定しなければならない。でも参考にしたプログラムはEXE上のフックプロセスをそのまま指定していた。
何故可能なのかは不明だが確かにそのサンプルAPは動く。
そこでそれをそのまま真似てみたがSetWindowsHookExからはエラーが返ってくる。この原因がわかるまで1週間近くかかってしまった。
結論から言うと(と言うか結論しか言えない)、プロジェクトのプロパティにあるデバックタグに「Visual Studioホスティングプロセスを有効にする」が規定値でオンになっている。
このチェックを外さないとSetWindowsHookExはフックプロセスを異常とみなすようだ。
以上を踏まえた上で、以下のソースを参照してもらいたい。
<<Window1.xaml>>
<<Window1.xaml.cs>>
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Reflection;
namespace magnify
{
}
<<MouseHook.cs>>
using System;
using System.Windows.Input;
using System.Runtime.InteropServices;
using System.Reflection;
namespace magnify
{
一番手っ取り早く作れそうなのがVisualBrushでVisualにデスクトップを指定する方法だが、XAMLとしてのオブジェクトでデスクトップを指定することができない。
しかも前回検証したようにWPFのAPIを使用した描画でなければVisualBrushは描画を拾ってくれない。
そこで今回はWindows FormsのBitmapクラスにあるCopyFromScreenを使うこととした。
当然、Windows FormsのBitmapクラスとXAMLのImageには互換性がない。そこで今回はWin32APIのBitmapハンドルを介してWindows FormsとXAML間でBitmapイメージを引き渡した。
また、.NET Frameworkにはグローバルフックがない。これにより自分のWindow外でのマウスを監視ができない。Mouseキャプチャなどもあるが、XAMLには存在していたなかった。
今回は不本意ながらWin32APIのSetWindowsHookExを使用した。ここまでくると、もうWPFのアプリケーションではないような気がするが、今後それ以外のところでWPFの色を出していきたい。
今回SetWindowsHookExを使って非常に苦戦した。単に私が.NET Framework上での使い方で知識不足なだけであったが、折角はまったのだから、その内容を紹介したい。
SetWindowsHookExは本来DLLを作成し、そのインスタンスを指定しなければならない。でも参考にしたプログラムはEXE上のフックプロセスをそのまま指定していた。
何故可能なのかは不明だが確かにそのサンプルAPは動く。
そこでそれをそのまま真似てみたがSetWindowsHookExからはエラーが返ってくる。この原因がわかるまで1週間近くかかってしまった。
結論から言うと(と言うか結論しか言えない)、プロジェクトのプロパティにあるデバックタグに「Visual Studioホスティングプロセスを有効にする」が規定値でオンになっている。
このチェックを外さないとSetWindowsHookExはフックプロセスを異常とみなすようだ。
以上を踏まえた上で、以下のソースを参照してもらいたい。
<<Window1.xaml>>
<Window x:Class="magnify.Window1"
</Window>
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="magnify" Height="300" Width="300"
>
<Grid>
- <Rectangle Margin="0,0,0,0">
- <Rectangle.Fill>
- <VisualBrush Viewbox="0,0,50,50" ViewboxUnits="Absolute" Viewport="0,0,1,1" ViewportUnits="RelativeToBoundingBox">
- <VisualBrush.Visual>
- <Image x:Name="BitmapScreen"/>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Window>
<<Window1.xaml.cs>>
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Reflection;
namespace magnify
{
- ///
/// Interaction logic for Window1.xaml
///
public partial class Window1 : System.Windows.Window
{
- MouseHook mh;
public Window1()
{
- InitializeComponent();
this.Loaded += new RoutedEventHandler(Window1_Loaded);
this.Unloaded += new RoutedEventHandler(Window1_Unloaded);
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
- CaptureView();
mh = new MouseHook();
mh.eventMouseMove += new MouseEventHandler(mh_OnMouseMove);
}
void mh_OnMouseMove(object sender, MouseEventArgs e)
{
- CaptureView();
}
void Window1_Unloaded(object sender, RoutedEventArgs e)
{
}
private void CaptureView()
{
- using (System.Drawing.Bitmap bm = new System.Drawing.Bitmap(50, 50, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
- System.Drawing.Point p = System.Windows.Forms.Cursor.Position;
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bm))
{
- g.CopyFromScreen(p.X - 25, p.Y - 25, 0, 0, new System.Drawing.Size(50, 50), System.Drawing.CopyPixelOperation.SourceCopy);
}
BitmapScreen.Source = Imaging.CreateBitmapSourceFromHBitmap(
- bm.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
}
}
}
<<MouseHook.cs>>
using System;
using System.Windows.Input;
using System.Runtime.InteropServices;
using System.Reflection;
namespace magnify
{
- class MouseHook : Object
{
- public event MouseEventHandler eventMouseMove;
public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
static int hMouseHook = 0; //Declare mouse hook handle as int.
public const int WH_MOUSE_LL = 14; //mouse hook constant
HookProc MouseHookProcedure; //Declare MouseHookProcedure as HookProc type.
//Import for SetWindowsHookEx function.
//Use this function to install a hook.
[DllImport("user32.dll", CharSet = CharSet.Auto,
- CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn,
- IntPtr hInstance, int threadId);
//Import for UnhookWindowsHookEx.
//Call this function to uninstall the hook.
[DllImport("user32.dll", CharSet = CharSet.Auto,
- CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
//Import for CallNextHookEx.
//Use this function to pass the hook information to next hook procedure in chain.
[DllImport("user32.dll", CharSet = CharSet.Auto,
- CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode,
- Int32 wParam, IntPtr lParam);
public MouseHook()
{
- Start();
}
~MouseHook()
{
- Stop();
}
private void Start()
{
- // install Mouse hook
if (hMouseHook == 0)
{
- // Create an instance of HookProc.
MouseHookProcedure = new HookProc(MouseHookProc);
hMouseHook = SetWindowsHookEx(WH_MOUSE_LL,
- MouseHookProcedure,
Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules(false)[0]),
0);
//If SetWindowsHookEx fails.
if (hMouseHook == 0)
{
- Stop();
throw new Exception("SetWindowsHookEx failed.");
}
}
}
private void Stop()
{
- bool retMouse = true;
if (hMouseHook != 0)
{
- retMouse = UnhookWindowsHookEx(hMouseHook);
hMouseHook = 0;
if (retMouse == false)
{
- throw new Exception("UnhookWindowsHookEx failed.");
}
}
}
private const int WM_MOUSEMOVE = 0x200;
private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
- // if ok and someone listens to our events
if (nCode >= 0 && eventMouseMove != null)
{
- switch (wParam)
{
- case WM_MOUSEMOVE:
- eventMouseMove(this, new MouseEventArgs(Mouse.PrimaryDevice, 0));
break;
}
}
return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}
}
}

参照設定については右の図を見てほしい。色々とやっているうちに何を追加したのかわからなくなってしまった。
System.Windows.FormsとSystem.Drawingを追加したのは確かなのだが...

実行結果は左のようになる。倍率などはまだ指定できない。
実行してもらえればわかるのだが、この拡大鏡はマウスを動かさないと画が動かない。
CPUのタコメータのように常に動いているところにマウスを持って行くと、その時点の画で静止画となってしまう。
やはりタイマなどで常にマウス上の画像を取り込まなくては使えないようである。
そうなるとSetWindowsHookExは不要になるが、CPUへの負荷が気になるところである。
この記事に対するコメント