G
It's easier to use. ItemsControl/Canvasand put the form through Path♪I've built this code: https://ru.stackoverflow.com/a/573196/10105 ♪First, VM-part. Reference Class VM Standard:class VM : INotifyPropertyChanged
{
protected bool Set<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
NotifyPropertyChanged(propertyName);
return true;
}
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
Several support structures:struct FieldSize
{
public int Width { get; }
public int Height { get; }
public FieldSize(int w, int h) { Width = w; Height = h; }
}
struct FieldPosition
{
public int X { get; }
public int Y { get; }
public FieldPosition(int x, int y) { X = x; Y = y; }
}
They represent the size of the field and the position on the field.Now VM is one cell. It's clear here:class CellVM : VM
{
public CellVM(int row, int column)
{
Position = new FieldPosition(row, column);
Activate = new RelayCommand(OnActivate);
}
public FieldPosition Position { get; }
public ICommand Activate { get; }
bool isActive = false;
public bool IsActive
{
get { return isActive; }
private set { Set(ref isActive, value); }
}
void OnActivate()
{
IsActive = !IsActive;
}
}
The only field that can change is IsActivethat's why he's changing. NotifyPropertyChanged♪ Team Activate I'll be on the click.It's the field now. To change the size of the field, we generate cells. The cages are listed as a list, if you need them as a two-strong area, add the retention in the body. GenerateCells♪class BoardVM : VM
{
FieldSize fieldSize;
public FieldSize FieldSize
{
get { return fieldSize; }
set { if (Set(ref fieldSize, value)) GenerateCells(); }
}
IEnumerable<CellVM> cells;
public IEnumerable<CellVM> Cells
{
get { return cells; }
private set { Set(ref cells, value); }
}
void GenerateCells()
{
var list = new List<CellVM>(FieldSize.Width * FieldSize.Height);
for (int j = 0; j < FieldSize.Height; j++)
for (int i = 0; i < FieldSize.Width; i++)
list.Add(new CellVM(i, j));
Cells = list;
}
}
With VM, we're all going to View. There'll be a few tricks.For starters, we need converters that turn coordinates into positions on the screen. There's a little different calculations for the right and wrong lines. The size of the cell is a parameter.class FieldPositionToCoordinateXConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
FieldPosition position = (FieldPosition)value;
double cellSize = (double)parameter;
if (position.Y % 2 == 0)
return position.X * cellSize;
else
return (position.X + 0.5) * cellSize;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
class FieldPositionToCoordinateYConverter : IValueConverter
{
static double diag = Math.Sqrt(3) / 2;
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
FieldPosition position = (FieldPosition)value;
double cellSize = (double)parameter;
return position.Y * cellSize * diag;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
Now XAML. In the window's resources, we'll connect the size of the cells and the converters:<Window.Resources>
<sys:Double x:Key="CellSize">25</sys:Double>
<view:FieldPositionToCoordinateXConverter x:Key="XConv"/>
<view:FieldPositionToCoordinateYConverter x:Key="YConv"/>
</Window.Resources>
Next, the content itself. I didn't make a window-size automobile, which would require an enveloper.Cells connect from the collection through ItemsControl:<ItemsControl ItemsSource="{Binding Cells}">
Since we're going to set out the coordinates of the “breast”, we're gonna need Canvas as a host: <ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
Now, one cell. <ItemsControl.ItemTemplate>
<DataTemplate>
Put it in square. Grid size required: <Grid Width="{StaticResource CellSize}" Height="{StaticResource CellSize}">
We'll put the inside through. Pathby identifying coordinates as https://ru.stackoverflow.com/a/632557/10105 : <Path Data="M -1,-1
M 0,-1
L 0.86602540378443864676372317075294,-0.5
L 0.86602540378443864676372317075294,0.5
L 0,1
L -0.86602540378443864676372317075294,0.5
L -0.86602540378443864676372317075294,-0.5
L 0,-1
M 1,1"
Stretch="Uniform"
Stroke="Black"
StrokeThickness="0.5">
Next, colour change depending on the value IsActive♪ We'll use the trigger. DataTrigger We're only available in style, so we're gonna have to set a flashlight: <Path.Style>
<Style TargetType="Path">
<Setter Property="Fill" Value="LightGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Fill" Value="DarkGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
Now, we need to set up a click. Since Path I can't do that on my own, there's three ways to use it as a stylized one. Button♪ Path Total ControlTemplateor InputBinding, or connect System.Windows.Interactivity.dll (from Expression Blend SDK, to be installed https://i.stack.imgur.com/W4V42.png or nuget https://www.nuget.org/packages/System.Windows.Interactivity.WPF/ and put a team on a mice event. It's just the second way. <Path.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding Activate}"/>
</Path.InputBindings>
Although I went third.i: - Prefix, defined as xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity") <i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding Activate}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
That's it. </Path>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
What else? We need to put the cage on the right coordinates. Sam. Grid Total DataTemplate It's packed in a container, so it's useless to display coordinates for him. So we need to move the container. This is done: <ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left"
Value="{Binding Position,
Converter={StaticResource XConv},
ConverterParameter={StaticResource CellSize}}"/>
<Setter Property="Canvas.Top"
Value="{Binding Position,
Converter={StaticResource YConv},
ConverterParameter={StaticResource CellSize}}"/>
</Style>
</ItemsControl.ItemContainerStyle>
I'll get the whole window code again.<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="HexGrid.View.MainWindow"
xmlns:view="clr-namespace:HexGrid.View"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="Test" Height="290" Width="300">
<Window.Resources>
<sys:Double x:Key="CellSize">25</sys:Double>
<view:FieldPositionToCoordinateXConverter x:Key="XConv"/>
<view:FieldPositionToCoordinateYConverter x:Key="YConv"/>
</Window.Resources>
<Grid Margin="10">
<ItemsControl ItemsSource="{Binding Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="{StaticResource CellSize}"
Height="{StaticResource CellSize}">
<Path Data="M -1,-1
M 0,-1
L 0.86602540378443864676372317075294,-0.5
L 0.86602540378443864676372317075294,0.5
L 0,1
L -0.86602540378443864676372317075294,0.5
L -0.86602540378443864676372317075294,-0.5
L 0,-1
M 1,1"
Stretch="Uniform"
Stroke="Black"
StrokeThickness="0.5">
<Path.Style>
<Style TargetType="Path">
<Setter Property="Fill" Value="LightGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Fill" Value="DarkGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
<Path.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding Activate}"/>
</Path.InputBindings>
</Path>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left"
Value="{Binding Position,
Converter={StaticResource XConv},
ConverterParameter={StaticResource CellSize}}"/>
<Setter Property="Canvas.Top"
Value="{Binding Position,
Converter={StaticResource YConv},
ConverterParameter={StaticResource CellSize}}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Window>
And standard App.xaml/App.xaml.cs:<Application x:Class="HexGrid.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new MainWindow()
{
DataContext = new BoardVM()
{
FieldSize = new FieldSize(10, 10)
}
}.Show();
}
}
Result:Everything!