Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

WPF Simple Puzzle

5.00/5 (5 votes)
9 Jul 2010CPOL6 min read 44.7K   3.4K  
Developing a drag and drop technique in a simple puzzle game application.

Introduction

This article discusses a simple puzzle game which consists of 9 pieces, implemented with Windows Presentation Foundation. This article shows that drag and drop is not a difficult thing to implement in Windows Presentation Foundation.

Background

I am very grateful to Mr. Rudi Grobler who wrote the article "ListBox Drag & Drop Using Attached Properties". The code used in this application is based on the source code from his article. I am also very grateful to Mr. Seshi who write the article "8 Puzzle - WPF" whose application UI inspired this application's UI.

Using the Code

Classes in this application are:

  • Puzzle
  • Attributes and objects

    1. puzzlePiece: a collection which represents the puzzle pieces.
    2. name

    Methods:

    • Puzzle: constructor.
    • OnEdit: method to raise the event.
    • Initialize: method to load all the puzzle pieces and shuffle them.
    • Validate: validate the puzzle.

    Events:

    • Edited: event raised for each when a puzzle's arrangement is edited.
  • PuzzlePiece
  • Attributes and objects:

    1. index: the index of the piece.
    2. PuzzleImageSource
    3. UriString
    4. DragFrom: to indicate whether the puzzle piece drags from ListBox/Canvas.
  • MainWindow (UI)
  • Attributes and objects:

    1. puzzle: represents the puzzle used in this application. The puzzle itself consists of a puzzlePiece, which is of type ObservableCollection and represents the puzzle pieces that must be arranged; name, which is the puzzle name, and an event Edited that is raised whenever the puzzle is edited.
    2. itemPlacement: represents the placement of the puzzle pieces. It is used to validate the puzzle. It is a collection of puzzle pieces whose index represent the Canvas' index, and it contains the puzzle piece dropped in the Canvas itself.
    3. emptyItem: represents an empty puzzle item that indicates if a Canvas doesn't contain a puzzle item.
    4. lbDragSource: A ListBox object to refer to the ListBox that raises the drag.
    5. cvDragSource: A Canvas object to refer to the Canvas that raises the drag.

    Method:

    • MainWindow(): constructor.
    • puzzleItemList_PreviewMouseLeftButtonDown: handles a drag attempt to ListBox.
    • PzItmCvs_MouseLeftButtonDown: handles a drag attempt to Canvas.
    • PuzzleItemDrop: handles the drop to Canvas.
    • puzzle_Edited: handles the edited event of the puzzle.
    • GetDataFromCanvas: gets data which will be transferred via drag drop if the drag is from a Canvas.
    • GetObjectDataFromPoint: gets data which will be transferred via drag drop if the drag is from a ListBox.
    • instruction_Click

Application Scenario

The application's steps are:

  1. The puzzle pieces are loaded into the listbox.
  2. The player drags a puzzle piece from the ListBox on the left and drops it to one of the nine Canvases in the right. The player may also drag a puzzle piece from the Canvas to the other Canvas. If the destination Canvas is empty, the puzzle piece will be moved. If it is not empty, the puzzle piece will be exchanged (see the screenshot).
  3. For each puzzle edited, an event is raised and the puzzle will be validated. If the puzzle is correctly arranged, the player wins.

Screenshot

This application uses a very simple user interface, as shown in the figure below. In the left side, there is a ListBox where the puzzle pieces are located. The puzzle piece will be dragged to one of the Canvases on the right side. A puzzle piece may be exchanged with another piece too by dragging and dropping it to a puzzle piece in another Canvas.

wpfsimplepuzzle.png

Initialization

Firstly, the puzzle piece is loaded. This application can use more than one kind of puzzle. An argument passed to the Initialize method is used to determine what puzzle is used. It can be generated randomly, but in this application, there is only one built-in puzzle, the rabbit puzzle. So the value 1 is passed as the method call argument. Originally, the initialization is sorted. So we must shuffle the puzzle after it has been initialized. This is done with the help of a random number built by .NET.

C#
public void Initialize(int chosen)
{
    string directorySource = "";

    if (chosen == 1)
    {
        this.name = "Rabbit Puzzle";

        directorySource = "RabbitPuzzle";
    }

    for(int i=0; i<9; i++)
    {
        this.puzzlePiece.Add(new PuzzlePiece());

        this.puzzlePiece[i].index = i;

        this.puzzlePiece[i].UriString = 
           "Puzzle/" + directorySource + "/" + (i + 1).ToString() + ".png";

        this.puzzlePiece[i].PuzzleImageSource = 
             new BitmapImage(new Uri(this.puzzlePiece[i].UriString, UriKind.Relative));
    }

    //shuffle
    Random rand = new Random();

    for (int i = 0; i < 9; i++)
    {
        int random = rand.Next(0, 8);

        PuzzlePiece buffer;

        buffer = this.puzzlePiece[i];

        this.puzzlePiece[i] = this.puzzlePiece[random];

        this.puzzlePiece[random] = buffer;
    }
}

The collection object itemPlacement in the MainWindow class is used to map the Canvas and the puzzle piece contained. The default value will be emptyItem, which is an object with a certain value defined in the constructor. Then the ListBox's item source is set with the puzzle piece and the edited handler defined.

Dragging

There are two elements that can be dragged in this application: ListBox and the Canvas. Both of them use the PreviewMouseLeftButtonDown.

In the PreviewMouseLeftButtonDown for the listbox, the data transferred through drag and drop operation is from the method GetObjectDataFromPoint. This method uses the drag source and the mouse coordinate relative from the drag source as arguments. So, what's inside the method GetObjectDataFromPoint? That's the method from Mr. Rudi Grobler from his article: ListBox Drag & Drop Using Attached Properties (see http://www.codeproject.com/KB/WPF/WPFDragDrop.aspx). We can guess how the code inside the method works by modifying it this way:

C#
private object GetObjectDataFromPoint(ListBox dragSource, Point point)
{
    UIElement element = dragSource.InputHitTest(point) as UIElement;

    //MessageBox.Show("Drag Source Element : " + element.ToString());

    if (element != null)
    {
        object data = DependencyProperty.UnsetValue;

        while (data == DependencyProperty.UnsetValue)
        {
            data = dragSource.ItemContainerGenerator.ItemFromContainer(element);

            if (data == DependencyProperty.UnsetValue)
            {
                element = VisualTreeHelper.GetParent(element) as UIElement;

                //MessageBox.Show("Element passed through : " + element.ToString());
            }

            if (element == dragSource)
            {
                return null;

                //MessageBox.Show("element == dragSource");
            }
        }

        if (data != DependencyProperty.UnsetValue)
        {
            //MessageBox.Show("Data : " + data.ToString());

            return data;
        }
    }
    return null;
}

Just uncomment the statement MessageBox.Show(...) and run the application. From the image element which is dragged, iteratively seek its parent with the while statement, until it reaches the ListBoxItem. From the ListBoxItem, we get the data from the method ItemFromContainer. The data is an object of type PuzzlePiece.

The PreviewMouseLeftButtonDown for the Canvas is identical, with a little modification to get the data from the Canvas and doing a drag and drop operation. The difference is, in the Canvas, we get the data directly from the itemPlacement because in the Canvas control, there is only the image, not the whole of the PuzzlePiece object.

Dropping

There are four possible conditions in dropping:

  1. If the Canvas is empty and the puzzle piece is dragged from the ListBox, the puzzle piece will be dropped.
  2. If the Canvas is empty and the puzzle piece is dragged from the other Canvas, the puzzle item will be moved there.
  3. If the Canvas is not empty and the puzzle piece is dragged from the ListBox, the puzzle item won't be dropped.
  4. If the Canvas is not empty (there is another puzzle piece) and the puzzle piece is dragged from the other Canvas, those two puzzle pieces will be exchanged.

When a puzzle piece is dropped, there are several things to do: drop it in the appropriate Canvas, update the puzzle item placement, remove that item from the source (ListBox / Canvas) if it is not switched, check if the puzzle is valid, and delete the value of the DragFrom property. For conditions 1 and 2, the process is simple; first, we check if the Children property of the Canvas is 0 (it means it has no child element). Then, we define an image control whose width, height, and source is the image source's width and height, and the image source we got from the puzzle piece data is transferred from the drag operation. After that, the old puzzle piece is deleted and the placement is updated.

C#
Image imageControl = new Image()
{
    Width = destination.Width,
    Height = destination.Height,
    Source = itemTransferred.PuzzleImageSource,
    Stretch = Stretch.UniformToFill
};

//For condition 1 a and b, canvas is empty
if (destination.Children.Count == 0)
{
    //put the puzzle piece to the canvas
    destination.Children.Add(imageControl);

    //Step 2
    //Update PuzzleItemPlacement
    //get the placement index to be updated
    int indexToUpdate = int.Parse(destination.Tag.ToString());

    //update now
    //this statement is for condition 1 a (item from listbox)
    if (itemTransferred.DragFrom == typeof(ListBox))
    {
        //update
        this.itemPlacement[indexToUpdate] = itemTransferred;

        //Step 3
        //delete the item dragged from listbox
        //NOTE : DELETING this way makes puzzle
        // pieces defined in puzzle.puzzleItem DELETED
        ((IList)lbDragSource.ItemsSource).Remove(itemTransferred);
    }

For condition 3, we only have to return from the method. For condition 4, first, we must get both of the indices, the source and the destination index. This is because we must exchange both of the puzzle pieces, the piece from the Canvas which is dragged and the Canvas where the puzzle piece is dropped. To change the Image control which is in the Canvas, the associated Canvas is accessed with the method GetAssociatedCanvasByIndex. This method takes an integer argument which is the index of the Canvas to be accessed, and will return the associated Canvas.

C#
else if (destination.Children.Count > 0)
{
    //condition 1c, from listbox
    if (itemTransferred.DragFrom == typeof(ListBox))
    {
        //do nothing
        return;
    }

    //condition 1d
    else if (itemTransferred.DragFrom == typeof(Canvas))
    {
        //Step 1 and 2, switch them
        //get the previous and destination index
        int sourceIndex = itemPlacement.IndexOf(itemTransferred);

        int destinationIndex = int.Parse(destination.Tag.ToString());

        Object buffer = null;

        //switch the image
        Image image0 = new Image() { Width = destination.Width, 
              Height = destination.Height, Stretch = Stretch.Fill };
        image0.Source = itemPlacement[sourceIndex].PuzzleImageSource;
        Image image1 = new Image() { Width = destination.Width, 
              Height = destination.Height, Stretch = Stretch.Fill };
        image1.Source = itemPlacement[destinationIndex].PuzzleImageSource;
        GetAssociatedCanvasByIndex(sourceIndex).Children.Clear();
        GetAssociatedCanvasByIndex(destinationIndex).Children.Clear();
        GetAssociatedCanvasByIndex(sourceIndex).Children.Add(image1);
        GetAssociatedCanvasByIndex(destinationIndex).Children.Add(image0);
        image0 = null;
        image1 = null;

        //switch the placement
        buffer = itemPlacement[sourceIndex];

        itemPlacement[sourceIndex] = itemPlacement[destinationIndex];
        itemPlacement[destinationIndex] = buffer as PuzzlePiece;
        buffer = null;
    }
}

Conclusion

Corrections and suggestions are appreciated. I hope this article will help someone who is struggling with a drag and drop puzzle. I'm sorry for any mistakes I made in this article, and thank you very much.

Points of Interest

The difficult things in creating this application were: understanding the GetObjectDataFromPoint method, and manually slicing the rabbit image with Microsoft Paint (believe me, that's difficult :D).

History

This is the first release of the puzzle application.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)