0

I have a very simple winforms project. It has a main form and a user control.

The UserControl is formed of a panel and a button.

I want to place an image on top of the UserControl. Any image will do.

The problem is that the image is displayed underneath the user control and not on top of the user control (on top of the button belonging to the user control).

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace DragImageOnUserControl
{
    public partial class Form1 : Form
    {
        private string _directoryContainingThePieces;
        private string _imageFileName;
        private Image _image;

        public Form1()
        {
            InitializeComponent();

            this.ClientSize = new Size(1000, 800);
            this.BackColor = Color.Bisque;

            _directoryContainingThePieces = @"g:\Programming\Chess\ChessboardWithPieces\Chess Pieces\";
            _imageFileName = "White King.PNG";
            _image = Image.FromFile(_directoryContainingThePieces + _imageFileName);

            UserControl_PanelAndButton panelAndButton = new UserControl_PanelAndButton();
            panelAndButton.Location = new Point(50, 50);
            panelAndButton.ImageToDisplay = _image; // Set the image after initializing _image.
            this.Controls.Add(panelAndButton);
        }
    }
}

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace DragImageOnUserControl
{
    public partial class UserControl_PanelAndButton : UserControl
    {
        public UserControl_PanelAndButton()
        {
            InitializeComponent();

            _panel1 = new Panel();
            _panel1.Location = new Point(20, 20);
            _panel1.Size = new Size(500, 500);
            _panel1.BackColor = Color.Cyan;
            _panel1.Parent = this; // Set the panel's parent to the user control
            _panel1.SendToBack(); // Send the panel to back.

            _button1 = new Button();
            _button1.Location = new Point(200, 200);
            _button1.Size = new Size(75, 75);
            _button1.BackColor = Color.Orchid;
            _panel1.Controls.Add(_button1); // Add the button to the panel
            _button1.SendToBack(); // Send the button to back.
        }

        private Panel _panel1;
        private Button _button1;

        private Image _imageToDisplay;
        public Image ImageToDisplay
        {
            get { return _imageToDisplay; }
            set 
            { 
                _imageToDisplay = value;
                Refresh(); // Force a redraw when the image changes.
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            // Draw the image.
            if (_imageToDisplay != null)
            {
                int x = Math.Max(0, (Width - _imageToDisplay.Width) / 2); // Center the image horizontally.
                int y = Math.Max(0, (Height - _imageToDisplay.Height) / 2); // Center the image vertically.

                e.Graphics.DrawImage(_imageToDisplay, new Point(x, y));
            }
        }
    }
}

This is the whole code.

I have another very similar project. It has a main form and instead of the UserControl it has a panel.

I use the same image and the image is displayed on top of the panel. I can even drag it around within the boundaries of the panel.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace DragImageOnForm
{
    public partial class Form1 : Form
    {
        private Panel _panel1;
        private string _directoryContainingThePieces;
        private string _imageFileName;
        private Image _image;
        private Point _imageLocation;
        private Point _dragOffset;
        private bool _isDragging;
        private Rectangle _previousImageRect;

        public Form1()
        {
            InitializeComponent();

            this.ClientSize = new Size(1000, 800);
            this.BackColor = Color.Bisque;

            _panel1 = new Panel();
            _panel1.Location = new Point(200, 200);
            _panel1.Size = new Size(500, 500);
            _panel1.BackColor = Color.Violet;

            _panel1.MouseDown += Panel1_MouseDown;
            _panel1.MouseMove += Panel1_MouseMove;
            _panel1.MouseUp += Panel1_MouseUp;
            _panel1.Paint += Panel1_Paint; // Attach the paint event handler.

            this.Controls.Add(_panel1);

            _directoryContainingThePieces = @"g:\Programming\Chess\ChessboardWithPieces\Chess Pieces\";
            _imageFileName = "White King.PNG";
            _image = Image.FromFile(_directoryContainingThePieces + _imageFileName);
            _imageLocation = new Point(100, 100); // Initial position.
            _isDragging = false;

            // Initialize previousImageRect with initial image location and size.
            _previousImageRect = new Rectangle(_imageLocation, _image. Size);
        }

        private void Panel1_Paint(object sender, PaintEventArgs e)
        {
            // Draw the image at the current location on the panel.
            e.Graphics.DrawImage(_image, _imageLocation);
        }

        private void Panel1_MouseDown(object sender, MouseEventArgs e)
        {
            // Check if the mouse click is within the image's bounds.
            Rectangle imageRect = new Rectangle(_imageLocation, _image. Size);
            if (imageRect.Contains(e.Location))
            {
                _isDragging = true;
                _dragOffset = new Point(e.X - _imageLocation.X, e.Y - _imageLocation.Y);
            }
        }

        private void Panel1_MouseMove(object sender, MouseEventArgs e)
        {
            if (_isDragging)
            {
                // Update the image's location based on the mouse position.
                _imageLocation = new Point(e.X - _dragOffset.X, e.Y - _dragOffset.Y);

                // Calculate the union of the previous and current image rectangles
                // and invalidate only that region to minimize redraw.
                Rectangle newImageRect = new Rectangle(_imageLocation, _image.Size);
                Rectangle updateRect = Rectangle.Union(_previousImageRect, newImageRect);

                // Invalidate to update rectangle.
                _panel1.Invalidate(updateRect);

                // Update previous image rectangle.
                _previousImageRect = newImageRect;
            }
        }

        private void Panel1_MouseUp(object sender, MouseEventArgs e)
        {
            _isDragging = false;
        }
    }
}

This project works fine. The only difference is that here it is a panel the image is on top of.

In the project that with the UserControl, the image should be displayed on top of the UserControl and not beneath it.

Any help would be greatly appreciated.

I changed the location of the instance of the image so that it exceeds the borders of the UserControl.

The image is indeed present, but it is under the UserControl. Only the part of the image that exceeds the boundaries of the UserControl is visible.

I need the image to be on top of the User Control (on top of the button belonging to the UserControl) and not under it.

user2102327
  • 59
  • 2
  • 6
  • 19
  • It's working fine. The image is being drawn overtop of the user control. The panel and the button are over the panel so they do obscure the image to a degree, but you are painting the image. I tested your code. It's working. – Enigmativity Aug 28 '23 at 05:01
  • @Enigmativity Unfortunately I do not see the image displayed in my project. I placed it also on the form itself and there it appears. – user2102327 Aug 28 '23 at 06:04
  • It sounds like a PEBKAC problem now. – Enigmativity Aug 28 '23 at 06:10
  • @Enigmativity I admit that I was not aware that the image was disoplayed since it was obstructed. That does not mean that there is no problem. I need it displayed on top of the userf control and not underneath. I never stated that I am a programming expert. If you have a solution please share it. – user2102327 Aug 28 '23 at 07:02
  • It is being displayed on top of the user control. You just have placed things on the user control that cover over the image. Are you trying to get it to paint over top of the panel and button too? – Enigmativity Aug 28 '23 at 07:54
  • @Enigmativity Absolutely! This is the whole task. – user2102327 Aug 28 '23 at 08:38
  • Open in Visual Studio the window _Document Outline_ while your form is opened in designer mode. In this window you can easily change the z-order of your controls. – Oliver Aug 28 '23 at 09:02
  • @Oliver In what way to change the z-order of the controls? – user2102327 Aug 28 '23 at 09:27
  • @Oliver - Z-order won't help as you can't set the order behind the hosting control. – Enigmativity Aug 28 '23 at 09:34
  • @user2102327 - That's a far more complicated exercise. You're now drawing on all of the controls - you'd need to override the `OnPaint` method for all of the controls. Why then, do you have the panel and the button anyway? Are you just trying to layout buttons under the image so that they can be clicked? What's the purpose of doing it? – Enigmativity Aug 28 '23 at 09:36
  • @Enigmativity The code I posted is a simplified project just in order to find a way to place an image upon a button within a UserControl. My real project is a chessboard and the images are chess pieces with transparent background. I want to be able to drag the pieces from one square (button) to another and also see the background colour of the square around the piece (as it has a transparent background). – user2102327 Aug 28 '23 at 10:11
  • @user2102327 - Then that should be easy. If you place panels that have transparent PNGs you should be able to drag them around. Let me see if I can put together a demo. – Enigmativity Aug 28 '23 at 10:44
  • @Enigmativity I do not place panels. I have a UserControl formed of one panel and 64 buttons. I want to place images of chess pieces on top of these buttons (the images have transparent background). – user2102327 Aug 28 '23 at 11:00
  • @Enigmativity You said in one of your comments: you'd need to override the OnPaint method for all of the controls. In my simplified project there are not so many controls: just one panel and one button. Please show me how to override the OnPaint method for the button and I shall do it for all 64 buttons in my project. – user2102327 Aug 29 '23 at 02:31
  • @user2102327 - You don't need to override the `OnPaint` if you use picture boxes. Otherwise it's exactly the same as you have already done with the panel anyway. Just make your own class that inherits `Button` and override the `OnPaint` method. – Enigmativity Aug 29 '23 at 04:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/255091/discussion-between-user2102327-and-enigmativity). – user2102327 Aug 29 '23 at 04:37
  • @Enigmativity I am having trouble with the PictureBox. My image has transparent background. When I use the way you showed in your answer, the transparency of the image disappears. The _dragPictureBox has an opaque rectangle background. – user2102327 Aug 29 '23 at 09:59
  • @user2102327 - Did you set the parent? `_pieces[i].Parent = _background;` – Enigmativity Aug 29 '23 at 10:51
  • @Enigmativity _background.Controls.Add(piece); – user2102327 Aug 29 '23 at 13:19
  • @user2102327 - Did you also set the parent? That's important. Adding to the controls collection isn't enough. – Enigmativity Aug 29 '23 at 23:23
  • Look at [this example](https://stackoverflow.com/a/74894576/3110834), which shows a chess board, and shows how you can move the chess pieces around. – Reza Aghaei Aug 30 '23 at 14:13
  • You can also take a look at [this example](https://stackoverflow.com/a/36102074/3110834), which shows how you can show how you can show picture box controls as layers over other controls. – Reza Aghaei Aug 30 '23 at 14:17
  • You are calling `BringToFront` on both panel and button inside `UserControl_PanelAndButton`. Why do you do that if you don't want them to be in front of your image? – Mat J Aug 31 '23 at 07:36
  • @Mat J You are right. I corrected in both places to: _panel1.SendToBack(); // Send the panel to back. and _button1.SendToBack(); but the situation did not change. The image is still underneath the button. – user2102327 Aug 31 '23 at 11:17
  • Ok, that won't help now that I see your `onPaint` is using the `graphics` of `userControl`. See your problem is that you are drawing the image to the usercontrol which is actually the backdrop of panel and button. In your other project, you have a panel without anything inside it, so when you draw image to the panel(which is on top of everything else I guess), it appears on top of everything else. What you need to do is add an image control and call `BringToFront` and then change its left and top positions when you drag. That way it will do what you expect to see. – Mat J Aug 31 '23 at 12:16

2 Answers2

1

Here's a Windows Forms app that does what you want. It uses Microsoft Reactive Framework (NuGet System.Reactive.Windows.Forms to get the components).

public class DragDropDemo : System.Windows.Forms.Form
{
    private System.Windows.Forms.PictureBox _background = new System.Windows.Forms.PictureBox()
    {
        Size = new System.Drawing.Size(290, 290),
        ImageLocation = @"D:\Projects\Drag-Drop Demo\Painting.jpg",
    };
    
    private System.Windows.Forms.PictureBox[] _pieces = new []
    {
        new System.Windows.Forms.PictureBox() { Size = new System.Drawing.Size(60, 60), ImageLocation = @"D:\Projects\Drag-Drop Demo\Chess_bdt60.png", BackColor = System.Drawing.Color.Transparent, },
        new System.Windows.Forms.PictureBox() { Size = new System.Drawing.Size(60, 60), ImageLocation = @"D:\Projects\Drag-Drop Demo\Chess_blt60.png", BackColor = System.Drawing.Color.Transparent, },
        new System.Windows.Forms.PictureBox() { Size = new System.Drawing.Size(60, 60), ImageLocation = @"D:\Projects\Drag-Drop Demo\Chess_kdt60.png", BackColor = System.Drawing.Color.Transparent, },
        new System.Windows.Forms.PictureBox() { Size = new System.Drawing.Size(60, 60), ImageLocation = @"D:\Projects\Drag-Drop Demo\Chess_klt60.png", BackColor = System.Drawing.Color.Transparent, },
    };
    
    public DragDropDemo()
    {
        this.DoubleBuffered = true;
        this.Size = new System.Drawing.Size(305, 305);
        this.Controls.Add(_background);

        for (int i = 0; i < _pieces.Length; i++)
        {
            _pieces[i].Parent = _background;
            _pieces[i].Location = new System.Drawing.Point(10 + i * 70, 10);
            _background.Controls.Add(_pieces[i]);
            this.EnableDragAndDrop(_pieces[i]);
        }
    }
    
    private void EnableDragAndDrop(System.Windows.Forms.PictureBox pb)
    {
        var downs =
    Observable
        .FromEventPattern<System.Windows.Forms.MouseEventHandler, System.Windows.Forms.MouseEventArgs>(
            h => pb.MouseDown += h,
            h => pb.MouseDown -= h)
        .Select(x => x.EventArgs);

        var moves =
            Observable
                .FromEventPattern<System.Windows.Forms.MouseEventHandler, System.Windows.Forms.MouseEventArgs>(
                    h => pb.MouseMove += h,
                    h => pb.MouseMove -= h)
                .Select(x => x.EventArgs);

        var ups =
            Observable
                .FromEventPattern<System.Windows.Forms.MouseEventHandler, System.Windows.Forms.MouseEventArgs>(
                    h => pb.MouseUp += h,
                    h => pb.MouseUp -= h)
                .Select(x => x.EventArgs);

        var deltas = from down in downs
                     from move in moves.TakeUntil(ups)
                     select new System.Drawing.Point()
                     {
                         X = move.X - down.X,
                         Y = move.Y - down.Y
                     };

        var subscription =
                deltas
                    .Subscribe(d =>
                        pb.SetBounds(
                            pb.Location.X + d.X,
                            pb.Location.Y + d.Y,
                            0,
                            0,
                            System.Windows.Forms.BoundsSpecified.Location));
    }
}

When I run that I get this displayed:

before dragging

I can then drag and drop:

after dragging

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • The image is indeed present, but it is under the UserControl. Only the part of the image that exceeds the boundaries of the UserControl is visible. I need the image to be on top of the UserControl and not under it. – user2102327 Aug 28 '23 at 06:15
  • It's not under the user control. It's on the user control. You have placed a panel on the user control and that panel is covering the image. – Enigmativity Aug 28 '23 at 07:59
1

Based on Enigmativity's answer, I came up with this program.

It contains a user control formed of 4 border panels, a background PictureBox and a button.

The image is placed on top of the button belonging to the user control and the image also preserves its transparent background. As the image's size is smaller than the button's, the button is visible around the outer contour of the image (not a rectangle).

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace DragAndDropIamgeOnUserContol
{
    public partial class UserControl_BorderPanelsBackgroundPictureBoxAndButton : UserControl
    {
        private Panel _panel_Top;
        private Panel _panel_Bottom;
        private Panel _panel_Left;
        private Panel _panel_Right;
        private PictureBox _background;
        private Button _button1;

        public UserControl_BorderPanelsBackgroundPictureBoxAndButton()
        {
            InitializeComponent();

            this. Size = new Size(600, 600);

            _panel_Top = new Panel();
            _panel_Top.Location = new Point(20, 20);
            _panel_Top.Size = new Size(310, 30);
            _panel_Top.BackColor = Color.Blue;
            this.Controls.Add(_panel_Top);

            _panel_Bottom = new Panel();
            _panel_Bottom.Location = new Point(20, 300);
            _panel_Bottom.Size = new Size(310, 30);
            _panel_Bottom.BackColor = Color.Blue;
            this.Controls.Add(_panel_Bottom);

            _panel_Left = new Panel();
            _panel_Left.Location = new Point(20, 20);
            _panel_Left.Size = new Size(30, 310);
            _panel_Left.BackColor = Color.Blue;
            this.Controls.Add(_panel_Left);

            _panel_Right = new Panel();
            _panel_Right.Location = new Point(300, 20);
            _panel_Right.Size = new Size(30, 310);
            _panel_Right.BackColor = Color.Blue;
            this.Controls.Add(_panel_Right);

            _background = new PictureBox();
            _background. Location = new Point(50, 50);
            _background. Size = new Size(250, 251);
            _background.BackColor = Color.Orange;
            this.Controls.Add(_background);

            _button1 = new Button();
            _button1.Size = new Size(100, 100);
            _button1.Location = new Point((_background.ClientSize.Width - _button1.Width) / 2, (_background.ClientSize.Height - _button1.Height) / 2);
            _button1.BackColor = Color.Aquamarine;
            _background.Controls.Add(_button1);
            _button1.SendToBack();
        }
    }
}


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace DragAndDropIamgeOnUserContol
{
    public partial class Form1 : Form
    {
        private UserControl_BorderPanelsBackgroundPictureBoxAndButton borderPanelsBackgroundPictureBoxAndButton;
        private PictureBox _chessPiece;
        private Point _offset;

        public Form1()
        {
            InitializeComponent();

            this.DoubleBuffered = true;
            this.Size = new Size(1000, 800);
            this.BackColor = Color.Bisque;

            borderPanelsBackgroundPictureBoxAndButton = new UserControl_BorderPanelsBackgroundPictureBoxAndButton();
            borderPanelsBackgroundPictureBoxAndButton.Location = new Point(0, 0);
            this.Controls.Add(borderPanelsBackgroundPictureBoxAndButton);

            _chessPiece = new PictureBox();
            _chessPiece.Size = new Size(100, 100);
            _chessPiece.Image = Image.FromFile(@"g:\Programming\Chess\ChessboardWithPieces\Chess Pieces\White King.PNG");
            _chessPiece.BackColor = Color.Transparent;
            _chessPiece.SizeMode = PictureBoxSizeMode.AutoSize;
            _chessPiece.Location = new Point(280, 250);
            _chessPiece.Parent = borderPanelsBackgroundPictureBoxAndButton;
            borderPanelsBackgroundPictureBoxAndButton.Controls.Add(_chessPiece);
            _chessPiece.BringToFront();

            ExcludeTransparentImagePortions();

            _chessPiece.MouseDown += ChessPiece_MouseDown;
            _chessPiece.MouseMove += ChessPiece_MouseMove;
            _chessPiece.MouseUp += ChessPiece_MouseUp;
        }

        /// <summary>
        /// The purpose of the ExcludeTransparentImagePortions method is to create a region that defines which parts of the PictureBox _piece 
        /// are considered "active" or "clickable" and which parts are not. 
        /// This is achieved by excluding regions that correspond to transparent or partially transparent pixels in the image.
        ///     1. Initialization: 
        ///        The method starts by getting the image of the PictureBox _piece as a Bitmap object (pieceBitmap) and initializing a Region object (pieceRegion) 
        ///        that will hold the information about the active and inactive areas of the PictureBox.
        ///     2. Union of Initial Region:
        ///        Initially, the entire area of the PictureBox _piece is added to the pieceRegion using the Union method.
        ///        This effectively sets up the initial state of the region where all pixels in the PictureBox are considered active.
        ///     3. Calculating Exclusion Region: 
        ///        The method then goes through each pixel in the image using nested loops.
        ///        For each pixel, it checks if the alpha channel value of that pixel (indicated by .GetPixel(x, y).A) is equal to 0.
        ///        An alpha value of 255 indicates a fully opaque pixel, while any value less than 255 indicates a transparent or partially transparent pixel.
        ///        An alpha value of 0 indicates a fully transparent pixel.
        ///     4. Excluding Transparent Pixels:
        ///        If the alpha value of the pixel is equal to 0, it means the pixel is fully transparent.
        ///        In this case, the method excludes a small rectangle (typically 1x1 pixel around the pixel whose alpha value is equal to 0) from the pieceRegion.
        ///        This effectively marks the area of 1x1 pixel around the pixel whose alpha value is equal to 0 as inactive or non-clickable.
        ///     5. Applying the Updated Region:
        ///        After processing all pixels, the pieceRegion contains the updated information about which parts of the _piece PictureBox should be considered active 
        ///        and which parts should be inactive.
        ///        The method sets this updated region to the _piece.Region property, which determines the clickable area of the PictureBox.
        ///        
        /// The purpose of this method is to create a precise region that corresponds to the visible area of the image, excluding transparent portions.
        /// </summary>
        private void ExcludeTransparentImagePortions()
        {
            Bitmap chessPieceBitmap = (Bitmap)_chessPiece.Image;
            Region chessPieceRegion = new Region();
            chessPieceRegion.Union(new Rectangle(0, 0, chessPieceBitmap.Width, chessPieceBitmap.Height));

            // Calculate the exclusion region based on transparent pixels.
            for (int x = 0; x < chessPieceBitmap.Width; x++)
            {
                for (int y = 0; y < chessPieceBitmap.Height; y++)
                {
                    if (chessPieceBitmap.GetPixel(x, y).A == 0) // The pixel is fully transparent.
                    {
                        chessPieceRegion.Exclude(new Rectangle(x, y, 1, 1));
                    }
                }
            }
            _chessPiece.Region = chessPieceRegion;
        }

        private void ChessPiece_MouseDown(object sender, MouseEventArgs e)
        {
            _chessPiece = (PictureBox)sender;
            _offset = new Point(e.X, e.Y);
        }

        private void ChessPiece_MouseMove(object sender, MouseEventArgs e)
        {
            PictureBox chessPiece = (PictureBox)sender;
            if (e.Button == MouseButtons.Left)
            {
                chessPiece.Left = e.X + chessPiece.Left - _offset.X;
                chessPiece.Top = e.Y + chessPiece.Top - _offset.Y;
            }
        }

        private void ChessPiece_MouseUp(object sender, MouseEventArgs e)
        {
            PictureBox piece = (PictureBox)sender;
        }
    }
}
user2102327
  • 59
  • 2
  • 6
  • 19