0

I am building a puzzle game in winforms, and i want to recognize the mousedown over any piece, and move it with the mousemove. The issue is that when i touch the transparent part of the puzzle piece, i want to verify if there is any piece behind that one, and if so, start the mousemove of that other piece, got it?

I am also able to recognize if the mousedown was over the image, or if it happens in the transparent part of the puzzle piece. My problem is to get the best way to pass the mouse event to the piece behind.

Many Thanks in advance.

UPDATE 1

The following is the class for the puzzle piece:

class Peça : DrawingArea
{
    private Point _Offset = Point.Empty;
    public Image imagem
    {
        get;
        set;
    }


    protected override void OnDraw()
    {
        Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
        this.graphics.DrawImage(imagem, location);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (_Offset != Point.Empty)
        {
            Point newlocation = this.Location;
            newlocation.X += e.X - _Offset.X;
            newlocation.Y += e.Y - _Offset.Y;
            this.Location = newlocation;
        }
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        _Offset = Point.Empty;
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        Down(e);
        //Console.WriteLine(color.ToString());
    }

    public void Down(MouseEventArgs e)
    {
        Bitmap b = new Bitmap(imagem);
        Color? color = null;
        try
        {
            color = b.GetPixel(e.X, e.Y);
            if (color.Value.A != 0 && color != null)
            {
                if (e.Button == MouseButtons.Left)
                {
                    _Offset = new Point(e.X, e.Y);
                    this.BringToFront();
                }
            }
        }
        catch {
             }
    }
} 

The following code, is my DrawingArea (Panel) that i create in order to work with transparency:

abstract public class DrawingArea : Panel
{
    protected Graphics graphics;
    abstract protected void OnDraw();

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT

            return cp;
        }
    }

    public DrawingArea()
    {

    }

    protected override void OnPaintBackground(PaintEventArgs pevent)
    {

    }

    protected override void OnPaint(PaintEventArgs e)
    {

        this.graphics = e.Graphics;


        this.graphics.TextRenderingHint =
            System.Drawing.Text.TextRenderingHint.AntiAlias;
        this.graphics.InterpolationMode =
            System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
        this.graphics.PixelOffsetMode =
            System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        this.graphics.SmoothingMode =
            System.Drawing.Drawing2D.SmoothingMode.HighQuality;


        OnDraw();
    } 
}

And you can also see my Form code:

 public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams handleParam = base.CreateParams;
            handleParam.ExStyle |= 0x02000000;       
            return handleParam;
        }
    }
}

Those are my pieces, and when i touch the transparent space in the first piece, i want to pick up the second one and move it on mouseMouse instead of doing nothing...

It looks like this:

enter image description here

Apologize my bad english.

UPDATE 2

I think i am getting very close to the solution, but something strange happens now, when i touch the piece behind another one, it disappear... What am i doing wrong?

SOME CODE UPDATES

Piece Class:

class Peça : DrawingArea
{
    private Point _Offset = Point.Empty;
    public Boolean movable = false;
    public Image imagem
    {
        get;
        set;
    }

    protected override void OnDraw()
    {
        Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
        this.graphics.DrawImage(imagem, location);
    }

    public void Move(MouseEventArgs e)
    {
        if (_Offset != Point.Empty)
        {
            Point newlocation = this.Location;
            newlocation.X += e.X - _Offset.X;
            newlocation.Y += e.Y - _Offset.Y;
            this.Location = newlocation;
        }
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        _Offset = Point.Empty;
        movable = false;
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        Down(e);
        //Console.WriteLine(color.ToString());
    }

    public Boolean Down(MouseEventArgs e, bool propaga=true)
    {
        Form parentForm = (this.Parent as Form);
        Bitmap b = new Bitmap(imagem);
        Color? color = null;
        Boolean flag = false;
        try
        {
            color = b.GetPixel(e.X, e.Y);
            if (color.Value.A != 0 && color != null)
            {
                if (e.Button == MouseButtons.Left)
                {
                    _Offset = new Point(e.X, e.Y);
                    this.BringToFront();
                    flag = true;
                    movable = true;
                }
            }
            else
            {
                if(propaga)
                (this.Parent as Form1).propagaEvento(this, e);
                flag = false;

            }
            return flag; 
        }
        catch {
            return flag; }
    }
}

Form1:

public partial class Form1 : Form
{
    private List<Peça> peças;
    private Point _Offset = Point.Empty;
    public Form1()
    {
        InitializeComponent();

        peças = new List<Peça>();
        SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
        criaListaPecas();
        associaEventosPecas();      
    }

    private void associaEventosPecas()
    {
        foreach (Peça p in peças)
        {
            p.MouseMove += Form1_MouseMove;
        }
    }

    private void criaListaPecas()
    {
        peças.Clear();
        foreach (Control p in this.Controls)
        {
            if (p.GetType() == typeof(Peça))
                peças.Add((Peça)p);
        }
        Console.WriteLine(peças[0].Name);
        Console.WriteLine(peças[1].Name);
        Console.WriteLine(peças[2].Name);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams handleParam = base.CreateParams;
            handleParam.ExStyle |= 0x02000000;       
            return handleParam;
        }
    }

    private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        label1.Text = e.Location.ToString();
        gereMovimento(e);
    }

    private void gereMovimento(MouseEventArgs e)
    {
        foreach (Peça p in peças)
        {
            if (p.movable)
            {
                p.Move(e);
            }
        }
    }

    internal void propagaEvento(Peça peça, MouseEventArgs e)
    {
        foreach (Peça p in peças)
        {
            if (p != peça)
            {
                if (p.Down(e, false))
                    break;
            }
        }
    }
}

Thanks in advance again :)

Jorge Oliveira
  • 345
  • 1
  • 2
  • 13
  • We have no idea what you're talking about unless you show your code. What is the `puzzle` `Control`? `Drawing`? or what? – Sriram Sakthivel May 13 '14 at 12:25
  • This is called *hit-test*. By having mouse coords you have to test which piece will be *dragged* away. Your issue is not only transparent part, but also z-order. I'd recommend you to not use for this winform controls, but to have a model, where you store and update pieces data (location, z-order, transparency, rotation?) and do hit-test. – Sinatr May 13 '14 at 12:35
  • @SriramSakthivel check my code updates to the post, and also my dropbox image. Can you understand what i am trying to do? – Jorge Oliveira May 13 '14 at 13:50
  • @Sinatr i'm going to try the hit-test. When you say model, you are talking about a class to take care about all pieces data, right? – Jorge Oliveira May 13 '14 at 13:52
  • @HighCore many thanks for your advise. i know that winforms is not the best plataform for games. However, i'm part of a project that implements something more like an tiny OS running over the windows, and has many functionality's, some of them are games. What do you mean by "winforms doesn't really support transparency"? – Jorge Oliveira May 13 '14 at 13:57
  • Yes. And you have a small bug in your software, try to pickup something at location `0;0`. It's better to have dedicated `bool` variable for a *state* (to example, `isDragging`, `isNotMoving`, etc) than using *special values* (`Point.Empty`). – Sinatr May 13 '14 at 14:04
  • @Sinatr do you have any hittest example for winforms? – Jorge Oliveira May 13 '14 at 14:39
  • Nope and I don't recommend to use winforms for this (which means use controls collection). – Sinatr May 13 '14 at 14:40

3 Answers3

0

In general, keep a list of all your puzzle piece controls, sorted top down. When you get a mouse down event on one piece, check the transparency at that point, if it it not transparent handle the event on that piece. If it is transparent forward the event to the next piece down in your list (directly calling the event handler is probably the easiest way). Keep doing this until you either find a non transparent point, or run out of pieces.

UPDATE Here is a link to a project showing an example of how to do this in pure GDI. https://drive.google.com/file/d/0B42fIyGTLNv3WlJwNGVRN2txTGs/edit?usp=sharing

Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
  • Thanks. Could you please explain better how can i sort my list by the view index of my controls, and remain it sorted top down? – Jorge Oliveira May 13 '14 at 14:31
  • The class you use to represent your pieces should have a zIndex property, or something else that indicates it's position within the stack. Just add them to a new list and sort the list by this property. Whenever you add, remove, or move a piece within this list you should resort it. – Bradley Uffner May 13 '14 at 15:21
  • Could you please check my UPDATE 2? I am following what you have suggested, but something is going wrong... I think i'm pretty close to the solution... – Jorge Oliveira May 13 '14 at 16:29
  • I'm not sure exactly what's happening there, but it may be related to the way that mouse gets captured on the individual controls. Trying to use actual controls for the puzzle pieces may end up being very complex because of this. You may have to forward the MouseMove and MouseUP events as well, since they will be "captured" by the control that initially raised MouseDown. You might have better luck using pure GDI on a single large control to draw your images and handle all the mouse events. That way you don't have to worry about controls fighting over who owns the mouse. – Bradley Uffner May 13 '14 at 16:37
  • I added an example project for you to look at on how to do this with pure GDI if you would like to go that route. – Bradley Uffner May 13 '14 at 17:27
  • Guys, check my solution... am i doing the things in a correct way? Or am i going to have problems? – Jorge Oliveira May 14 '14 at 17:19
  • You should probably be using a loop to go through each of your puzzle pieces instead of manually checking each one by index. The current way isn't really scalable if you have a lot of pieces. – Bradley Uffner May 14 '14 at 17:27
0

Pieces can be represent as:

public class Piece
{
    public Point Location {get; set;}
    public int Z {get; set;}
    public int ID {get; set;} // to be bound to control or a control itself?
    public Image Image {get; set;} // texture?
    public DockStyle PlusArea {get; set;}
    public DockStyle MinusArea {get; set;}  // can be None
    ...

    public bool HitTest(Point point)
    {
        // assuming all of same size
        if((new Rectangle(Location, new Size(...)).Contains(point))
        {
            switch(MinusArea)
            {
                case Top:
                    if((new Rectangle(...)).Contains(point))
                        return false;
                ...
            }
        }
        switch(MinusArea)
        {
            case Top:
                if((new Rectangle(...)).Contains(point))
                    return true;
            ...
        }
        return false;
    }

Then puzzle is

public class Puzzle
{
    public List<Piece> Pieces {get; set;}

    public void Draw(Graphics graphics)
    {
        // draw all pictures with respect to z order
    }

    public Piece HitTest(Point point)
    {
        ... // hittest all pieces, return highest z-order or null
    }
}

It is not a complete solution, but should give you idea.

Basically:

  • In mouse event you call Figure.HitTest() to get figure to start moving (that's what you need).
  • You draw everything into owner drawn control by calling Figure.Draw().
  • Obviously, drag-n-drop operations are calling Invalidate().
  • You may have special flag to indicate figure being dragged and draw it differently (with shadows, a bit offsetted to simulate it's pulled over other pieces, etc).
  • Each figure is represented as rectangle and PlusArea or MinusArea (don't know how to call them better, it's this extra or missing area of piece connectors), this is simplification, you can improve it.
Sinatr
  • 20,892
  • 15
  • 90
  • 319
0

SOLVED :)

i have figured it out... Here's the code to anybody how needs (I have made it right now, so the code is not clean yet):

Piece Class:

class Peça : DrawingArea
{
    private Point _Offset = Point.Empty;
    public Boolean movable = false;

    public Image imagem
    {
        get;
        set;
    }

    protected override void OnDraw()
    {
        Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
        this.graphics.DrawImage(imagem, location);
    }

    public Boolean Down(Point e, bool propaga = true)
    {
        Bitmap b = new Bitmap(imagem);
        Color? color = null;
        Boolean flag = false;
        try
        {
            color = b.GetPixel(e.X, e.Y);
            if (color.Value.A != 0 && color != null)
            {
               flag = true;
            }
            else
            {
                flag = false;
            }
            return flag;
        }
        catch
        {
            return flag;
        }
    }
}

Form1:

public partial class Form1 : Form
{
    private List<Peça> peças;
    private Point _Offset = Point.Empty;
    private Peça peça1, peça2, peça3, peça4;
    private bool canMove;
    private Peça atual;
    private bool other=false;
    public Form1()
    {
        FormBorderStyle = FormBorderStyle.None;
        WindowState = FormWindowState.Maximized;
        InitializeComponent();

        atual = new Peça();

        peça1 = new Peça();
        peça2 = new Peça();
        peça3 = new Peça();
        peça4 = new Peça();
        peça1.imagem = Properties.Resources._4p1_1;
        peça2.imagem = Properties.Resources._4p1_2;
        peça3.imagem = Properties.Resources._4p1_3;
        peça4.imagem = Properties.Resources._4p1_4;

        peças = new List<Peça>();

        peça1.Name = "peça1";
        peça2.Name = "peça2";
        peça3.Name = "peça3";
        peça4.Name = "peça4";

        this.Controls.Add(peça1);
        this.Controls.Add(peça2);
        this.Controls.Add(peça3);
        this.Controls.Add(peça4);

        criaListaPecas();

        foreach (Peça p in peças)
        {
            p.Size = new Size(p.imagem.Width, p.imagem.Height);
        }

        SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);

        associaEventosPecas();
        canMove = false;
    }

    private void associaEventosPecas()
    {
        foreach (Peça p in peças)
        {
            p.MouseMove += Form1_MouseMove;
            p.MouseDown += Form1_MouseDown;
            p.MouseUp += Form1_MouseUp;
        }
    }

    private void criaListaPecas()
    {
        peças.Clear();
        foreach (Control p in this.Controls)
        {
            if (p.GetType() == typeof(Peça))
                peças.Add((Peça)p);
        }
        Console.WriteLine(peças[0].Name);
        Console.WriteLine(peças[1].Name);
        Console.WriteLine(peças[2].Name);
        Console.WriteLine(peças[3].Name);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams handleParam = base.CreateParams;
            handleParam.ExStyle |= 0x02000000;
            return handleParam;
        }
    }

    private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        if (sender.GetType().Equals(typeof(Peça)))
        {
            label1.Text = new Point(e.Location.X + (sender as Peça).Location.X, e.Location.Y + (sender as Peça).Location.Y).ToString();
        }
        else
        label1.Text = e.Location.ToString();
        gereMovimento(sender, e);
    }

    private void gereMovimento(object sender, MouseEventArgs e)
    {
        if (canMove)
        {
            if (other)
            {
                Point p = atual.PointToClient(new Point(e.X + (sender as Peça).Location.X, e.Y + (sender as Peça).Location.Y));

                Point newlocation = atual.Location;
                newlocation.X += p.X - _Offset.X;
                newlocation.Y += p.Y - _Offset.Y;
                atual.Location = newlocation;
            }
            else
            {
                Point newlocation = atual.Location;
                newlocation.X += e.X - _Offset.X;
                newlocation.Y += e.Y - _Offset.Y;
                atual.Location = newlocation;
            }
        }
    }

    private void Form1_MouseDown(object sender, MouseEventArgs e)
    {
        if (sender.GetType().Equals(typeof(Peça)) && e.Button == MouseButtons.Left)
        {
            atual = sender as Peça;
            atual.BringToFront();
            criaListaPecas();
            if (atual.Down(e.Location))
            {
                _Offset = new Point(e.X, e.Y);
                canMove = true;
                other = false;
            }
            else
            {
                Console.WriteLine(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)));
                Console.WriteLine(atual.Location);
                Point p = new Point(); 
                if (peças[1].ClientRectangle.Contains(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
                    && peças[1].Down(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
                {
                    p = peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
                    atual = peças[1];
                    atual.BringToFront();
                    criaListaPecas();
                    _Offset = p;
                    canMove = true;
                    other = true;
                }
                else if (peças[2].ClientRectangle.Contains(peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
                    && peças[2].Down(peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
                {
                    p = peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
                    atual = peças[2];
                    atual.BringToFront();
                    criaListaPecas();
                    _Offset = p;
                    canMove = true;
                    other = true;
                }
                else if (peças[3].ClientRectangle.Contains(peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
                    && peças[3].Down(peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
                {
                    p = peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
                    atual = peças[3];
                    atual.BringToFront();
                    criaListaPecas();
                    _Offset = p;
                    canMove = true;
                    other = true;
                }
            }
        }
    }

    private void Form1_MouseUp(object sender, MouseEventArgs e)
    {
        canMove = false;
    }
}

Apologize the repeated and confused code, but as i said, i have made it seconds ago, and did not clean the code yet ;)

Jorge Oliveira
  • 345
  • 1
  • 2
  • 13