Within WPF there it is possible to create a Brush based on an Image, this is known as an ImageBrush. You are then able to use this ImageBrush anywhere that you would be able to use another Brush. One of the less documented features of the ImageBrush is its ViewBox property. Using the ViewBox property you are able to grab certain portions of the original image. You may have seen this in use in the Blendables zoom box, where a small box shows a zoomed section of the full image.
I was thinking about this the other day, and was a little miffed with the world, so decided to write a small puzzle in WPF. I used the ImageBrush and its ViewBox property to create a small 9 square slider puzzle just like the ones you got for MXAS in your stocking (cracker if you were very lucky).
You know like this.
And here is all the code that makes it work
XAML
1: <Window x:Class="SlideGame.Window1"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: Title="Window1" Height="300" Width="300">
5: <DockPanel LastChildFill="True">
6:
7:
8: <Button x:Name="btnPickImage" Content="Pick Image"
9: Click="btnPickImage_Click" DockPanel.Dock="Top"/>
10: <!-- Puzzle -->
11: <Grid x:Name="gridMain">
12: <Grid.ColumnDefinitions>
13: <ColumnDefinition Width="*"/>
14: <ColumnDefinition Width="*"/>
15: <ColumnDefinition Width="*"/>
16: </Grid.ColumnDefinitions>
17:
18: <Grid.RowDefinitions>
19: <RowDefinition Height="*"/>
20: <RowDefinition Height="*"/>
21: <RowDefinition Height="*"/>
22: </Grid.RowDefinitions>
23:
24: </Grid>
25:
26:
27: </DockPanel>
28: </Window>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
C# Code behind
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Windows;
6: using System.Windows.Controls;
7: using System.Windows.Data;
8: using System.Windows.Documents;
9: using System.Windows.Input;
10: using System.Windows.Media;
11: using System.Windows.Media.Imaging;
12: using System.Windows.Navigation;
13: using System.Windows.Shapes;
14:
15: namespace SlideGame
16: {
17: /// <summary>
18: /// A simple 9 square sliding puzzle using an
19: /// image that the user picks
20: /// </summary>
21: public partial class Window1 : Window
22: {
23: #region Data
24: BitmapImage image;
25: Image img;
26: List<Rectangle> initialUnallocatedParts = new List<Rectangle>();
27: List<Rectangle> allocatedParts = new List<Rectangle>();
28: #endregion
29:
30: #region Ctor
31: /// <summary>
32: /// Creates a new Window
33: /// </summary>
34: public Window1()
35: {
36: InitializeComponent();
37: }
38: #endregion
39:
40: #region Private Methods
41:
42: /// <summary>
43: /// Randomizes the tiles such that they are not
44: /// show in the order they were created
45: /// </summary>
46: private void RandomizeTiles()
47: {
48: Random rand = new Random(15);
49: int allocated = 0;
50: while (allocated != 8)
51: {
52: int index = 0;
53: if (initialUnallocatedParts.Count > 1)
54: {
55: index = (int)(rand.NextDouble() *
56: initialUnallocatedParts.Count);
57: }
58: allocatedParts.Add(initialUnallocatedParts[index]);
59: initialUnallocatedParts.RemoveAt(index);
60: allocated++;
61: }
62: }
63:
64: /// <summary>
65: /// Creates all the puzzles squares, which will either
66: /// be a portion of the original image or will be the
67: /// single blank puzzle square
68: /// </summary>
69: private void CreatePuzzleForImage()
70: {
71: initialUnallocatedParts.Clear();
72: allocatedParts.Clear();
73:
74: //row0
75: CreateImagePart(0, 0, 0.33333, 0.33333);
76: CreateImagePart(0.33333, 0, 0.33333, 0.33333);
77: CreateImagePart(0.66666, 0, 0.33333, 0.33333);
78: //row1
79: CreateImagePart(0, 0.33333, 0.33333, 0.33333);
80: CreateImagePart(0.33333, 0.33333, 0.33333, 0.33333);
81: CreateImagePart(0.66666, 0.33333, 0.33333, 0.33333);
82: //row2
83: CreateImagePart(0, 0.66666, 0.33333, 0.33333);
84: CreateImagePart(0.33333, 0.66666, 0.33333, 0.33333);
85: RandomizeTiles();
86: CreateBlankRect();
87:
88: int index = 0;
89: for (int i = 0; i < 3; i++)
90: {
91: for (int j = 0; j < 3; j++)
92: {
93: allocatedParts[index].SetValue(Grid.RowProperty, i);
94: allocatedParts[index].SetValue(Grid.ColumnProperty, j);
95: gridMain.Children.Add(allocatedParts[index]);
96: index++;
97: }
98: }
99: }
100:
101: /// <summary>
102: /// Creates the single blank Rectangle for the puzzle
103: /// </summary>
104: private void CreateBlankRect()
105: {
106: Rectangle rectPart = new Rectangle();
107: rectPart.Fill = new SolidColorBrush(Colors.White);
108: rectPart.Margin = new Thickness(0);
109: rectPart.HorizontalAlignment = HorizontalAlignment.Stretch;
110: rectPart.VerticalAlignment = VerticalAlignment.Stretch;
111: allocatedParts.Add(rectPart);
112: }
113:
114: /// <summary>
115: /// Creates a ImageBrush using x/y/width/height params
116: /// to create a Viewbox to only show a portion of original
117: /// image. This ImageBrush is then used to fill a Rectangle
118: /// which is added to the internal list of unallocated
119: /// Rectangles
120: /// </summary>
121: /// <param name="x">x position in the original image</param>
122: /// <param name="y">y position in the original image</param>
123: /// <param name="width">the width to use</param>
124: /// <param name="height">the hieght to use</param>
125: private void CreateImagePart(double x, double y, double width, double height)
126: {
127: ImageBrush ib = new ImageBrush();
128: ib.Stretch = Stretch.UniformToFill;
129: ib.ImageSource = image;
130: ib.Viewport = new Rect(0, 0, 1.0, 1.0);
131: //grab image portion
132: ib.Viewbox = new Rect(x, y, width, height);
133: ib.ViewboxUnits = BrushMappingMode.RelativeToBoundingBox;
134: ib.TileMode = TileMode.None;
135:
136: Rectangle rectPart = new Rectangle();
137: rectPart.Fill = ib;
138: rectPart.Margin = new Thickness(0);
139: rectPart.HorizontalAlignment = HorizontalAlignment.Stretch;
140: rectPart.VerticalAlignment = VerticalAlignment.Stretch;
141: rectPart.MouseDown += new MouseButtonEventHandler(rectPart_MouseDown);
142: initialUnallocatedParts.Add(rectPart);
143: }
144:
145: /// <summary>
146: /// Swaps the blank puzzle square with the square clicked if its a
147: /// valid move
148: /// </summary>
149: private void rectPart_MouseDown(object sender, MouseButtonEventArgs e)
150: {
151: //get the source Rectangle, and the blank Rectangle
152: //NOTE : Blank Rectangle never moves, its always the last Rectangle
153: //in the allocatedParts List, but it gets re-allocated to
154: //different Gri Row/Column
155: Rectangle rectCurrent = sender as Rectangle;
156: Rectangle rectBlank = allocatedParts[allocatedParts.Count - 1];
157:
158: //get current grid row/col for clicked Rectangle and Blank one
159: int currentTileRow = (int)rectCurrent.GetValue(Grid.RowProperty);
160: int currentTileCol = (int)rectCurrent.GetValue(Grid.ColumnProperty);
161: int currentBlankRow = (int)rectBlank.GetValue(Grid.RowProperty);
162: int currentBlankCol = (int)rectBlank.GetValue(Grid.ColumnProperty);
163:
164: //create possible valid move positions
165: List<PossiblePositions> posibilities = new List<PossiblePositions>();
166: posibilities.Add(new PossiblePositions
167: { Row = currentBlankRow - 1, Col = currentBlankCol });
168: posibilities.Add(new PossiblePositions
169: { Row = currentBlankRow + 1, Col = currentBlankCol });
170: posibilities.Add(new PossiblePositions
171: { Row = currentBlankRow, Col = currentBlankCol-1 });
172: posibilities.Add(new PossiblePositions
173: { Row = currentBlankRow, Col = currentBlankCol + 1 });
174:
175: //check for valid move
176: bool validMove = false;
177: foreach (PossiblePositions position in posibilities)
178: if (currentTileRow == position.Row && currentTileCol == position.Col)
179: validMove = true;
180:
181: //only allow valid move
182: if (validMove)
183: {
184: rectCurrent.SetValue(Grid.RowProperty, currentBlankRow);
185: rectCurrent.SetValue(Grid.ColumnProperty, currentBlankCol);
186:
187: rectBlank.SetValue(Grid.RowProperty, currentTileRow);
188: rectBlank.SetValue(Grid.ColumnProperty, currentTileCol);
189: }
190: else
191: return;
192: }
193:
194: /// <summary>
195: /// Allows user to pick a new image to create a puzzle for
196: /// </summary>
197: private void btnPickImage_Click(object sender, RoutedEventArgs e)
198: {
199: Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog();
200: ofd.Filter = "Image Files(*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG" +
201: "|All Files (*.*)|*.*";
202: ofd.Multiselect = false;
203: if (ofd.ShowDialog() == true)
204: {
205: try
206: {
207: image = new BitmapImage(new Uri(ofd.FileName,
208: UriKind.RelativeOrAbsolute));
209: img = new Image { Source = image };
210: CreatePuzzleForImage();
211: }
212: catch
213: {
214: MessageBox.Show("Couldnt load the image file " +
215: ofd.FileName);
216: }
217: }
218: }
219: #endregion
220: }
221:
222: #region PossiblePositions STRUCT
223: /// <summary>
224: /// Simply struct to store Row/Column data
225: /// </summary>
226: struct PossiblePositions
227: {
228: public int Row { get; set; }
229: public int Col { get; set; }
230: }
231: #endregion
232: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
I have written a more detail account of how this works in this article at codeproject so if you want to download the code you can do it from there.