WPF简单拼图(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/wpf-simple-puzzle-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 9 分钟阅读 - 4283 个词 阅读量 0WPF简单拼图(译文)
原文地址:https://www.codeproject.com/Articles/92858/WPF-Simple-Puzzle
原文作者:Nanung
译文由本站 robot-v1.0 翻译
前言
Developing a drag and drop technique in a simple puzzle game application.
在简单的益智游戏应用程序中开发拖放技术.
介绍(Introduction)
本文讨论了一个简单的益智游戏,该游戏由9个部分组成,并通过Windows Presentation Foundation实现.本文表明,在Windows Presentation Foundation中实现拖放并非难事.(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)
我非常感谢Rudi Grobler先生写的文章"(I am very grateful to Mr. Rudi Grobler who wrote the article “) 使用附加属性拖放列表框(ListBox Drag & Drop Using Attached Properties) “.本应用程序中使用的代码基于其文章中的源代码.我也非常感谢Seshi先生撰写的文章”(”. 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拼图-WPF(8 Puzzle - WPF) “的应用程序用户界面启发了该应用程序的用户界面.(” whose application UI inspired this application’s UI.)
使用代码(Using the Code)
此应用程序中的类为:(Classes in this application are:)
-
Puzzle
属性和对象(Attributes and objects)-
puzzlePiece
:代表拼图的集合.(: a collection which represents the puzzle pieces.) -
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:)index
:作品的索引.(: the index of the piece.)PuzzleImageSource
UriString
DragFrom
:指示拼图是否从中拖动(: to indicate whether the puzzle piece drags from)ListBox
/(/)Canvas
.(.)
-
MainWindow
(UI)((UI)) 属性和对象:(Attributes and objects:)-
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.) -
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.) -
emptyItem
:代表一个空的拼图项目,指示是否(: represents an empty puzzle item that indicates if a)Canvas
不包含拼图项目.(doesn’t contain a puzzle item.) -
lbDragSource
: 一种(: A)ListBox
引用对象(object to refer to the)ListBox
这增加了阻力.(that raises the drag.) -
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:)
- 拼图被加载到列表框中.(The puzzle pieces are loaded into the listbox.)
- 玩家将拼图块从(The player drags a puzzle piece from the)
ListBox
在左侧并将其拖放到九个之一(on the left and drops it to one of the nine)Canvas
es在右边.玩家还可以从(es 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).) - 对于每个已编辑的难题,都会引发一个事件并验证难题.如果正确安排了拼图,则玩家将获胜.(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) Canvas
es在右边.通过将拼图块拖放到另一个拼图块中,也可以与另一个拼图交换(es 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
.(.)
初始化(Initialization)
首先,拼图块已加载.此应用程序可以使用多种拼图.一个论点传递给(Firstly, the puzzle piece is loaded. This application can use more than one kind of puzzle. An argument passed to the) Initialize
方法用于确定使用什么拼图.它可以随机生成,但是在此应用程序中,只有一个内置拼图,即兔子拼图.因此,将值1作为方法调用参数传递.最初,初始化是排序的.因此,我们必须在拼图初始化后重新整理.这是通过.NET建立的随机数完成的.(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.)
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
?这是Rudi Grobler先生在他的文章中的方法:使用附加属性拖放列表框(请参见(? 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(http://www.codeproject.com/KB/WPF/WPFDragDrop.aspx) ).通过这样修改,我们可以猜测方法内部的代码如何工作:(). We can guess how the code inside the method works by modifying it this way:)
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:)
- 如果(If the)
Canvas
是空的,拼图块从(is empty and the puzzle piece is dragged from the)ListBox
,拼图将被丢弃.(, the puzzle piece will be dropped.) - 如果(If the)
Canvas
是空的,拼图块从另一个拖动(is empty and the puzzle piece is dragged from the other)Canvas
,拼图项目将被移到那里.(, the puzzle item will be moved there.) - 如果(If the)
Canvas
不为空且拼图块从(is not empty and the puzzle piece is dragged from the)ListBox
,拼图项目不会被删除.(, the puzzle item won’t be dropped.) - 如果(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
属性.对于条件1和2,过程很简单;首先,我们检查(property. For conditions 1 and 2, the process is simple; first, we check if the)Children
的属性(property of the)Canvas
为0(表示它没有子元素).然后,我们定义一个图像控件,其宽度,高度和来源是图像来源的宽度和高度,从拼图数据中获得的图像来源是通过拖动操作传输的.之后,将删除旧拼图块并更新放置.(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.)
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);
}
对于条件3,我们只需要从方法中返回即可.对于条件4,首先,我们必须同时获取两个索引,即源索引和目标索引.这是因为我们必须交换两个拼图块,(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
.(.)
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
方法,并使用Microsoft Paint手动切片兔子图像(相信我,这很困难:D).(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.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# .NET WPF Dev game 新闻 翻译