21499


お世話になります。

.Net4.5です。  
System.Diagnostics.Processで指定されている別プロセスのフォームを、枠抜きしたクライアント  
領域のみメインのアプリからキャプチャしたいのですが、そういった方法はありますでしょうか。  
ご助力ください。

よろしくお願いいたします。

[![画像の説明をここに入力](https://i.stack.imgur.com/1PIbm.jpg)](https://i.stack.imgur.com/1PIbm.jpg)


CC BY-SA 3.0
2016-01-26T08:52:12.510
21328
2016-01-30T05:14:09.727
2016-01-28T01:49:38.713
9374
9374
question
c#
C# Processで指定した別プロセスのウインドウを、クライアント領域のみキャプチャしたい。


まず`Process`のウィンドウハンドルは`MainWindowHandle`で取れると思います。ただし対象アプリ次第なので確実ではありません。

対象ウィンドウが求められたらP/Invokeの`ClientToScreen`でクライアント領域のスクリーン座標を、`GetClientRect`でクライアント領域のサイズを求めます。

[code]

    Point origin = default(Point);
    ClientToScreen(process.MainWindowHandle, ref origin);
    
    RECT rect;
    GetClientRect(process.MainWindowHandle, out rect);
    
[/code]

なお上記の2メソッドはpinvoke.netで定義された宣言を使用しています。

スクリーン上の領域が求められたら、書き込む`Bitmap`を作成して`Graphics.CopyFromScreen`でキャプチャを実行します。

[code]

    var bmp = new Bitmap(rect.Width, rect.Height);
    
    using (var g = Graphics.FromImage(bmp))
    {
        g.CopyFromScreen(new Point(origin.X + rect.Left, origin.Y + rect.Top), new Point(0, 0), rect.Size);
    }
    
[/code]

# 追記

pinvoke.netから参照しているメンバーの宣言を転記しておきます。

[ClientToScreen](http://www.pinvoke.net/default.aspx/user32/ClientToScreen.html)

[code]

    [DllImport("user32.dll")]
    static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);
    
[/code]

[GetClientRect](http://www.pinvoke.net/default.aspx/user32/GetClientRect.html)

[code]

    [DllImport("user32.dll")]
    static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
    
[/code]

[RECT](http://www.pinvoke.net/default.aspx/Structures/RECT.html)

[code]

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left, Top, Right, Bottom;
    
        public RECT(int left, int top, int right, int bottom)
        {
          Left = left;
          Top = top;
          Right = right;
          Bottom = bottom;
        }
    
        public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }
    
        public int X
        {
          get { return Left; }
          set { Right -= (Left - value); Left = value; }
        }
    
        public int Y
        {
          get { return Top; }
          set { Bottom -= (Top - value); Top = value; }
        }
    
        public int Height
        {
          get { return Bottom - Top; }
          set { Bottom = value + Top; }
        }
    
        public int Width
        {
          get { return Right - Left; }
          set { Right = value + Left; }
        }
    
        public System.Drawing.Point Location
        {
          get { return new System.Drawing.Point(Left, Top); }
          set { X = value.X; Y = value.Y; }
        }
    
        public System.Drawing.Size Size
        {
          get { return new System.Drawing.Size(Width, Height); }
          set { Width = value.Width; Height = value.Height; }
        }
    
        public static implicit operator System.Drawing.Rectangle(RECT r)
        {
          return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
        }
    
        public static implicit operator RECT(System.Drawing.Rectangle r)
        {
          return new RECT(r);
        }
    
        public static bool operator ==(RECT r1, RECT r2)
        {
          return r1.Equals(r2);
        }
    
        public static bool operator !=(RECT r1, RECT r2)
        {
          return !r1.Equals(r2);
        }
    
        public bool Equals(RECT r)
        {
          return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
        }
    
        public override bool Equals(object obj)
        {
          if (obj is RECT)
            return Equals((RECT)obj);
          else if (obj is System.Drawing.Rectangle)
            return Equals(new RECT((System.Drawing.Rectangle)obj));
          return false;
        }
    
        public override int GetHashCode()
        {
          return ((System.Drawing.Rectangle)this).GetHashCode();
        }
    
        public override string ToString()
        {
          return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
        }
    }
    
[/code]

なおコメントで指摘されているP/Invokeに`System.Drawing.Point`を使うことの是非ですが、`Point`には[StructLayoutAttribute属性が明示されていません](http://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Point.cs)。しかし、C#コンパイラは値型に対して既定でSequentialレイアウトを行うとされているので、実際には[pinvoke.netで定義されているPOINT](http://www.pinvoke.net/default.aspx/Structures/POINT.html)型と完全に同一レイアウトになります。なのであえて独自の型を用意する必要はないです。


CC BY-SA 3.0
2016-01-26T11:42:56.017
21342
2016-01-28T07:03:27.083
2016-01-28T07:03:27.083
5750
5750
21328
answer


`ClientToScreen`と`GetClientRect`でクライアント領域を取得するサンプルアプリケーションを組んでみました。  
コードの内容はpgrhoさんの回答とほとんどかぶっていますが、サンプルアプリケーションが問題なく動作すれば、それを足掛かりにエラーの原因箇所が特定できるかもしれません。

[![サンプルアプリケーション](https://i.stack.imgur.com/xh9Rq.png)](https://i.stack.imgur.com/xh9Rq.png)

  * `RECT`と`POINT`構造体には`StructLayout‌`を付けています。
  * サンプルアプリケーション自体の動作確認はWindows10 64bit のみで行いました。
  * `ClientCapture.cs`の`GetBitmap`メソッドはアプリケーション開発時のUIテストに使った実績があります。(Windows7 32bitで使用) 
  * [画面をキャプチャする](http://dobon.net/vb/dotnet/graphics/screencapture.html)方法や[フォームやコントロールの外観をキャプチャする](http://dobon.net/vb/dotnet/graphics/invokepaint.html)方法は他の選択肢もあります。   
私がUIテスト時に`Graphics#CopyFromScreen`を採用した理由は以下のとおりです。

    * 比較的容易にクライアント領域をキャプチャできる
    * 運用上クライアント領域が他の画面の後ろに回り込むことを考慮しなくても良い

* * *

以下はサンプルアプリケーションのコード  
参照設定に`System.Drawing`を追加する必要があります。

**ClientCapture.cs**

[code]

    using System;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Windows.Media.Imaging;
    
    namespace WpfApplication1
    {
        public class ClientCapture
        {
            //クライアント領域キャプチャ用のメソッドと、戻り値用の構造体
            [StructLayout(LayoutKind.Sequential)]
            private struct RECT
            {
                public int left;
                public int top;
                public int right;
                public int bottom;
            }
            [StructLayout(LayoutKind.Sequential)]
            private struct POINT
            {
                public int x;
                public int y;
            }
    
            [DllImport("user32.dll")]
            private static extern int GetClientRect(IntPtr hwnd, out RECT lpRect);
            [DllImport("user32.dll")]
            private static extern bool ClientToScreen(IntPtr hwnd, out POINT lpPoint);
    
            /// <summary>
            /// 画面をキャプチャしてBitmapを返す。
            /// </summary>
            /// <param name="handle"></param>
            /// <returns>クライアント領域のBitmap。キャプチャに失敗した場合null。</returns>
            public static Bitmap GetBitmap(IntPtr handle)
            {
                var rect = new RECT();
                if (GetClientRect(handle, out rect) == 0)
                {
                    //キャプチャ失敗
                    return null;
                }
                var size = new Size(rect.right - rect.left, rect.bottom - rect.top);
                if (size.Width <= 0 || size.Height <= 0)
                {
                    //キャプチャ失敗
                    return null;
                }
                var img = new Bitmap(size.Width, size.Height);
                var pt = new POINT { x = rect.left, y = rect.top };
                ClientToScreen(handle, out pt);
                using (var g = Graphics.FromImage(img))
                {
                    g.CopyFromScreen(pt.x, pt.y, 0, 0, img.Size);
                }
                return img;
            }
    
            /// <summary>
            /// 画面をキャプチャしてBitmapImageを返す。
            /// </summary>
            /// <param name="handle"></param>
            /// <returns>クライアント領域のBitmapImage。キャプチャに失敗した場合null。</returns>
            public static BitmapImage GetBitmapImage(IntPtr handle)
            {
                var bitmap = GetBitmap(handle);
                if(bitmap == null)
                {
                    //キャプチャ失敗
                    return null;
                }
                //変換処理
                //see http://stackoverflow.com/a/1069509
                using (var memory = new MemoryStream())
                {
                    bitmap.Save(memory, ImageFormat.Png);
                    memory.Position = 0;
                    var bitmapImage = new BitmapImage();
                    bitmapImage.BeginInit();
                    bitmapImage.StreamSource = memory;
                    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                    bitmapImage.EndInit();
                    return bitmapImage;
                }
            }
        }
    }
    
[/code]

**MainWindow.xaml**

[code]

    <Window x:Class="WpfApplication1.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:WpfApplication1"
            mc:Ignorable="d"
            Title="{Binding WindowTitle}" Height="350" Width="525">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Label Grid.Row="0" Grid.Column="0" Content="{Binding ExePathCaption}"/>
            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ExePath}"/>
            <Button Grid.Row="0" Grid.Column="2" Content="{Binding RunExeCaption}" Command="{Binding Path=RunExeCommand}" Padding="10,0"/>
            <Image Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Name="ImageView" Source="{Binding CapturedImage}" Stretch="None"/>
            <Label Grid.Row="2" Grid.Column="0" Content="{Binding RecCaption}" Foreground="DarkRed" Visibility="{Binding RecVisibility}"/>
        </Grid>
    </Window>
    
[/code]

**MainWindow.xaml.cs**

[code]

    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Timers;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media.Imaging;
    
    namespace WpfApplication1
    {
        /// <summary>
        /// MainWindow.xaml の相互作用ロジック
        /// </summary>
        public partial class MainWindow : Window, ICommand, INotifyPropertyChanged
        {
            const double REC_INTERVAL = 1000d;
            #region xaml とのバインディング関連プロパティ
            public string WindowTitle { get { return "1秒ごとにキャプチャするサンプル"; } }
            public string ExePathCaption { get { return "exeファイルのパス"; } }
            public string RunExeCaption { get { return "起動"; } }
            public string RecCaption { get { return "●Rec"; } }
            public string ExePath { get; set; }
            public Visibility RecVisibility { get; private set; }
            public BitmapImage CapturedImage { get; private set; }
            #endregion
    
            private Timer CaptureTimer;
            private Process ExeProcess;
    
            #region キャプチャに関係のない処理
            public MainWindow()
            {
                InitializeComponent();
    
                ExePath = "notepad";
                SetRecVisible(false);
                DataContext = this;
                CaptureTimer = new Timer(REC_INTERVAL);
    
                Closed += MainWindow_Closed;
            }
    
            private void SetRecVisible(bool isVisible)
            {
                RecVisibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("RecVisibility"));
                }
            }
    
            private void MainWindow_Closed(object sender, EventArgs e)
            {
                CaptureTimer.Dispose();
                if (ExeProcess != null)
                {
                    ExeProcess.Dispose();
                }
            }
            #endregion
    
            public ICommand RunExeCommand { get { return this; } }
            public bool CanExecute(object parameter) { return true; }
            public event EventHandler CanExecuteChanged;
            public event PropertyChangedEventHandler PropertyChanged;
    
            /// <summary>
            /// ボタンを押したときの処理。プロセス実行→クライアント領域を一定間隔でキャプチャする。
            /// </summary>
            /// <param name="parameter"></param>
            /// <remarks>エラー回避してないので注意すること</remarks>
            public void Execute(object parameter)
            {
                ExeProcess = Process.Start(ExePath);
                CaptureTimer.Elapsed += Timer_Elapsed;
                CaptureTimer.Start();
                SetRecVisible(true);
            }
    
            private void Timer_Elapsed(object sender, ElapsedEventArgs e)
            {
                if (ExeProcess == null || ExeProcess.HasExited)
                {
                    //プロセスがなくなったらタイマーも停止する
                    CaptureTimer.Stop();
                    SetRecVisible(false);
                    return;
                }
                //非同期処理なのでメインスレッドでキャプチャ処理
                Dispatcher.BeginInvoke(new Action(SetImage), null);
            }
    
            private void SetImage()
            {
                //クライアント領域をキャプチャ
                CapturedImage = ClientCapture.GetBitmapImage(ExeProcess.MainWindowHandle);
                if (PropertyChanged != null)
                {
                    //変更を通知して再描画
                    PropertyChanged(this, new PropertyChangedEventArgs("CapturedImage"));
                }
            }
        }
    }
    
[/code]


CC BY-SA 3.0
2016-01-30T05:14:09.727
21499
2016-01-30T05:14:09.727
9820
21328
answer