I'm currently creating a board game in WPF. I'm creating my board in my PlayerViewModel so I have a nice 10x10 board. I also create pieces that the player starts with (4). Here is where I get stuck, because I'm not quite sure if it's possible to just put the player's pieces in my GamePiece.xaml the way I've done it since this is the "component" that creates the whole board for me. The circle/image is the component I want as a player's disc.
I bind this into my GameView.xaml, however the problem is that I want to have circles as my pieces in the game. Obviously here I'm just creating the whole board and so it's also creating the pieces (circles/photos) and I can't seem to manipulate this and decide how many I want to show on the board in the beginning. I have tried different ways, like put the pieces on specific coordinates and just having the color of the square it's taking up on the board change, but it doesn't look very nice.
First, you would create a ListView and configure it to place the items in rows (by using WrapPanel as ItemsPanel and setting the width and height for the ListView), and -as you mentioned- you can use BoardPiece UserControl as ItemTemplate.
Your GameView.xaml can be something similar to this
<UserControl
x:Class="GameView">
<StackPanel Orientation="Vertical">
<ListView
Width="600"
Height="500"
ItemsSource="{Binding BoardPieces}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<testingThings:BoardPiece Margin="0" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button
Width="100"
Margin="8"
Click="ButtonBase_OnClick"
Content="Fill All" />
</StackPanel>
</UserControl>
Then, Configure the ViewModel: create your items and store them in an ObservableCollection to bind them later with the ListView..
public class GameViewViewModel
{
public ObservableCollection<BoardPieceItem> BoardPieces { set; get; } =
new ObservableCollection<BoardPieceItem>();
public GameViewViewModel()
{
for (var i = 0; i < 10; i++)
for (var j = 0; j < 10; j++)
{
// random coloring at initialization, do it as you want..
BoardPieces.Add(new BoardPieceItem
{
Index = (i, j),
RectangleColor = "#00FF00", // green
EllipseColor = (i + j) % 2 == 0
? "#00FFFFFF" // transparent
: "#000000", // Black
});
}
}
}
Now, Bind the ViewModel with the View
public partial class GameView
{
public GameView()
{
InitializeComponent();
DataContext = new GameViewViewModel();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: TestDialogVm vm })
{
foreach (var item in vm.BoardPieces)
{
item.EllipseColor = "#0000FF";
}
}
}
}
In BoardPiece.xaml there is no need to use Converters
<UserControl
x:Class="SharedModule.Views.TestLab.BoardPiece"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:testLab="clr-namespace:SharedModule.Views.TestLab"
d:DataContext="{d:DesignInstance testLab:BoardPieceItem}"
MouseDown="UIElement_OnMouseDown"
mc:Ignorable="d">
<Grid>
<Rectangle
Width="45"
Height="45"
Fill="{Binding RectangleColor}"
Stroke="Black"
StrokeThickness="1.3" />
<Ellipse
Width="20"
Height="20"
Fill="{Binding EllipseColor}" />
</Grid>
</UserControl>
Finally, Configure the BoardPieceItem to make it possible to update the game board at runtime..
public class BoardPieceItem : INotifyPropertyChanged
{
public (int, int) Index { get; set; }
private string _rectangleColor;
public string RectangleColor
{
get => _rectangleColor;
set
{
_rectangleColor = value;
OnPropertyChanged();
}
}
private string _ellipseColor;
public string EllipseColor
{
get => _ellipseColor;
set
{
_ellipseColor = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now you are all set, see how UIElement_OnMouseDown will update the item's color, and ButtonBase_OnClick will update the cells of the board at all (I could go all the way MVVM, but used some events in a hurry).
This is how the game looks like at my side
Related
This question already has answers here:
How do you bind a grid's children to a list?
(1 answer)
WPF Grid as ItemsPanel for a list dynamically bound to an ItemsControl
(3 answers)
Closed last year.
For the purpose of learning MVVM and WPF (I am fairly new to both these), I am building a simple game.
The game board consists of 9 (3x3) tiles (I want to have it NxN later on, but 3x3 is good enough for the example), which need to be evenly placed next to each other in three rows.
I have defined an user control for the tiles as shown below in the View XAML.
The problem: while my data bindings work fine, I have been unable to find a layout where I could place my UserControls, as I cannot use ItemsSource and ItemTemplate with a Grid or a Canvas.
Since the game tiles might change their (PositionX and PositionY), I am looking for a layout that could support placement of user controls in X and Y coordinates, something like Grid.Column and Grid.Row (or Canvas.Left and .Top properties).
P.S. Never mind the ListBox, it is there just to make sure the data bindings worked and the tile value was displayed on the tiles.
View XAML:
<UserControl.Resources>
<DataTemplate x:Key="TileTemplate">
<Border x:Name="GamePieceItem" BorderThickness="2" BorderBrush="DimGray"
CornerRadius="5" Background="LightYellow" Height="60" Width="60"
Grid.Column="{Binding Path=PositionX, Mode=TwoWay}"
Grid.Row="{Binding Path=PositionY, Mode=TwoWay}">
<TextBlock Text="{Binding Path=TileValue, Mode=TwoWay}" FontSize="40" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"
x:Name="ValueField"
Margin="2"
/>
</Border>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding GameTiles}"
ItemTemplate="{StaticResource TileTemplate}"/>
</Grid>
</UserControl>
Model:
public class GameTile : INotifyPropertyChanged
{
private int _positionx;
private int _positiony;
private int _tilevalue;
public int PositionX
{
get { return _positionx; }
set
{
_positionx = value;
RaisePropertyChanged("PositionX");
}
}
public int PositionY
{
get { return _positiony; }
set
{
_positiony = value;
RaisePropertyChanged("PositionY");
}
}
public int TileValue
{
get
{ return _tilevalue; }
set
{
_tilevalue = value;
RaisePropertyChanged("TileValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
ViewModel:
public class GameTileViewModel
{
public ObservableCollection<GameTile> GameTiles
{
get;
set;
}
public void CreateBoard()
{
ObservableCollection<GameTile> gametiles = new ObservableCollection<GameTile>();
// the game board should be auto-generated in a loop, these are just two examples of the tiles
gametiles.Add(new GameTile { SequencePosition = 0, PositionX = 0, PositionY = 0, TileValue = 3 });
gametiles.Add(new GameTile { SequencePosition = 1, PositionX = 1, PositionY = 1, TileValue = 5 });
GameTiles = gametiles;
}
}
MainWindow XAML:
<Grid>
<views:GameBoardView x:Name="GameBoard" Loaded="GameBoard_Loaded"/>
</Grid>
MainWindow code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void GameBoard_Loaded(object sender, RoutedEventArgs e)
{
GameTileViewModel GameBoardObject = new GameTileViewModel();
GameBoardObject.CreateBoard();
GameBoard.DataContext = GameBoardObject;
}
}
I am writing a programme with C#, .NET 4.6 and WPF. I would like to have a set of CustomControls arranged in a two-dimensional grid (size dynamically specified at runtime) and be able to access each CustomControl.
I did some research, found different pieces of information about the ItemsControl, and created a solution which to some extend does what I want.
Here are the relevant parts of the code, they compile and run.
XAML for CustomControl
<UserControl x:Class="TestApp.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestApp"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Rectangle Fill="{Binding MyFill1, RelativeSource={RelativeSource FindAncestor, AncestorType=local:MyUserControl}}">
</Rectangle>
<Viewbox>
<TextBlock Text="{Binding MyText1, RelativeSource={RelativeSource FindAncestor, AncestorType=local:MyUserControl}}" >
</TextBlock>
</Viewbox>
</Grid>
</UserControl>
code-behind for CustomControl
namespace TestApp
{
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty MyText1Property =
DependencyProperty.Register("MyText1",
typeof(String), typeof(MyUserControl),
new PropertyMetadata(""));
public String MyText1
{
get { return (String)GetValue(MyText1Property); }
set { SetValue(MyText1Property, value); }
}
public static readonly DependencyProperty MyFill1Property =
DependencyProperty.Register("MyFill1",
typeof(SolidColorBrush),
typeof(MyUserControl),
new PropertyMetadata(new SolidColorBrush(Colors.Green)));
public SolidColorBrush MyFill1
{
get { return (SolidColorBrush)GetValue(MyFill1Property); }
set { SetValue(MyFill1Property, value); }
}
public MyUserControl()
{
InitializeComponent();
}
}
}
XAML for hosting MainWindow
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestApp"
mc:Ignorable="d"
Name="MyMainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl Name="MyItemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding ElementName=MyMainWindow, Path=UniformGridColumns, Mode=OneWay}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MyUserControl MyText1="{Binding Text1}" MyFill1="{Binding Fill1}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
code-behind for hosting main window
namespace TestApp
{
public partial class MainWindow : Window
{
public int UniformGridColumns //number of columns of the grid
{
get { return (int)GetValue(UniformGridColumnsProperty); }
set { SetValue(UniformGridColumnsProperty, value); }
}
public static readonly DependencyProperty UniformGridColumnsProperty =
DependencyProperty.Register("UniformGridColumns", typeof(int), typeof(MainWindow),
new FrameworkPropertyMetadata((int)0));
public MainWindow()
{
InitializeComponent();
//this.DataContext = this;
Setup(13, 5); //13 columns, 5 rows
}
public void Setup(int columns, int rows) //setup the grid
{
UniformGridColumns = columns;
SingleControl[] singleControls = new SingleControl[rows*columns];
for (int i = 0; i < rows*columns; i++)
singleControls[i] = new SingleControl()
{
Text1 = (i/ columns + 1) + ", " + (i % columns + 1),
Fill1 = new SolidColorBrush((i % 2 != 0) ? Colors.Yellow : Colors.Red)
}; //example, display position in grid and fill with two different colours
MyItemsControl.ItemsSource = singleControls.ToList<SingleControl>();
}
public MyUserControl GetSingleControl(int column, int row) //access a single control
{
//some code involving VisualTreeHelper
return null;
}
private class SingleControl //helper class for setting up the grid
{
public String Text1 { get; set; }
public Brush Fill1 { get; set; }
}
}
}
The method MainWindow.Setup(int, int) fills the ItemControl with the desired number of MyCustomControls, I can label and fill them with any colour I want.
Question 1:
How can I implement GetSingleControl(int, int) that returns the MyCustomControl on the specified position? I started with a solution involving VisualTreeHelper which seems to be clumsy and unflexible.
Question 2:
How can I set Name of all MyCustomControls, e.g. something like "MyCustomControl_01_05" for the item in row 1 and column 5.
Question 3:
If questions 1 and 2 cannot be answered on the basis of my solution, what would be a more suitable approach?
Thank you!
To give an example of what both elgonzo and Andy said, you should change things to be more MVVM friendly. Once you do more research you will understand why you dont want to bother with the DependencyProperties, binding to the code behind, and manually coding all the additions of the usercontrols.
This could be made pretty or more streamlined, but i coded it to give a full example of how this could be done with MVVM. I tried to make it simple and basic, while demonstrating how to refactor your idea.
New MainWindow.xaml
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestApp"
d:DataContext="{d:DesignInstance {x:Type local:MainWindowViewModel}}"
mc:Ignorable="d"
Name="MyMainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl Name="MyItemsControl" ItemsSource="{Binding MyList}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding GridRow}" />
<Setter Property="Grid.Column" Value="{Binding GridColumn}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding ColumnCount}" Rows="{Binding RowCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Fill="{Binding Fill1}"/>
<TextBlock Text="{Binding Text1}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
New MainWindow.xaml.cs (Notice there is no extra code)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
Add a file MainWindowViewModel.cs:
-note the MyElement could be abstracted to a viewmodel for a UserControl if you desire.
public class MyElement : INotifyPropertyChanged
{
public MyElement()
{
//some default data for design testing
Text1 = "Test";
Fill1 = new SolidColorBrush(Colors.Red);
GridColumn = 13;
GridRow = 5;
}
private string _text1;
public string Text1
{
get { return _text1; }
set{
if (value != _text1) { _text1 = value; RaisePropertyChanged(); }
}
}
private Brush _fill1;
public Brush Fill1
{
get { return _fill1; }
set
{
if (value != _fill1) { _fill1 = value; RaisePropertyChanged(); }
}
}
private int _gridRow;
public int GridRow
{
get { return _gridRow; }
set
{
if (value != _gridRow) { _gridRow = value; RaisePropertyChanged(); }
}
}
private int _gridColumn;
public int GridColumn
{
get { return _gridColumn; }
set
{
if (value != _gridColumn) { _gridColumn = value; RaisePropertyChanged(); }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel() : this(13, 5) { }
public MainWindowViewModel(int columns, int rows)
{
ColumnCount = columns;
RowCount = rows;
MyList = new ObservableCollection<MyElement>();
//your original setup code
for (int i = 0; i < columns; i++)
{
for (int j = 0; j < rows; j++)
{
var vm = new MyElement
{
Text1 = (i / columns + 1) + ", " + (i % columns + 1),
Fill1 = new SolidColorBrush((i % 2 != 0) ? Colors.Yellow : Colors.Red),
GridColumn = i,
GridRow = j
};
MyList.Add(vm);
}
}
}
private int _rowCount;
public int RowCount
{
get { return _rowCount; }
set
{
if (value != _rowCount) { _rowCount = value; RaisePropertyChanged(); }
}
}
private int _columnCount;
public int ColumnCount
{
get { return _columnCount; }
set
{
if (value != _columnCount) { _columnCount = value; RaisePropertyChanged(); }
}
}
public ObservableCollection<MyElement> MyList { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I did a more full solution where it uses INotifyPropertyChanged. I wont explain the reason for using it (in the event you are unaware), as there are much better explanations you can quickly search for.
I also made it so all the dynamic information uses Binding to make things easier to change. Now the Grid size, and item positioning are bound to your data. So it should adjust automatically as you change your "MyElement"
This should give you a good starting point for refactoring your code, and help you utilize what WPF was designed to do, as there are many mechanisms built in so you dont have to hard code UI layer manipulation (as you were in the code behind)
This also answers your Questions:
Q1 : You can now just access to the List of MyElements and change them accordingly. The UI layer should update automatically when you change anything.
Q2 : You shouldnt need to do this now, as each MyElement will keep a property for it's Grid Position. Thus you can just access that.
I have a Log object that contains a list of Curve objects. Each curve has a Name property and an array of doubles. I want the Name to be in the column header and the data below it. I have a user control with a datagid. Here is the XAML;
<UserControl x:Class="WellLab.UI.LogViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="500">
<Grid>
<StackPanel Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="stackPanel1" VerticalAlignment="Stretch" Width="Auto">
<ToolBarTray Height="26" Name="toolBarTray1" Width="Auto" />
<ScrollViewer Height="Auto" Name="scrollViewer1" Width="Auto" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Visible" CanContentScroll="True" Background="#E6ABA4A4">
<DataGrid AutoGenerateColumns="True" Height="Auto" Name="logDataGrid" Width="Auto" ItemsSource="{Binding}" HorizontalAlignment="Left">
</DataGrid>
</ScrollViewer>
</StackPanel>
</Grid>
In the code behind I have figured out how to create columns and name them, but I have not figured out how to bind the data.
public partial class LogViewer
{
public LogViewer(Log log)
{
InitializeComponent();
foreach (var curve in log.Curves)
{
var data = curve.GetData();
var col = new DataGridTextColumn { Header = curve.Name };
logDataGrid.Columns.Add(col);
}
}
}
I wont even show the code I tried to use to bind the array "data", since nothing even came close. I am sure I am missing something simple, but after hours of searching the web, I have to come begging for an answer.
You need to pivot the log data into a collection of RowDataItem where each row contains a collection of double values, one for each Curve. At the same time you can extract the column names. So the data would end up like this.
public class PivotedLogData : ViewModelBase
{
public PivotedLogData(Log log)
{
ColumnNames = log.Curves.Select(c => c.Name).ToList();
int numRows = log.Curves.Max(c => c.Values.Count);
var items = new List<RowDataItem>(numRows);
for (int i = 0; i < numRows; i++)
{
items.Add(new RowDataItem(
log.Curves.Select(
curve => curve.Values.Count > i
? curve.Values[i]
: (double?) null).ToList()));
}
Items = items;
}
public IList<string> ColumnNames { get; private set; }
public IEnumerable<RowDataItem> Items { get; private set; }
}
public class RowDataItem
{
public RowDataItem(IList<double?> values)
{
Values = values;
}
public IList<double?> Values { get; private set; }
}
Then you would create DataGridTextColumn items as above but with a suitable binding
var pivoted = new PivotedLogData(log);
int columnIndex = 0;
foreach (var name in pivoted .ColumnName)
{
dataGrid.Columns.Add(
new DataGridTextColumn
{
Header = name,
Binding = new Binding(string.Format("Values[{0}]", columnIndex++))
});
}
Now bind the data to the grid
dataGrid.ItemsSource = pivoted.Items;
Is there anyway to speed-test which of the following resolve faster in XAML for colors:
Named Color: Orange
Hex: #ff6600
Shorthand Hex: #f60
Known Color: DarkOrange
This is not just a curiosity or an academic exercise. I have tons and tons of animations and colors that change colors many many times, on a large scale. I need to eek out ever bit of time-saving I can.
Looking for a way to test the above against each other for Silverlight. Any ideas?
I couldn't think of a definitive way to test this, but I put something together that may give you a hint. Effectively, I bound the color of a rectangle to a (string) SelectedColor property of a class that raised the PropertyChanged event every time it changed. Then I created four different List collections that had colors defined in all the different ways that you described, and in a background thread looped through them each some 10000 times, setting the SelectedColor property on each loop by dispatching to back to the UI thread. The complicated part was just keeping all the threads synchronized properly, and there's at least one hack in there where I loop every 20 ms until I saw that the previous task had finished. But it at least forces the UI thread to parse a color string, which is presumably what you're interested in.
At any rate, to the extent that this testing methodology is valid, it looks like using the "hex" notation is perhaps slightly faster than the others, though not by much. The average results on my machine after 10 test runs:
Hex: 4596 ms
Named: 4609 ms
Shorthand Hex: 5018 ms
Known Colors: 5065 ms
Just for reference, here's the code-behind:
public partial class MainPage : UserControl, INotifyPropertyChanged
{
public MainPage()
{
InitializeComponent();
namedColors.Add("Black");
namedColors.Add("Black");
namedColors.Add("Brown");
namedColors.Add("Cyan");
namedColors.Add("DarkGray");
namedColors.Add("Gray");
namedColors.Add("Green");
namedColors.Add("LightGray");
namedColors.Add("Magenta");
namedColors.Add("Orange");
hexColors.Add(Colors.Black.ToString());
hexColors.Add(Colors.Black.ToString());
hexColors.Add(Colors.Brown.ToString());
hexColors.Add(Colors.Cyan.ToString());
hexColors.Add(Colors.DarkGray.ToString());
hexColors.Add(Colors.Gray.ToString());
hexColors.Add(Colors.Green.ToString());
hexColors.Add(Colors.LightGray.ToString());
hexColors.Add(Colors.Magenta.ToString());
hexColors.Add(Colors.Orange.ToString());
knownColors.Add("LawnGreen");
knownColors.Add("LemonChiffon");
knownColors.Add("LightBlue");
knownColors.Add("LightCoral");
knownColors.Add("LightCyan");
knownColors.Add("LightGoldenrodYellow");
knownColors.Add("LightGray");
knownColors.Add("LightGreen");
knownColors.Add("LightPink");
knownColors.Add("LightSalmon");
shorthandHexColors.Add("#000");
shorthandHexColors.Add("#111");
shorthandHexColors.Add("#222");
shorthandHexColors.Add("#333");
shorthandHexColors.Add("#444");
shorthandHexColors.Add("#555");
shorthandHexColors.Add("#666");
shorthandHexColors.Add("#777");
shorthandHexColors.Add("#aaa");
shorthandHexColors.Add("#bbb");
LayoutRoot.DataContext = this;
}
private List<string> namedColors = new List<string>();
private List<string> hexColors = new List<string>();
private List<string> shorthandHexColors = new List<string>();
private List<string> knownColors = new List<string>();
private List<double> namedColorDurations = new List<double>();
private List<double> hexColorDurations = new List<double>();
private List<double> shorthandHexColorDurations = new List<double>();
private List<double> knownColorDurations = new List<double>();
private string selectedColor;
public string SelectedColor
{
get
{
return selectedColor;
}
set
{
if (selectedColor != value)
{
selectedColor = value;
RaisePropertyChanged("SelectedColor");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private bool isTesting = false;
private void testButton_Click(object sender, RoutedEventArgs e)
{
if (isTesting)
{
return;
}
else
{
isTesting = true;
}
ThreadPool.QueueUserWorkItem(o =>
{
AutoResetEvent resetEvent = new AutoResetEvent(false);
for (int i = 0; i < 10; i++)
{
TestColors(resetEvent, hexColors, lstHexColorDuration, hexColorDurations);
resetEvent.WaitOne();
TestColors(resetEvent, namedColors, lstNamedColorDuration, namedColorDurations);
resetEvent.WaitOne();
TestColors(resetEvent, shorthandHexColors, lstShorthandHexDuration, shorthandHexColorDurations);
resetEvent.WaitOne();
TestColors(resetEvent, knownColors, lstKnownColorDuration, knownColorDurations);
resetEvent.WaitOne();
}
Dispatcher.BeginInvoke(() =>
{
lstHexColorDuration.Items.Add(hexColorDurations.Average().ToString());
lstNamedColorDuration.Items.Add(namedColorDurations.Average().ToString());
lstShorthandHexDuration.Items.Add(shorthandHexColorDurations.Average().ToString());
lstKnownColorDuration.Items.Add(knownColorDurations.Average().ToString());
});
isTesting = false;
});
}
private int testsFinished = 0;
private void TestColors(AutoResetEvent resetEvent, List<string> colorList, ListBox resultListBox, List<double> results)
{
ThreadPool.QueueUserWorkItem(o =>
{
var start = DateTime.Now;
int testPasses = 10000;
testsFinished = 0;
for (int i = 0; i < testPasses; i++)
{
foreach (string color in colorList)
{
SetColor(color);
}
}
while (testsFinished < testPasses * colorList.Count)
{
Thread.Sleep(20);
}
DateTime finish = DateTime.Now;
results.Add((finish - start).TotalMilliseconds);
Dispatcher.BeginInvoke(() => resultListBox.Items.Add((DateTime.Now - start).ToString()));
resetEvent.Set();
});
}
private void SetColor(string color)
{
Dispatcher.BeginInvoke(() =>
{
SelectedColor = color;
Interlocked.Increment(ref testsFinished);
});
}
}
And here's the XAML proper:
<UserControl
x:Class="SilverlightScratch.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SilverlightScratch"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500">
<Grid x:Name="LayoutRoot" Background="White" >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Height="30" Width="100" x:Name="testButton" Content="Start Test" Click="testButton_Click" />
<Rectangle Height="30" Width="100" Fill="{Binding SelectedColor}" Grid.Row="2" />
<StackPanel Orientation="Horizontal" Grid.Row="1">
<StackPanel>
<TextBlock Text="Named Color Duration" />
<ListBox x:Name="lstNamedColorDuration" />
</StackPanel>
<StackPanel>
<TextBlock Text="Hex Color Duration" />
<ListBox x:Name="lstHexColorDuration" />
</StackPanel>
<StackPanel>
<TextBlock Text="Shorthand Hex Duration" />
<ListBox x:Name="lstShorthandHexDuration" />
</StackPanel>
<StackPanel>
<TextBlock Text="Known Colors Duration" />
<ListBox x:Name="lstKnownColorDuration" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
I'm very new to WPF, so I've just started making a very simple Memory card game just to learn the syntax and such. The game is where all the cards are facing down, you flip two over and if they match you remove them, otherwise reflip them down and try to remove all the cards in the shortest number of flips. Like I said, very simple... :)
My question is, is there no table element like in HTML so I can easily put the cards in a uniform layout instead of having to mess with margins?
Here's an example with it using the UniformGrid as Matt Hamilton suggested.
First, lets create the classes and data that we will be using.
Each card will be represented by a Card object, and have a Face property:
public class Card
{
public string Face { get; set; }
public Card() { }
}
Next, we will need a class that has our collection of Cards, and also a property that lets us set the number of cards. For the CardCollection we can use an ObservableCollection since that will automaticaly notify the UI when a Card is added or removed. The NumberOfCards property will need it's own method to notify the UI, for this we can implement the INotifyPropertyChanged interface. We'll also want a property that represents the number of Rows/Columns to use, this will just be the square root of our NumberOfCards:
public class Cards : INotifyPropertyChanged
{
private int myNumberOfCards;
public int NumberOfCards
{
get { return this.myNumberOfCards; }
set
{
this.myNumberOfCards = value;
NotifyPropertyChanged("NumberOfCards");
// Logic is going in here since this is just an example,
// Though I would not recomend hevily modifying the setters in a finalized app.
while (this.myNumberOfCards > CardCollection.Count)
{
CardCollection.Add(new Card { Face = (CardCollection.Count + 1).ToString() });
}
while (this.myNumberOfCards < CardCollection.Count)
{
CardCollection.RemoveAt(CardCollection.Count - 1);
}
NotifyPropertyChanged("CardColumns");
}
}
public int CardColumns
{
get
{
return (int)Math.Ceiling((Math.Sqrt((double)CardCollection.Count)));
}
}
private ObservableCollection<Card> myCardCollection;
public ObservableCollection<Card> CardCollection
{
get
{
if (this.myCardCollection == null)
{ this.myCardCollection = new ObservableCollection<Card>(); }
return this.myCardCollection;
}
}
public Cards(int initalCards)
{
NumberOfCards = initalCards;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
Finally, we can set this as our DataContext in the Window, and bind to our Cards class in the XAML. For the XAML I used a simple ItemsControl, so that it isn't selectable, and I set the DataTemplate to be a button, so that each card can be clicked on, that's all that is needed!
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new Cards(25);
}
}
<Window x:Class="Sample_BoolAnimation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300">
<Grid>
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<TextBlock Text="Number of Cards:" />
<TextBox Text="{Binding NumberOfCards, UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
<ItemsControl ItemsSource="{Binding CardCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding CardColumns}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Face}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</Grid>
</Window>
Another thing that I would recomend looking at is Josh Smith's ContentControl3D implementation. As that can give you the 'flipping' behavior that you are looking for implementing in the Card class quite nicely.
I'd recommend UniformGrid for your scenario. A quick search yielded this article which includes some code and screenshots that might help.
There is a Table in WPF, here's a good article on getting started with it. From experience the Table in WPF is not that easy to use and using a Grid is generally a better option.