ScreenShot

<Window x:Class="ScreenShotApp.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:ScreenShotApp"
        mc:Ignorable="d"
        Title="ScreenShot" Height="720" Width="1100" MinHeight="520" MinWidth="820">
    <Grid Margin="16">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Grid.Row="0" VerticalAlignment="Center" Margin="0,0,0,12">
            <Button x:Name="CaptureButton" Width="120" Height="36" Margin="0,0,10,0" Click="CaptureButton_Click">スクリーンショット</Button>
            <Button x:Name="RangeCaptureButton" Width="100" Height="36" Margin="0,0,10,0" Click="RangeCaptureButton_Click">範囲選択</Button>
            <Button x:Name="SaveButton" Width="100" Height="36" Margin="0,0,10,0" Click="SaveButton_Click">保存</Button>
            <Button x:Name="OpenFolderButton" Width="120" Height="36" Margin="0,0,18,0" Click="OpenFolderButton_Click">フォルダを開く</Button>
            <CheckBox x:Name="IncludeCursorCheckBox" VerticalAlignment="Center" Margin="0,0,14,0">カーソルも含める</CheckBox>
            <CheckBox x:Name="CopyClipboardCheckBox" VerticalAlignment="Center">クリップボードへコピー</CheckBox>
        </StackPanel>

        <Grid Grid.Row="1" Margin="0,0,0,12">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" VerticalAlignment="Center" Margin="0,0,10,0">保存先</TextBlock>
            <TextBox x:Name="SaveFolderTextBox" Grid.Column="1" Height="30" IsReadOnly="True" VerticalContentAlignment="Center"/>
            <Button Grid.Column="2" Width="80" Height="30" Margin="10,0,0,0" Click="ChangeFolderButton_Click">変更</Button>
        </Grid>

        <Border Grid.Row="2" BorderBrush="#B0B0B0" BorderThickness="1" CornerRadius="6" Padding="8" Background="#FAFAFA">
            <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                <Image x:Name="PreviewImage" Stretch="None" SnapsToDevicePixels="True"/>
            </ScrollViewer>
        </Border>

        <TextBlock x:Name="StatusTextBlock" Grid.Row="3" Margin="0,10,0,0" Foreground="#333333"/>
    </Grid>
</Window>
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Forms = System.Windows.Forms;
using WpfClipboard = System.Windows.Clipboard;

namespace ScreenShotApp;

public partial class MainWindow : Window
{
    private byte[]? _latestPng;
    private string _saveFolder;

    public MainWindow()
    {
        InitializeComponent();

        _saveFolder = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
            "ScreenShots");
        Directory.CreateDirectory(_saveFolder);
        SaveFolderTextBox.Text = _saveFolder;

        IncludeCursorCheckBox.IsChecked = true;
        StatusTextBlock.Text = "準備完了。";
    }

    private void CaptureButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            SetUiEnabled(false);

            var includeCursor = IncludeCursorCheckBox.IsChecked == true;
            var (pngBytes, bitmapSource, size) = CaptureScreen(includeCursor);

            _latestPng = pngBytes;
            PreviewImage.Source = bitmapSource;

            if (CopyClipboardCheckBox.IsChecked == true)
            {
                WpfClipboard.SetImage(bitmapSource);
            }

            StatusTextBlock.Text = $"キャプチャ完了: {size.Width}x{size.Height} ({DateTime.Now:HH:mm:ss})";
        }
        catch (Exception ex)
        {
            StatusTextBlock.Text = $"失敗: {ex.Message}";
        }
        finally
        {
            SetUiEnabled(true);
        }
    }

    private void RangeCaptureButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            SetUiEnabled(false);

            var selector = new SelectionOverlay { Owner = this };
            if (selector.ShowDialog() != true || selector.SelectedBounds is null)
            {
                StatusTextBlock.Text = "範囲選択をキャンセルしました。";
                return;
            }

            var includeCursor = IncludeCursorCheckBox.IsChecked == true;
            var (pngBytes, bitmapSource, size) = CaptureBounds(selector.SelectedBounds.Value, includeCursor);

            _latestPng = pngBytes;
            PreviewImage.Source = bitmapSource;

            if (CopyClipboardCheckBox.IsChecked == true)
            {
                WpfClipboard.SetImage(bitmapSource);
            }

            StatusTextBlock.Text = $"範囲キャプチャ完了: {size.Width}x{size.Height} ({DateTime.Now:HH:mm:ss})";
        }
        catch (Exception ex)
        {
            StatusTextBlock.Text = $"失敗: {ex.Message}";
        }
        finally
        {
            SetUiEnabled(true);
        }
    }

    private void SaveButton_Click(object sender, RoutedEventArgs e)
    {
        if (_latestPng is null)
        {
            StatusTextBlock.Text = "保存するスクリーンショットがありません。";
            return;
        }

        Directory.CreateDirectory(_saveFolder);
        var fileName = $"Screenshot_{DateTime.Now:yyyyMMdd_HHmmss_fff}.png";
        var path = Path.Combine(_saveFolder, fileName);
        File.WriteAllBytes(path, _latestPng);
        StatusTextBlock.Text = $"保存しました: {path}";
    }

    private void OpenFolderButton_Click(object sender, RoutedEventArgs e)
    {
        if (!Directory.Exists(_saveFolder))
        {
            StatusTextBlock.Text = "保存先フォルダが見つかりません。";
            return;
        }

        Process.Start(new ProcessStartInfo
        {
            FileName = _saveFolder,
            UseShellExecute = true
        });
    }

    private void ChangeFolderButton_Click(object sender, RoutedEventArgs e)
    {
        using var dialog = new Forms.FolderBrowserDialog
        {
            Description = "保存先フォルダを選択",
            SelectedPath = _saveFolder,
            ShowNewFolderButton = true
        };

        if (dialog.ShowDialog() == Forms.DialogResult.OK &&
            !string.IsNullOrWhiteSpace(dialog.SelectedPath))
        {
            _saveFolder = dialog.SelectedPath;
            SaveFolderTextBox.Text = _saveFolder;
            StatusTextBlock.Text = $"保存先を変更しました: {_saveFolder}";
        }
    }

    private void SetUiEnabled(bool enabled)
    {
        CaptureButton.IsEnabled = enabled;
        RangeCaptureButton.IsEnabled = enabled;
        SaveButton.IsEnabled = enabled;
        OpenFolderButton.IsEnabled = enabled;
    }

    private static (byte[] PngBytes, BitmapSource Image, System.Drawing.Size Size) CaptureScreen(bool includeCursor)
    {
        var screens = Forms.Screen.AllScreens;
        var left = screens.Min(screen => screen.Bounds.Left);
        var top = screens.Min(screen => screen.Bounds.Top);
        var right = screens.Max(screen => screen.Bounds.Right);
        var bottom = screens.Max(screen => screen.Bounds.Bottom);
        var bounds = Rectangle.FromLTRB(left, top, right, bottom);
        return CaptureBounds(bounds, includeCursor);
    }

    private static (byte[] PngBytes, BitmapSource Image, System.Drawing.Size Size) CaptureBounds(Rectangle bounds, bool includeCursor)
    {

        using var bitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
        using (var graphics = Graphics.FromImage(bitmap))
        {
            graphics.CopyFromScreen(bounds.Left, bounds.Top, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy);
            if (includeCursor)
            {
                DrawCursor(graphics, bounds);
            }
        }

        var hBitmap = bitmap.GetHbitmap();
        try
        {
            var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(
                hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
            bitmapSource.Freeze();

            using var stream = new MemoryStream();
            bitmap.Save(stream, ImageFormat.Png);

            return (stream.ToArray(), bitmapSource, bitmap.Size);
        }
        finally
        {
            DeleteObject(hBitmap);
        }
    }

    private static void DrawCursor(Graphics graphics, Rectangle bounds)
    {
        var cursorInfo = new CURSORINFO { cbSize = Marshal.SizeOf<CURSORINFO>() };
        if (!GetCursorInfo(ref cursorInfo) || cursorInfo.flags != CURSOR_SHOWING)
        {
            return;
        }

        if (!GetIconInfo(cursorInfo.hCursor, out var iconInfo))
        {
            return;
        }

        if (cursorInfo.ptScreenPos.x < bounds.Left || cursorInfo.ptScreenPos.x > bounds.Right ||
            cursorInfo.ptScreenPos.y < bounds.Top || cursorInfo.ptScreenPos.y > bounds.Bottom)
        {
            return;
        }

        var x = cursorInfo.ptScreenPos.x - bounds.Left;
        var y = cursorInfo.ptScreenPos.y - bounds.Top;
        if (!iconInfo.fIcon)
        {
            x -= iconInfo.xHotspot;
            y -= iconInfo.yHotspot;
        }

        var hdc = graphics.GetHdc();
        try
        {
            DrawIconEx(hdc, x, y, cursorInfo.hCursor, 0, 0, 0, IntPtr.Zero, DI_NORMAL);
        }
        finally
        {
            graphics.ReleaseHdc(hdc);
            if (iconInfo.hbmColor != IntPtr.Zero)
            {
                DeleteObject(iconInfo.hbmColor);
            }

            if (iconInfo.hbmMask != IntPtr.Zero)
            {
                DeleteObject(iconInfo.hbmMask);
            }
        }
    }

    private const int CURSOR_SHOWING = 0x00000001;
    private const int DI_NORMAL = 0x0003;

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int x;
        public int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct CURSORINFO
    {
        public int cbSize;
        public int flags;
        public IntPtr hCursor;
        public POINT ptScreenPos;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct ICONINFO
    {
        [MarshalAs(UnmanagedType.Bool)]
        public bool fIcon;
        public int xHotspot;
        public int yHotspot;
        public IntPtr hbmMask;
        public IntPtr hbmColor;
    }

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetCursorInfo(ref CURSORINFO pci);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DrawIconEx(
        IntPtr hdc,
        int xLeft,
        int yTop,
        IntPtr hIcon,
        int cxWidth,
        int cyWidth,
        int istepIfAniCur,
        IntPtr hbrFlickerFreeDraw,
        int diFlags);

    [DllImport("gdi32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DeleteObject(IntPtr hObject);
}
<Window x:Class="ScreenShotApp.SelectionOverlay"
        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"
        mc:Ignorable="d"
        WindowStyle="None"
        AllowsTransparency="True"
        Background="#10000000"
        ShowInTaskbar="False"
        Topmost="True"
        ResizeMode="NoResize"
        Cursor="Cross"
        KeyDown="Window_KeyDown"
        MouseLeftButtonDown="Window_MouseLeftButtonDown"
        MouseMove="Window_MouseMove"
        MouseLeftButtonUp="Window_MouseLeftButtonUp">
    <Canvas x:Name="RootCanvas">
        <Rectangle x:Name="SelectionRect"
                   Stroke="#FF2B59"
                   StrokeThickness="2"
                   Fill="#402B59"
                   Visibility="Collapsed"/>
    </Canvas>
</Window>
using System;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Shapes;
using Forms = System.Windows.Forms;
using DrawingRect = System.Drawing.Rectangle;
using WpfPoint = System.Windows.Point;

namespace ScreenShotApp;

public partial class SelectionOverlay : Window
{
    private WpfPoint? _startPoint;
    private DrawingRect _virtualBounds;

    public DrawingRect? SelectedBounds { get; private set; }

    public SelectionOverlay()
    {
        InitializeComponent();
        Loaded += OnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        var screens = Forms.Screen.AllScreens;
        var left = screens.Min(screen => screen.Bounds.Left);
        var top = screens.Min(screen => screen.Bounds.Top);
        var right = screens.Max(screen => screen.Bounds.Right);
        var bottom = screens.Max(screen => screen.Bounds.Bottom);
        _virtualBounds = DrawingRect.FromLTRB(left, top, right, bottom);

        Left = _virtualBounds.Left;
        Top = _virtualBounds.Top;
        Width = _virtualBounds.Width;
        Height = _virtualBounds.Height;
    }

    private void Window_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        _startPoint = e.GetPosition(RootCanvas);
        SelectionRect.Visibility = Visibility.Visible;
        CaptureMouse();
    }

    private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
    {
        if (_startPoint is null || e.LeftButton != MouseButtonState.Pressed)
        {
            return;
        }

        var current = e.GetPosition(RootCanvas);
        var x = Math.Min(_startPoint.Value.X, current.X);
        var y = Math.Min(_startPoint.Value.Y, current.Y);
        var width = Math.Abs(_startPoint.Value.X - current.X);
        var height = Math.Abs(_startPoint.Value.Y - current.Y);

        Canvas.SetLeft(SelectionRect, x);
        Canvas.SetTop(SelectionRect, y);
        SelectionRect.Width = width;
        SelectionRect.Height = height;
    }

    private void Window_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        if (_startPoint is null)
        {
            return;
        }

        var end = e.GetPosition(RootCanvas);
        ReleaseMouseCapture();

        var x = Math.Min(_startPoint.Value.X, end.X);
        var y = Math.Min(_startPoint.Value.Y, end.Y);
        var width = Math.Abs(_startPoint.Value.X - end.X);
        var height = Math.Abs(_startPoint.Value.Y - end.Y);

        if (width < 2 || height < 2)
        {
            SelectedBounds = null;
            DialogResult = false;
            Close();
            return;
        }

        var left = (int)Math.Round(_virtualBounds.Left + x);
        var top = (int)Math.Round(_virtualBounds.Top + y);
        var right = (int)Math.Round(left + width);
        var bottom = (int)Math.Round(top + height);
        SelectedBounds = DrawingRect.FromLTRB(left, top, right, bottom);

        DialogResult = true;
        Close();
    }

    private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key == Key.Escape)
        {
            SelectedBounds = null;
            DialogResult = false;
            Close();
        }
    }
}