DOBON.NET DOBON.NETプログラミング掲示板過去ログ

セカンダリモニタのhDCを取得したい

環境/言語:[WindowsXP,VB2005]
分類:[.NET]

2006/03/23(Thu) 09:42:22 編集(投稿者)

たびたび似たようなスレッドをたてて申し訳ありません。

GetDC(IntPtr.Zero)でプライマリモニタのデバイスコンテキストハンドルが取得できることはわかったのですが,マルチディスプレイ環境でセカンダリモニタのデバイスコンテキストハンドルが取得できません。

Dim hDC As IntPtr = CreateDC("\\.\DISPLAY2", "\\.\DISPLAY2", Nothing, IntPtr.Zero)

とすると例外も発生せずハンドルが取得できるのですが,実際に描画しようとするとプライマリモニタに描画されます。それもセカンダリモニタの大きさにクリッピングされてです。(つまりプライマリが1600*1200,セカンダリが1024*768ならプライマリの1024*768に描画される)
座標はx:1200,y:0を原点にすると何も描かれず,x:0,y:0を原点にすると前述のようにプライマリの一部に描画されます。

GetDC(IntPtr.Zero)でプライマリモニタに描画するのと同じようにセカンダリモニタに描画する方法をご存じの方がいらっしゃいましたらご教授ください。
> GetDC(IntPtr.Zero)でプライマリモニタに描画するのと同じようにセカンダリモニタに描画する方法をご存じの方がいらっしゃいましたらご教授ください。

EnumDisplayMonitors() して hdc を入手してみるとか?
> EnumDisplayMonitors() して hdc を入手してみるとか?

やってみましたが,
CreateDC("\\.\DISPLAY2", "\\.\DISPLAY2", Nothing, IntPtr.Zero)
と同じ結果となりました。

そこで,Graphicsクラスを使わず,以下のようにgdi32.dllを使って描画してみました。

Dim hDC As IntPtr = GetDC(IntPtr.Zero)
mhPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0))
mhBrush = CreateSolidBrush(RGB(255, 0, 0))
SelectObject(hDC, mhPen)
SelectObject(hDC, mhBrush)
Dim p As Point = Control.MousePosition
Const r As Integer = 5
Ellipse(hDC, p.X - r, p.Y - r, p.X + r, p.Y + r)
ReleaseDC(IntPtr.Zero, hDC)

すると!なんとプライマリにもセカンダリにも描画することが出来ました。
また,
Dim hDC As Intptr = CreateDC("\\.\DISPLAY1", Nothing, Nothing, IntPtr.Zero)
とすればプライマリだけに,
Dim hDC As Intptr = CreateDC("\\.\DISPLAY2", Nothing, Nothing, IntPtr.Zero)
とすればセカンダリだけに描画できました。

原因はデバイスコンテキストハンドルではなくて,Graphicsクラスにあったようです。
クリッピングなどの影響かと思い,Graphics.SetClipなどを設定してみましたが変化がありませんでした。

できるだけWin32APIは呼びたくないのでGraphicsクラスで描画したいのですが,なにかアイデアがありましたらご教授いただけたらと思います。
2006/03/23(Thu) 17:08:52 編集(投稿者)

> できるだけWin32APIは呼びたくないのでGraphicsクラスで描画したいのですが,なにかアイデアがありましたらご教授いただけたらと思います。

ひょっとしてレベルですが、Graphics.FromHDC(IntPtr,IntPtr) 形式のファクトリメソッドから Graphics を作るとイケたりとか。。。

第二2引数の解説が無いけど、hMonitor でいいのかなあ?
渋木宏明(ひどり)様,ありがとうございます。

> 第二2引数の解説が無いけど、hMonitor でいいのかなあ?

やってみました。すこし長いですけれど,リスト載せます。
結論を先に書きますが,ダメでした。やはりプライマリモニタにのみ描画されます。しかもセカンダリモニタの大きさにクリップされて。
ちなみに,g.Clear(Color.Black)とするとちゃんとセカンダリモニタが真っ黒に
なるんです。
う〜ん。もしかして.NETのバグ?


Option Strict On
Imports System.Runtime.InteropServices

Public Class Form1

    <DllImport("user32.dll")> _
    Private Shared Function GetDC( _
        ByVal hWnd As IntPtr) _
        As IntPtr
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function ReleaseDC( _
        ByVal hWnd As IntPtr, _
        ByVal hDC As IntPtr) _
        As Integer
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function UpdateWindow( _
        ByVal hWnd As IntPtr) _
        As Boolean
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function RedrawWindow( _
        ByVal hWnd As IntPtr, _
        ByVal lprcUpdate As IntPtr, _
        ByVal hrgnUpdate As IntPtr, _
        ByVal flags As RDW) _
        As Boolean
    End Function

    'RedrawWindow() flags
    Private Enum RDW As Integer
        INVALIDATE = &H1
        INTERNALPAINT = &H2
        [ERASE] = &H4
        VALIDATE = &H8
        NOINTERNALPAINT = &H10
        NOERASE = &H20
        NOCHILDREN = &H40
        ALLCHILDREN = &H80
        UPDATENOW = &H100
        ERASENOW = &H200
        FRAME = &H400
        NOFRAME = &H800
    End Enum

    <DllImport("gdi32.dll")> _
    Private Shared Function CreateDC( _
        ByVal pszDriver As String, _
        ByVal pszDevice As String, _
        ByVal pszOutput As String, _
        ByVal pInitData As IntPtr) _
        As IntPtr
    End Function


    <DllImport("user32.dll")> _
    Shared Function EnumDisplayMonitors( _
        ByVal hdc As IntPtr, _
        ByVal lprcClip As IntPtr, _
        ByVal lpfnEnum As MonitorEnumProc, _
        ByRef dwData As IntPtr) _
        As Boolean
    End Function

    Delegate Function MonitorEnumProc( _
        ByVal hMonitor As IntPtr, _
        ByVal hdcMonitor As IntPtr, _
        ByVal lprcMonitor As IntPtr, _
        ByRef dwData As IntPtr) _
        As Boolean

    Dim WithEvents Timer As New Timer
    Dim MonitorList As New List(Of IntPtr)

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        EnumDisplayMonitors(Nothing, Nothing, AddressOf EnumMonitors, IntPtr.Zero)
        Me.Timer.Interval = 10
        Me.Timer.Enabled = True
    End Sub

    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        Me.Timer.Enabled = False
        Me.RefreshDesktop()
    End Sub

    Private Function EnumMonitors(ByVal hMonitor As IntPtr, ByVal hdcMonitor As IntPtr, ByVal lprcMonitor As IntPtr, ByRef dwData As IntPtr) As Boolean
        Me.MonitorList.Add(hMonitor)
        Return True
    End Function

    Private Sub Timer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Timer.Tick
        Select Case Control.MouseButtons
            Case Windows.Forms.MouseButtons.Left
                Dim hDC As IntPtr = CreateDC(Screen.AllScreens(1).DeviceName, Nothing, Nothing, IntPtr.Zero)
                Using g As Graphics = Graphics.FromHdc(hDC, Me.MonitorList(1))
                    g.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality
                    g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
                    Dim p As Point = Control.MousePosition
                    Const r As Integer = 5
                    g.FillEllipse(Brushes.Red, p.X - r, p.Y - r, r * 2, r * 2)
                End Using
                ReleaseDC(IntPtr.Zero, hDC)
            Case Windows.Forms.MouseButtons.Right
                Me.RefreshDesktop()
        End Select
    End Sub

    Private Sub RefreshDesktop()
        RedrawWindow(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, RDW.ALLCHILDREN Or RDW.UPDATENOW Or RDW.ERASE Or RDW.INVALIDATE Or RDW.INTERNALPAINT)
        UpdateWindow(IntPtr.Zero)
    End Sub

End Class
■No14985に返信(YASさんの記事)
> 2006/03/23(Thu) 09:42:22 編集(投稿者)
>
> たびたび似たようなスレッドをたてて申し訳ありません。
>
> GetDC(IntPtr.Zero)でプライマリモニタのデバイスコンテキストハンドルが取得できることはわかったのですが,マルチディスプレイ環境でセカンダリモニタのデバイスコンテキストハンドルが取得できません。

  マルチモニタの画面のハードコピーソフトを作りましたので、その辺の情報
  は、かなり苦労して探しました。

  プライマリ以外のhMonitorのhDCは、基本的には仮想の全ウィンドウの一部と
  して管理されているので、あるプライマリ以外のhDCと言うものは、プライマ
  リの原点からの距離を加算して、仮想全画面のhDCに描画すれば、そのプライ
  マリ以外の指定した画面位置に描画されます。

  GetDC のAPIの説明に、それなりに説明がありますが・・・

  GetSystemMetrics(SM_CMONITORS)
  で、ディスプレイ台数を取得し
  GetSystemMetrics(SM_XVIRTUAL_SCREEN)
  GetSystemMetrics(SM_YVIRTUAL_SCREEN)
  GetSystemMetrics(SM_XVIRTUAL_SCREEN)+GetSystemMetrics(SM_CXVIRTUAL_SCREEN)
  GetSystemMetrics(SM_YVIRTUAL_SCREEN)+GetSystemMetrics(SM_CYVIRTUAL_SCREEN)
  で仮想画面の大きさを取得

  MonitorFromPoint( p , MONITOR_DEFAULTTONEAREST)
  を使って、仮想画面中の全HMONITORを探し(p.x/p.y を可変させて)
  GetMonitorInfo( hMon , ...)
  のhMonにMontorFormPointで探して取得した値を代入し、各々の画面
  の大きさを取得

  プライマリ画面の位置・大きさから、プライマリと称する画面の大きさ
  位置を算出して、仮想画面のhDCに描画・・・

  となると思います。

  ご参考までに・・・

以上。
オショウ様,ご返答ありがとうございます。

仮想画面の大きさはSystemInformation.VirtualScreenで取得できるようです。
各画面の大きさはScreen.AllScreens().Boundsで取得できるようです。
ある点が含まれる各画面の大きさはScreen.GetBounds(p)で取得できるようです。

>プライマリ画面の位置・大きさから、プライマリと称する画面の大きさ
>位置を算出して、仮想画面のhDCに描画・・・

ここのところがいろいろ調べたつもりなのですが,わかりません。
全部の画面が含まれた大きな仮想画面の中で実際の画面の位置を計算で特定し,
そこに描画するという意味だと捉えましたが....
仮想画面のhDCとはどのように取得するのでしょう?
■No14998に返信(YASさんの記事)
> オショウ様,ご返答ありがとうございます。
>
> ここのところがいろいろ調べたつもりなのですが,わかりません。
> 全部の画面が含まれた大きな仮想画面の中で実際の画面の位置を計算で特定し,
> そこに描画するという意味だと捉えましたが....

  その通りです。

> 仮想画面のhDCとはどのように取得するのでしょう?

  GetDC( NULL ); で取れますので・・・

以上。
オショウ様,ご返答ありがとうございます。

■No15013に返信(オショウさんの記事)
>   GetDC( NULL ); で取れますので・・・

やはり...
いえ,実はそうかなとは思っていたのですが,GetDC(IntPtr.Zero)ではGDIではうまくいくのですが,GDI+(.NETのGraphicsクラス)ではうまくいかないのです。

試してみると,CopyFromScreenメソッドで,画面を取得することには問題ありませんでした。
試したことを全部書くと長くなってしまいますので,下のリンクにまとめました。
http://homepage1.nifty.com/yasunari/VB/VB2005/ScreenCaptureMulti.htm
http://homepage1.nifty.com/yasunari/VB/VB2005/ScreenCaptureMultiBitBlt.htm

「仮想画面全体のはずのGetDC(IntPtr.Zero)に対して,Graphics.Draw****がプライマリモニタ以外に描画できない」というのが現在の悩みです。
■No15016に返信(YASさんの記事)
> 「仮想画面全体のはずのGetDC(IntPtr.Zero)に対して,Graphics.Draw****がプライマリモニタ以外に描画できない」というのが現在の悩みです。

  .NETだけのコーディングでは不可避です。
  残念ながら、API使って下さい。

※ .NETのみで実現できる方法があればいいのですが・・・

以上。
オショウ様,何度もご返答いただきありがとうございます。

■No15019に返信(オショウさんの記事)
>   .NETだけのコーディングでは不可避です。
>   残念ながら、API使って下さい。

うぅ。本当に残念です....

今回みなさまに教えていただいたこと,自分で調べてわかったことをWebページにまとめました。よろしかったらご覧ください。

http://homepage1.nifty.com/yasunari/VB/VB2005/DrawToScreen.htm

DOBON.NET | プログラミング道 | プログラミング掲示板