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

■35510 / 親記事)  重なったPictureBox同士を透過する方法
  
□投稿者/ 本体は眼鏡 一般人(4回)-(2023/09/05(Tue) 08:53:37)
  • アイコン環境/言語:[Windows 10 Pro 64bit/VB.net] 
    分類:[.NET] 

    VB.net(Microsoft Visual Studio Community 2022 (64 ビット) )を使用しています。

    お世話になります。
    またまたお尋ねしたい事があり、ご教示いただけますと幸いです。

    重なっているPictureBoxにフォームの背景色ではなくて、画像の重なっている部分を表示させたいのですが、実現出来ずに困っております。

    添付画像の様に、Windowsフォームデザイナ上にPictureBox同士を隣合せで配置しており、
    Imageプロパティに、背景を透過処理したGIF画像を設定しています。

    https://teratail.com/questions/163912
    を参考にし、どちらのPictureBoxもBackgroundColorプロパティにはTransparentを設定済みです。

129×118
イメージ
1693871617.jpg
/9KB
マルチポストを報告
違反を報告
引用返信 削除キー/
■35511 / ResNo.1)  Re[1]: 重なったPictureBox同士を透過する方法
□投稿者/ 魔界の仮面弁士 大御所(1565回)-(2023/09/05(Tue) 09:52:08)
  • アイコン
    No35510に返信(本体は眼鏡さんの記事)
    > 重なっているPictureBoxにフォームの背景色ではなくて、
    そういう重ね合わせは、WinForms ではなく XAML ベースの方が楽なのですけれどね。

    WinForms のコントロールは、レイヤー間では背景部を転送描画するだけで、
    前景部を透過させる仕組みはありません。PictureBox 上に PictureBox を
    Controls.Add させた場合も同様です。

    そのため、透過(あるいは半透明の)画像を複数重ね合わせて描画したい場合は、
    PictureBox を複数枚使うのではなく、単一の PictureBox 上に
    自前で複数の画像を Graphics.DrawImage することで実現します。

    具体的な手順は、ここ DOBON.NET のサイトの
    [.NET プログラミング Tips] > [画像、印刷(GDI+)]
    当たりをご覧ください。
    https://dobon.net/vb/dotnet/graphics/index.html


    Graphics クラスのインスタンスの作り方ですが、特に理由がなければ
    CreateGraphics メソッドは使わないようにします。

    変化が大きい場合は、PictureBox の Paint イベントにて
    e.Graphics に対して DrawImage するのが良いでしょう。

    変化が少ない場合は、PictureBox と同じサイズの Bitmap を New して、
    Graphics.FromImage(…) に対して DrawIamge し、その Bitmap を
    PictureBox の BackgroundImage プロパティに割り当てるようにします。

    要件によっては、BackgroundImage と Image と Paint イベントの 3 つを
    組み合わせて使うこともありますね。

違反を報告
引用返信 削除キー/
■35512 / ResNo.2)  Re[1]: 重なったPictureBox同士を透過する方法
□投稿者/ KOZ 一般人(25回)-(2023/09/05(Tue) 12:01:54)
  • アイコン
    2023/09/05(Tue) 19:59:32 編集(投稿者)
    2023/09/05(Tue) 19:59:28 編集(投稿者)
    
    ■No35510に返信(本体は眼鏡さんの記事)
    
    親の子コントロールの処理がまずかったので修正しています。
    
    いつか作ろうと思って作ってなかったんですが、いい機会なのでカスタムコントロールを作ってみました。
    (1) 背景に親コントロールを描画
    親コントロールは子コントロールを除いて描画する(でないと自分が描画されてしまい、ループする)
    描画は WM_PRINTCLIENT メッセージを送ることで行っています。
    
    (2) 親の子コントロールを描画
    親の子コントロールは、その子も描画するので DrawToBitmap で良い
    
    Option Strict On
    Imports System.Runtime.InteropServices
    
    Public Class TransparentPictureBox
        Inherits PictureBox
    
        Protected Overrides Sub OnPaintBackground(pevent As PaintEventArgs)
            MyBase.OnPaintBackground(pevent)
            If Parent IsNot Nothing Then
                DrawParent(pevent.Graphics, Parent)
                Dim ctls = Parent.Controls
                Dim i = ctls.Count - 1
                While i >= 0
                    Dim ctrl As Control = ctls(i)
                    If ctrl Is Me Then Exit While
                    If ctrl.Visible Then
                        DrawSameHierarchy(pevent.Graphics, ctrl)
                    End If
                    i -= 1
                End While
            End If
        End Sub
    
        Private Sub DrawParent(g As Graphics, ctrl As Control)
            Dim mapRect = MapRectangle(Me, Me.ClientRectangle, ctrl)
            Dim rect = Rectangle.Intersect(ctrl.ClientRectangle, mapRect)
            If rect.Width > 0 AndAlso rect.Height > 0 Then
                Using bmp As New Bitmap(ctrl.Width, ctrl.Height,
                                        Imaging.PixelFormat.Format32bppRgb)
                    Using bmpG = Graphics.FromImage(bmp)
                        Dim hdc = bmpG.GetHdc()
                        Dim printParam = PRF_CLIENT Or PRF_ERASEBKGND
                        SendMessage(ctrl.Handle, WM_PRINTCLIENT,
                                    hdc, New IntPtr(printParam))
                        bmpG.ReleaseHdc()
                    End Using
                    Dim drawRect = MapRectangle(ctrl, rect, Me)
                    g.DrawImage(bmp, drawRect, rect, GraphicsUnit.Pixel)
                End Using
            End If
        End Sub
    
        Private Sub DrawSameHierarchy(g As Graphics, ctrl As Control)
            Dim mapRect = MapRectangle(Me, Me.ClientRectangle, Parent)
            Dim rect = Rectangle.Intersect(ctrl.Bounds, mapRect)
            If rect.Width > 0 AndAlso rect.Height > 0 Then
                Using bmp As New Bitmap(ctrl.Width, ctrl.Height,
                                        Imaging.PixelFormat.Format32bppRgb)
                    ctrl.DrawToBitmap(bmp, New Rectangle(Point.Empty, ctrl.Size))
                    Dim drawRect = MapRectangle(Parent, rect, Me)
                    Dim clipRect = New Rectangle(
                                    rect.Location - CType(ctrl.Location, Size), rect.Size)
                    g.DrawImage(bmp, drawRect, clipRect, GraphicsUnit.Pixel)
                End Using
            End If
        End Sub
    
        Private Function MapRectangle(fromCtrl As Control,
                                      fromRect As Rectangle,
                                      toCtrl As Control) As Rectangle
            Dim rect As New RECT(fromRect)
            MapWindowPoints(fromCtrl.Handle, toCtrl.Handle, rect, 2)
            Return rect.ToRectangle()
        End Function
    
        Private Const WM_PRINTCLIENT = &H318
        Private Const PRF_CHECKVISIBLE = &H1,
                      PRF_NONCLIENT = &H2,
                      PRF_CLIENT = &H4,
                      PRF_ERASEBKGND = &H8,
                      PRF_CHILDREN = &H10
    
        <StructLayout(LayoutKind.Sequential)>
        Private Structure RECT
            Public Left, Top, Right, Bottom As Integer
            Public Sub New(r As Rectangle)
                Left = r.Left
                Top = r.Top
                Bottom = r.Bottom
                Right = r.Right
            End Sub
            Public Function ToRectangle() As Rectangle
                Return Rectangle.FromLTRB(Left, Top, Right, Bottom)
            End Function
        End Structure
    
        <DllImport("user32")>
        Private Shared Function MapWindowPoints(
                            hwndFrom As IntPtr, hwndTo As IntPtr,
                            ByRef RECT As RECT, cPoints As Integer) As Integer
        End Function
    
        <DllImport("user32", CharSet:=CharSet.Auto)>
        Private Shared Function SendMessage(
                            hwnd As IntPtr, msg As Integer,
                            wparam As IntPtr, lparam As IntPtr) As IntPtr
        End Function
    
    End Class
    

違反を報告
引用返信 削除キー/
■35513 / ResNo.3)  Re[2]: 重なったPictureBox同士を透過する方法
□投稿者/ KOZ 一般人(26回)-(2023/09/05(Tue) 12:10:16)
  • アイコンNo35512に返信(KOZの記事)
    補足です。
    Controls コレクションは Z オーダー順に並んでいるので、後ろから列挙して自分が出てきたら終了です。
    ただ、SetWindowPos で並びを変えてしまうと合わなくなるかもしれないので、API で列挙したほうがいいかもしれません。
違反を報告
引用返信 削除キー/
■35514 / ResNo.4)  Re[2]: 重なったPictureBox同士を透過する方法
□投稿者/ 本体は眼鏡 一般人(5回)-(2023/09/06(Wed) 08:38:37)
  • アイコンNo35511に返信(魔界の仮面弁士さんの記事)
    す。

    魔界の仮面弁士 様 お返事ありがとうございます。
    返信遅くなり申し訳ありません。

    > そのため、透過(あるいは半透明の)画像を複数重ね合わせて描画したい場合は、
    > PictureBox を複数枚使うのではなく、単一の PictureBox 上に
    > 自前で複数の画像を Graphics.DrawImage することで実現します。
    一つ一つのPictureBoxが機器の死活監視状態を表しており、
    300個以上あるため、私一人で実装するのは納期的にもちょっと難しそうです…
    プロパティの変更で出来る物かと安易に考えていました。
    今回はPictureBoxのサイズを小さくし、重ならないギリギリで配置することで回避しようと思います。
    せっかく教えて頂いたのに申し訳ありません…

    > https://dobon.net/vb/dotnet/graphics/index.html
    こちら折れ線グラフ作成等の際にとても助かりました。
    ありがとうございます。

    > 要件によっては、BackgroundImage と Image と Paint イベントの 3 つを
    > 組み合わせて使うこともありますね。
    今のプログラムが完成したら、頂いたページを参照しながらサンプルプログラムを作成し、今後の参考資料にしたいと思います。
解決み!
違反を報告
引用返信 削除キー/
■35515 / ResNo.5)  Re[3]: 重なったPictureBox同士を透過する方法
□投稿者/ 本体は眼鏡 一般人(6回)-(2023/09/06(Wed) 08:40:23)
  • アイコン2023/09/06(Wed) 14:17:22 編集(投稿者)
    2023/09/06(Wed) 14:17:13 編集(投稿者)

    No35513に返信(KOZさんの記事)
    > ■No35512に返信(KOZの記事)

    KOZ 様 お返事・サンプルソースのご提供ありがとうございます。

    プロパティ設定程度で実現出来るかと安易に考えていました。
    頂いたソースを理解して、私一人で実装するには納期的にもちょっと難しそうです…

    これまで何度かカスタムコントロールの作成に挫折してきましたが、
    今のプログラムが完成したら、頂いたソースを基にカスタムコントロールに挑戦してみたいと思います。
解決み!
違反を報告
引用返信 削除キー/
■35516 / ResNo.6)  Re[4]: 重なったPictureBox同士を透過する方法
□投稿者/ KOZ 一般人(27回)-(2023/09/06(Wed) 18:19:00)
  • アイコンNo35515に返信(本体は眼鏡さんの記事)
    > 頂いたソースを理解して、私一人で実装するには納期的にもちょっと難しそうです…
    >
    > これまで何度かカスタムコントロールの作成に挫折してきましたが、
    > 今のプログラムが完成したら、頂いたソースを基にカスタムコントロールに挑戦してみたいと思います。

    VisualStyle が無効でも透過できるよう作りましたが、VisualStyle が有効で BackColor = Color.Transparent のときのみ透過するのであれば、親の描画は PictureBox に任せて不要になるので、コードはかなり短くなります。
    挑戦するときは考慮してみてください。
解決み!
違反を報告
引用返信 削除キー/
■35517 / ResNo.7)  Re[5]: 重なったPictureBox同士を透過する方法
□投稿者/ KOZ 一般人(28回)-(2023/09/09(Sat) 11:50:05)
  • アイコン
    2023/09/09(Sat) 11:50:38 編集(投稿者)
    
    Windows 8 から子ウインドウがレイヤードウインドウになれるので、アプリケーションマニュフェストを追加して
    
          <!-- Windows 8 -->
          <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
    
    のコメントアウトを解除すると、簡単に透過できますね。
    
    # GetWindowLong/SetWindowLong を使っているのは手抜きです。(長くなるので)
    # プロセスが 64bit なら GetWindowLongPtr,SetWindowLongPtr を使うのが正式なやり方。
    # 64bit で SetWindowLong を使うと成功しても戻り値がゼロになってしまいます。
    
    Imports System.Runtime.InteropServices
    
    Public Class Form1
    
        Private Const WS_EX_LAYERED = &H80000
        Private Const GWL_EXSTYLE = -20
        Private Enum LWA
            COLORKEY = &H1
            ALPHA = &H2
        End Enum
    
        Private Sub PictureBox_HandleCreated(sender As Object, e As EventArgs) _
                    Handles PictureBox1.HandleCreated,
                            PictureBox2.HandleCreated
            Dim pic = DirectCast(sender, PictureBox)
            pic.Image = My.Resources.megane_hikaru_woman
            pic.SizeMode = PictureBoxSizeMode.Zoom
            pic.BackColor = Color.Gray
            Dim dwStyle = GetWindowLong(pic.Handle, GWL_EXSTYLE)
            dwStyle = dwStyle Or WS_EX_LAYERED
            SetWindowLong(pic.Handle, GWL_EXSTYLE, dwStyle)
            SetLayeredWindowAttributes(
                    pic.Handle, ColorTranslator.ToWin32(pic.BackColor),
                    0, LWA.COLORKEY)
        End Sub
    
        <DllImport("user32.dll")>
        Private Shared Function GetWindowLong(
                    hWnd As IntPtr, nIndex As Integer) As Integer
        End Function
    
        <DllImport("user32.dll")>
        Private Shared Function SetWindowLong(
                    hWnd As IntPtr, nIndex As Integer,
                    dwNewLong As Integer) As Integer
        End Function
    
        <DllImport("user32.dll")>
        Private Shared Function SetLayeredWindowAttributes(
                    hWnd As IntPtr, crKey As Integer,
                    bAlpha As Byte, dwFlags As LWA) As Boolean
        End Function
    
    End Class
    
    透過画像のまわりに BackColor が残ってしまいますが、目立たない色を設定しておくといいです。
    
    
    

解決み!
268×285 => 235×250
イメージ
layeredWindow.jpg
/15KB
違反を報告
引用返信 削除キー/



スレッド内ページ移動 / << 0 >>

このスレッドに書きこむ

Mode/  Pass/


- Child Tree -