今天要學習一個拼圖項目。
目標是傳入一張圖片,然后將它分成9份,去掉一份,鼠標點擊進行拼圖。
源文件結構很簡單
第一步、新建項目
這一步沒什么好說的,新建一個項目就跟源文件結構一樣了
第二步、頁面布局(.xaml文件)
看下源文件
控件有 DockPanel Grid Button三個然后設置了Grid有三列和三行。DockPannel暫時不知道有什么用,所以我先不忙加。然后我就報錯了
原來 xaml是用的xml格式。button外面沒有雙標簽包圍,不能識別,所以報錯。所以外面再加個標簽包裹就行了,如果加DockPanel標簽就和源文件一樣了,此處為了明白DockPane有什么用,所以還是用Grid,看等會兒會不會報錯。我現在的代碼是
第二步、編寫點擊按鈕選圖片的功能
這個帖子上周就開始寫了,但是做了一半又去研究c++了。c++研究了一段時間,忽然明白我為什么要編程了。我編程不是對計算機有興趣,不是為了0和1。我學計算機和程序只是為了做東西。所以又回過頭來繼續寫這個系列,之后的內容我不會再抓細節,有些東西,能看懂就行了。記不住也沒關系,要用的時候再查就是了。將項目做出來之后,我還要將它做成我喜歡的樣子,而不是做成跟源代碼一樣。
點擊按鈕要做兩件事
1、彈出文件選擇對話框,選擇圖片。
2、選擇圖片后生成拼圖
下面是選擇圖片的代碼

OpenFileDialog ofd = new OpenFileDialog(); // 需要引用Microsoft.Win32. ofd.Filter = "Image Files(*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG"; //支持的圖片格式 ofd.Multiselect = false; //不允許多選 if (ofd.ShowDialog()!=true) //ofd.ShowDialog()可能有三個值 true flase null { return; } try { BitmapImage image = new BitmapImage(new Uri(ofd.FileName, UriKind.RelativeOrAbsolute)); Image img = new Image { Source = image }; //這里寫創建拼圖的代碼 } catch { MessageBox.Show("Couldnt load the image file " + ofd.FileName); }
生成拼圖 第一步是把圖片分成9塊,並填充相應區域的圖像
這個有點復雜,源碼用了很多方法,我習慣拆出來作為一個類單獨寫。
/// <summary> /// 拼圖生成類 /// 傳入一張圖片 生成一個9*9的圖片集合 /// </summary> public class PuzzleForImage { BitmapImage _image; //原圖片 public List<Rectangle> initialUnallocatedParts = new List<Rectangle>();//要返回的拼圖集合 /// <summary> /// 新建對象時傳入原圖片 /// </summary> /// <param name="image"></param> public PuzzleForImage(BitmapImage image) { _image = image;
//創建拼圖 } }
第一步:寫個子方法,根據起點和圖片寬高繪制矩形。然后調用9次,得到整個拼圖集合
第二步:將9張中的8張拼圖隨機排列,這里選前八張
第三步:再添加塊空白的拼圖
第四步:添加鼠標點擊移動事件
到這一步,源碼部分就結束了,自己添加了個判斷成功的代碼 在方塊中的點擊事件中執行。
下面是我的代碼。

/// <summary> /// MainWindow.xaml 的交互邏輯 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } /// <summary> /// 挑選圖片生成拼圖 /// </summary> private void BtnPickImg_Click(object sender, RoutedEventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "Image Files(*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG";//支持的圖片格式 ofd.Multiselect = false;//不允許多選 if (ofd.ShowDialog() != true) { return; } try { BitmapImage image = new BitmapImage(new Uri(ofd.FileName, UriKind.RelativeOrAbsolute)); //Image img = new Image { Source = image }; PuzzleForImage puzzle = new PuzzleForImage(image);//創建拼圖 puzzle.SetGrid(GridImg); } catch { MessageBox.Show("不支持該文件: " + ofd.FileName); } } }

/// <summary> /// 拼圖生成類 /// 傳入一張圖片 生成一個9*9的圖片集合 /// </summary> public class PuzzleForImage { BitmapImage _image; //原圖片 List<Rectangle> initialUnallocatedParts = new List<Rectangle>();//要返回拼圖集合 public List<Rectangle> allocatedParts = new List<Rectangle>(); //被打亂后的圖片 int[] map = new int[9]; //游戲地圖 判斷是否成功 /// <summary> /// 新建對象時傳入原圖片 /// </summary> /// <param name="image"></param> public PuzzleForImage(BitmapImage image) { _image = image; CreatePuzzleForImage(); } /// <summary> /// 將拼圖放到UI中 /// </summary> /// <param name="GridImg"></param> public void SetGrid(Grid GridImg) { GridImg.Children.Clear(); GridImg.Height = _image.Height / _image.Width * GridImg.Width; int index = 0; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { allocatedParts[index].SetValue( Grid.RowProperty, i); allocatedParts[index].SetValue(Grid.ColumnProperty, j); GridImg.Children.Add(allocatedParts[index]); index++; } } } /// <summary> /// 創建拼圖 /// </summary> private void CreatePuzzleForImage() { #region 拼圖容器初始化 initialUnallocatedParts.Clear(); allocatedParts.Clear(); #endregion #region 創建前8個拼圖 double width = 1.0 / 3;//每個拼圖的寬度 double height = 1.0 / 3;//每個拼圖的高度 //row0 CreateImagePart( 0, 0, width, height); CreateImagePart( width, 0, width, height); CreateImagePart( 2 * width, 0, width, height); //row1 CreateImagePart( 0, height, width, height); CreateImagePart( width, height, width, height); CreateImagePart( 2 * width, height, width, height); //row2 CreateImagePart( 0, 2 * height, width, height); CreateImagePart( width, 2 * height, width, height); //CreateImagePart( 2 * width, 2 * height, width, height); #endregion //隨機排列 RandomizeTiles(); //創建白色方塊 CreateBlankRect(); } /// <summary> /// 繪制一個矩形 /// 創建一個圖像畫刷使用X / Y /寬度/高度參數創建時,只顯示圖像的一部分。這是ImageBrush用來填充矩形,添加到未分配的矩形的內部表 /// </summary> /// <param name="x">起點x</param> /// <param name="y">起點y</param> /// <param name="width">寬</param> /// <param name="height">高</param> private void CreateImagePart(double x, double y, double width, double height) { #region 定義筆刷 ImageBrush ib = new ImageBrush(); //ib.Stretch = Stretch.UniformToFill; //裁剪已適應屏幕 這個不能要,會造成圖片不拉伸對不上 ib.ImageSource = _image; ib.Viewbox = new Rect(x, y, width, height); ib.ViewboxUnits = BrushMappingMode.RelativeToBoundingBox; //按百分比設置寬高 ib.TileMode = TileMode.None; //按百分比應該不會出現 image小於要切的值的情況 #endregion #region 定義矩形 Rectangle rectPart = new Rectangle(); rectPart.Fill = ib; rectPart.Margin = new Thickness(0); rectPart.HorizontalAlignment = HorizontalAlignment.Stretch; rectPart.VerticalAlignment = VerticalAlignment.Stretch; rectPart.MouseDown += new MouseButtonEventHandler(rectPart_MouseDown);//添加矩形點擊事件 #endregion initialUnallocatedParts.Add(rectPart); //將矩形添加到拼圖集合 } /// <summary> /// 將 圖片打亂 /// </summary> private void RandomizeTiles() { Random rand = new Random(); for (int i = 0; i < 8; i++) { int index = 0; //if (initialUnallocatedParts.Count > 1) //{ // index = (int)(rand.NextDouble() * initialUnallocatedParts.Count); //} index = (int)(rand.NextDouble() * initialUnallocatedParts.Count); while(initialUnallocatedParts[index] == null) { index = (int)(rand.NextDouble() * initialUnallocatedParts.Count); } allocatedParts.Add(initialUnallocatedParts[index]); //initialUnallocatedParts.RemoveAt(index); // 移除圖片 initialUnallocatedParts[index] = null; map[i] = index; // 添加地圖 } } /// <summary> /// 再創建一個空白矩形 /// </summary> private void CreateBlankRect() { Rectangle rectPart = new Rectangle(); rectPart.Fill = new SolidColorBrush(Colors.White); rectPart.Margin = new Thickness(0); rectPart.HorizontalAlignment = HorizontalAlignment.Stretch; rectPart.VerticalAlignment = VerticalAlignment.Stretch; allocatedParts.Add(rectPart); map[8] = 8; } /// <summary> /// 方塊點擊事件 /// </summary> private void rectPart_MouseDown(object sender, MouseButtonEventArgs e) { //get the source Rectangle, and the blank Rectangle //NOTE : Blank Rectangle never moves, its always the last Rectangle //in the allocatedParts List, but it gets re-allocated to //different Gri Row/Column Rectangle rectCurrent = sender as Rectangle; //當前方塊 Rectangle rectBlank = allocatedParts[allocatedParts.Count - 1]; //缺省方塊 //得到白塊和缺省方塊的位置 int currentTileRow = (int)rectCurrent.GetValue(Grid.RowProperty); int currentTileCol = (int)rectCurrent.GetValue(Grid.ColumnProperty); int currentBlankRow = (int)rectBlank.GetValue(Grid.RowProperty); int currentBlankCol = (int)rectBlank.GetValue(Grid.ColumnProperty); //白塊能移動的四個位置 List<PossiblePositions> posibilities = new List<PossiblePositions>(); posibilities.Add(new PossiblePositions { Row = currentBlankRow - 1, Col = currentBlankCol }); posibilities.Add(new PossiblePositions { Row = currentBlankRow + 1, Col = currentBlankCol }); posibilities.Add(new PossiblePositions { Row = currentBlankRow, Col = currentBlankCol - 1 }); posibilities.Add(new PossiblePositions { Row = currentBlankRow, Col = currentBlankCol + 1 }); //檢查該方塊是否能點擊(白塊能移動到當前方塊的位置) bool validMove = false; foreach (PossiblePositions position in posibilities) if (currentTileRow == position.Row && currentTileCol == position.Col) validMove = true; //only allow valid move if (validMove) { //交換位置 rectCurrent.SetValue( Grid.RowProperty, currentBlankRow); rectCurrent.SetValue(Grid.ColumnProperty, currentBlankCol); rectBlank .SetValue( Grid.RowProperty, currentTileRow); rectBlank .SetValue(Grid.ColumnProperty, currentTileCol); //更新地圖 int indexCur = currentTileRow * 3 + currentTileCol; int indexBlank = currentBlankRow * 3 + currentBlankCol; int temp = map[indexCur]; map[indexCur] = map[indexBlank]; map[indexBlank] = temp; //判斷是否成功 if (isSuccess()) { MessageBox.Show("成功了,好棒啊!"); } } } /// <summary> /// 驗證是否游戲成功 /// </summary> /// <returns></returns> private bool isSuccess() { for (int i = 0; i < 9; i++) { if(map[i]!=i) { return false; } } return true; } } /// <summary> /// Simply struct to store Row/Column data /// </summary> struct PossiblePositions { public int Row { get; set; } public int Col { get; set; } }
運行效果:
還存在的問題:
1、現在圖片會被拉伸,暫時沒想到好的辦法。
2、會隨機一些拼不出來的拼圖
------------------------------分割線2016-8-3 16:47---------------------------------
圖片拉伸問題已經解決
@曙光閃現
[quote]圖片拉伸應該只能通過限制窗體的縮放按圖片的比例縮放了。之前搞過9path原理就是類似這個九宮格[/quote]
下面是代碼

/*------------------------------------------------------------------ *如果選擇的圖片寬高比例比屏幕的寬高比例大,則窗體和圖片區域寬度采用600,高度等比縮放 * 反之,圖片區域高度采用600,寬度等比算出,但是窗體寬度就不等比縮放了(照顧button) ------------------------------------------------------------------*/ double imgWidthHeightRatio = image.Width / image.Height;//圖片 寬高比 double windowWidthHeightRatio = 600/ 600;//默認屏幕寬高比 this.Width = 600; if (imgWidthHeightRatio > windowWidthHeightRatio) { GridImg.Width = 600; GridImg.Height = GridImg.Width / imgWidthHeightRatio; this.Height = GridImg.Height + 50; } else { this.Height = 650;//button占了50 GridImg.Height = 600; GridImg.Width = GridImg.Width * imgWidthHeightRatio; }
更新了MainWindow.xaml.cs文件 和 MainWindow.xaml文件
下面是最新的代碼

using Microsoft.Win32; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace Puzzle { /// <summary> /// MainWindow.xaml 的交互邏輯 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } /// <summary> /// 挑選圖片生成拼圖 /// </summary> private void BtnPickImg_Click(object sender, RoutedEventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "Image Files(*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG";//支持的圖片格式 ofd.Multiselect = false;//不允許多選 if (ofd.ShowDialog() != true) { return; } try { BitmapImage image = new BitmapImage(new Uri(ofd.FileName, UriKind.RelativeOrAbsolute)); #region 設置寬高 /*------------------------------------------------------------------ *如果選擇的圖片寬高比例比屏幕的寬高比例大,則窗體和圖片區域寬度采用600,高度等比縮放 * 反之,圖片區域高度采用600,寬度等比算出,但是窗體寬度就不等比縮放了(照顧button) ------------------------------------------------------------------*/ double imgWidthHeightRatio = image.Width / image.Height;//圖片 寬高比 double windowWidthHeightRatio = 600/ 600;//默認屏幕寬高比 this.Width = 600; if (imgWidthHeightRatio > windowWidthHeightRatio) { GridImg.Width = 600; GridImg.Height = GridImg.Width / imgWidthHeightRatio; this.Height = GridImg.Height + 50; } else { this.Height = 650;//button占了50 GridImg.Height = 600; GridImg.Width = GridImg.Width * imgWidthHeightRatio; } #endregion //Image img = new Image { Source = image }; PuzzleForImage puzzle = new PuzzleForImage(image);//創建拼圖 puzzle.SetGrid(GridImg); } catch { MessageBox.Show("不支持該文件: " + ofd.FileName); } } } }

<Window x:Class="Puzzle.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="650" Width="600"> <Grid> <Button x:Name="BtnPickImg" Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="560" RenderTransformOrigin="-0.459,-0.974" Click="BtnPickImg_Click"/> <Grid x:Name="GridImg" Margin="0,50,0,0" Background="#eee"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> </Grid> </Grid> </Window>
PuzzleForImage.cs沒變還是上面那個
--20180419 發現一個功能多的:http://download.microsoft.com/download/B/2/5/B25C4C6A-97FE-4014-9D4B-B39607BA9A12/wpf_samples/15Puzzle.exe