🎯 背景
Windowsで画面に表示されているQRコードを読み取りたいとき、
ちょうどいいアプリが見つからなかったので自作しました。
ひとまず動きました。
作成にはAI生成を利用しています。
🧩 できること
- 画面の任意範囲を選択
- QRコードを読み取り
- テキストとして取得&コピー
🛠️ 使用技術
- .NET 10
- WPF
- ZXing.Net
- ZXing.Net.Bindings.Windows.Compatibility
メイン画面
メイン画面です。ひとまず動かしたかっただけなので、雑です。
MainWindow.xaml
<Window x:Class="ScreenQRCodeReader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ScreenQRCodeReader"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="250">
<Grid>
<StackPanel Margin="10">
<Button Content="QRコード取得" Height="40" Click="Capture_Click"/>
<Image x:Name="DebugImage"
Height="120"
Stretch="Uniform"
Margin="0,10"/>
<TextBox x:Name="ResultText"
Margin="0,10,0,10"
Height="60"
TextWrapping="Wrap"
AcceptsReturn="True"/>
<Button Content="コピー" Height="40" Click="Copy_Click"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Drawing;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using ZXing.Windows.Compatibility;
namespace ScreenQRCodeReader
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Capture_Click(object sender, RoutedEventArgs e)
{
this.Hide();
try
{
var selector = new SelectionWindow();
if (selector.ShowDialog() == true)
{
string? text = null;
var bmp = CaptureArea(selector.X, selector.Y, selector.W, selector.H);
try
{
// デバッグ表示
this.DebugImage.Source = BitmapUtil.ConvertBitmap(bmp);
// QR解析
text = DecodeQR(bmp);
}
finally
{
bmp.Dispose();
}
ResultText.Text = text ?? "QRコードが見つかりません";
}
}
finally
{
this.Show();
}
}
private string? DecodeQR(Bitmap bitmap)
{
var reader = new BarcodeReader();
var result = reader.Decode(bitmap);
return result?.Text;
}
private Bitmap CaptureArea(int x, int y, int width, int height)
{
var bmp = new Bitmap(width, height);
using (var g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(x, y, 0, 0, new System.Drawing.Size(width, height));
}
return bmp;
}
private void Copy_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(ResultText.Text))
{
System.Windows.Clipboard.SetText(ResultText.Text);
}
}
}
}
キャプチャ用画面
SelectionWindow.xaml
<Window x:Class="ScreenQRCodeReader.SelectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ScreenQRCodeReader"
mc:Ignorable="d"
WindowStyle="None"
AllowsTransparency="True"
Background="#40000000"
Topmost="True"
WindowState="Maximized"
Title="SelectionWindow">
<Grid>
<Canvas x:Name="RootCanvas">
<TextBlock x:Name="CoordText"
Foreground="White"
Background="#80000000"
FontSize="14"
Padding="5"
Visibility="Collapsed"/>
<Rectangle x:Name="SelectionRect"
Stroke="Red"
StrokeThickness="2"
Fill="#40FFFFFF"
Visibility="Collapsed"/>
</Canvas>
</Grid>
</Window>
SelectionWindow.xaml.cs
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Input;
using System.Windows.Shapes;
using System.Windows.Controls;
namespace ScreenQRCodeReader
{
/// <summary>
/// Interaction logic for SelectionWindow.xaml
/// </summary>
public partial class SelectionWindow : Window
{
private System.Windows.Point Start;
private System.Windows.Shapes.Rectangle RangeRect;
public int X { get; private set; }
public int Y { get; private set; }
public int W { get; private set; }
public int H { get; private set; }
public SelectionWindow()
{
InitializeComponent();
RangeRect = SelectionRect;
MouseDown += OnMouseDown;
MouseMove += OnMouseMove;
MouseUp += OnMouseUp;
KeyDown += (_, e) => { if (e.Key == Key.Escape) Close(); };
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
Start = e.GetPosition(this);
Canvas.SetLeft(RangeRect, Start.X);
Canvas.SetTop(RangeRect, Start.Y);
RangeRect.Width = 0;
RangeRect.Height = 0;
RangeRect.Visibility = Visibility.Visible;
}
private void OnMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
var pos = e.GetPosition(this);
var startScreen = this.PointToScreen(Start);
var posScreen = this.PointToScreen(pos);
// 座標表示(常に更新)
CoordText.Text = $"X:{(int)posScreen.X} Y:{(int)posScreen.Y}";
Canvas.SetLeft(CoordText, pos.X);
Canvas.SetTop(CoordText, pos.Y);
CoordText.Visibility = Visibility.Visible;
if (e.LeftButton == MouseButtonState.Pressed)
{
double x = Math.Min(pos.X, Start.X);
double y = Math.Min(pos.Y, Start.Y);
double w = Math.Abs(pos.X - Start.X);
double h = Math.Abs(pos.Y - Start.Y);
Canvas.SetLeft(RangeRect, x);
Canvas.SetTop(RangeRect, y);
RangeRect.Width = w;
RangeRect.Height = h;
// 範囲サイズも表示
CoordText.Text += $" W:{(int)w} H:{(int)h}";
}
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
var end = e.GetPosition(this);
var startScreen = this.PointToScreen(Start);
var endScreen = this.PointToScreen(end);
double x = Math.Min(startScreen.X, endScreen.X);
double y = Math.Min(startScreen.Y, endScreen.Y);
double w = Math.Abs(startScreen.X - endScreen.X);
double h = Math.Abs(startScreen.Y - endScreen.Y);
if (w < 1 || h < 1) { Close(); return; }
this.X = (int)x;
this.Y = (int)y;
this.W = (int)w;
this.H = (int)h;
DialogResult = true;
Close();
}
}
}
Bitmap操作
BitmapUtil.cs
using System;
using System.Windows.Media.Imaging;
using System.Runtime.InteropServices;
using System.Drawing;
namespace ScreenQRCodeReader
{
internal class BitmapUtil
{
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
public static BitmapSource ConvertBitmap(Bitmap bitmap)
{
IntPtr hBitmap = bitmap.GetHbitmap();
try
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
System.Windows.Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DeleteObject(hBitmap); // メモリリーク防止
}
}
}
}
工夫ポイント
- ウィンドウを一旦
Hide()して自分を写さない -
PointToScreen()で正確な座標取得
はまったところ
- 選択枠がキャプチャに写る問題
- DPIスケーリング
→ 環境によって座標ズレる可能性あり?
まとめ
- ZXing.Netはとても便利でした
- https://github.com/gippol/Win-AreaQR-Readerで公開しています