Relocate children in grid - c#

I'm trying to realize a chess game in C# with WPF. By now I visualized the chess grid and the figures in it. My .xaml file contains just a grid (named "playground") which is 8x8. The initializing in the .xaml.cs file looks like the following:
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
Border border = new Border();
if (y % 2 == (x % 2 == 0 ? 0 : 1))
{ // Chess like look
border.Background = black; //black is a static SolidColorBrush
}
// a ChessTile is an Image which can be a Figure or an EmptyTile
// omitted code... evaluate whether what figure the tile is or empty
ChessTile tile;
Grid.SetColumn(border, x);
Grid.SetRow(border, y);
border.HorizontalAlignment = HorizontalAlignment.Stretch;
border.VerticalAlignment = VerticalAlignment.Stretch;
if (tile is Figure)
{
// Set Event to border so the user is able to click outside the image
border.MouseDown += ClickFigure;
}
border.Child = tile; // Set tile as Child
playground.Children.Add(border); // Add border to Child
}
}
After initializing I want to move a chess figure to another Column and or Row in the grid. Currently I am evaluating in ClickFigure on which empty tile the figure is able to move to, then adding a new Handler to these empty tiles and moving my Figures there. I'm calling tile.Move(otherTile) in the Handler:
(from ChessTile.cs)
public Vector Coordinates
{
get
{
return position; //private Vector
}
private set
{
position = value;
Grid.SetRow(this, (int)position.X); //relocate this (ChessTile)
Grid.SetColumn(this, (int)position.Y);
}
}
public void Move(ChessTile other)
{
Vector temp = position;
Coordinates = other.position; //relocating both tiles through properties
other.Coordinates = temp;
}
The problem now is that the grid does not do anything at all. I searched for some method to update/repaint the grid manually but I didn't find anything. I also tried to remove the child first and then to add it again but that gave me an error saying that the child was already bound to an UIElement and that I would have to seperate it first.
Do you have any idea what I am doing wrong?

You need to change the row and column on the original Border placed inside the Grid.
In your setup code, you set column and row on a border object:
Grid.SetColumn(border, x);
Grid.SetRow(border, y);
But then in your Coordinates property, you're setting the column and row on this.
Grid.SetRow(this, (int)position.X); //relocate this (ChessTile)
Grid.SetColumn(this, (int)position.Y);
Assuming this is a custom class you've written, then you need to make sure it has a reference to the original Border and change your Coordinate setter to:
Grid.SetRow(border, (int)position.X); //relocate the border!
Grid.SetColumn(border, (int)position.Y);
Notice the change from this => border.

Related

Dynamically add Chart to Windows Forms - shows up blank

I'm trying to add a Chart control dynamically to a Form (using C#, this should all be .NET 4.0), but it's always blank (only the background color shows). I tried the same code in a Form that already has a control and it works, so I imagine it's some initialization function I should call (I tried Invalidate() on the control and Refresh() on both control and the panel it's being placed in, made no difference). I went through the few similar posts I found, tried throwing in any other commands they used (BeginInit() is from one such post) but no luck so far. Any ideas?
BTW I want to display 6-9 charts (position, speed and acceleration in 3D space) so I'd rather add them dynamically than have 9 sets of assignments. Here's the code that adds the charts to the panel:
foreach (KeyValuePair<string, List<double>> p in b.storedValues)
{
Control c = getChartForData(p);
panel1.Controls.Add(c);
c.Invalidate();
c.Refresh();
break;
}
And the function that creates each chart:
private Chart getChartForData(KeyValuePair<string, List<double>> data)
{
Chart c = new Chart();
((System.ComponentModel.ISupportInitialize)c).BeginInit();
c.Series.Clear();
c.BackColor = Color.White;
c.Height = 300;
c.Width = 500;
c.Palette = ChartColorPalette.Bright;
Series s = new Series(data.Key);
s.ChartType = SeriesChartType.Spline;
double maxValue = 0;
//NOTE: Going logarithmic on this, too big numbers
for (int i = 0; i < data.Value.Count; i++)
{
maxValue = Math.Max(Math.Log10(data.Value[i]), maxValue);
}
for (int i = 0; i < data.Value.Count; i++)
{
s.Points.AddXY(i,Math.Log10(data.Value[i]) * c.Height / maxValue);
}
c.Series.Add(s);
return c;
}
Many thanks in advance.
When you create a Chart yourself, in code, it does not contain any ChartArea.
Therefore, nothing is displayed.
I'm guessing that the designer generates some code to initialize a default chart area when you drag and drop a chart control onto the form.
Also, you should really let the chart control handle the layout, instead of calculating the desired position of each point based on the height of the chart control.
I would go as simple as possible to get something that's working, and then you can tweak the range of the axis afterwards. You can also set an axis to be logarithmic.
Start with trying out this minimal version, and make sure that displays something, before you complicate things. This works for me.
private Chart getChartForData(string key, List<double> data)
{
Chart c = new Chart();
Series s = new Series(key);
s.ChartType = SeriesChartType.Spline;
for (int i = 0; i < data.Count; i++)
{
s.Points.AddXY(i, data[i]);
}
c.Series.Add(s);
var area = c.ChartAreas.Add(c.ChartAreas.NextUniqueName());
s.ChartArea = area.Name;
// Here you can tweak the axis of the chart area - min and max value,
// where they display "ticks", and so on.
return c;
}

Textblock margin causes out of bounds text

I'm currently trying to create a visual component to have scrolling text (left to right and right to left) - pretty much an html marquee.
I have a grid divided in several columns & rows, and I want to place my component inside one of the grid slots.
The grid (named UIGrid) is generated like this :
for (int i = 0; i < xDivisions; i++)
{
ColumnDefinition newColumn = new ColumnDefinition();
UIGrid.ColumnDefinitions.Add(newColumn);
}
for (int i = 0; i < yDivisions; i++)
{
RowDefinition newRow = new RowDefinition();
UIGrid.RowDefinitions.Add(newRow);
}
The component I'm adding is just a border with a textblock as a child. I place the border inside the Grid like this :
border = new Border();
Grid.SetColumn(border, xPosition);
Grid.SetRow(border, yPosition);
textBlock = new TextBlock();
border.Child = textBlock;
textBlock.Text = "Scrolling text from left to right";
UIGrid.Children.Add(border);
I'm using a timer to increment the textblock margin, here's the timer callback simplified body :
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double textWidth = textBlock.DesiredSize.Width;
double visibleWidth = componentBase.ActualWidth;
double targetMargin = textWidth < visibleWidth ? visibleWidth : textWidth;
if (margin.Left == targetMargin)
{
margin.Left = -textWidth;
} else
{
margin.Left++;
}
When the text slides from left to right, it behaves nicely :
https://s10.postimg.org/p0nt7vl09/text_good.png
Text "leaving" the grid slot is hidden.
However, when I set the textblock's margin as negative so it may come back inside the viewable area from the left, the text is visible even though it's outside its allocated slot :
https://s10.postimg.org/pownqtjq1/text_bad.png
I've tried using padding instead, but I can't set a negative padding. I've tried a few other things, but I feel like I've encountered a roadblock.
What could I do to get a nicely scrolling text ?
If you want nicely scrolling text ListView might be a better option. It is dynamic and you can bind it to your object. It would take a lot of this guess work out.
Ed Plunkett led me in the right direction with the Clip property. The idea is to do this :
border.Clip = new RectangleGeometry
{
Rect = new Rect(0, 0, border.ActualWidth, border.ActualHeight)
};
Of course, that doesn't work if the border hasn't been rendered yet (and of course it isn't when my code is running). You can force the measurement to take place using 'Measure' as I did to measure the text length in pixels, but it behaved strangely on my border. I wouldn't get the correct size at all.
In the end, I simply subscribed to the border's SizeChanged event :
border.SizeChanged += OnSizeComputed;
When that event is fired, I create the RectangleGeometry using ActualWidth & ActualHeight.

WPF InkCanvas access all pixels under the strokes

It seems that WPF's InkCanvas is only able to provide the points of the stroke (independent of the width and height of the stroke). For an application, I need to know all the points that are drawn by the InkCanvas.
For instance, assume that the width and height of the stroke are 16. Using this stroke size I paint a dot on the InkCanvas. Is there a straightforward way to obtain all 256 pixels in this dot (and not the center point of this giant dot alone)?
Why I care:
In my application, the user uses an InkCanvas to draw on top of a Viewport3D which is displaying a few 3D objects. I want to use all the points of the strokes to perform ray casting and determine which objects in the Viewport3D have been overlaid by the user's strokes.
I found a very dirty way of handling this. If anyone knows of a better method, I'll be more than happy to upvote and accept their response as an answer.
Basically my method involves getting the Geometry of each stroke, traversing all the points inside the boundaries of that geometry and determining whether the point is inside the geometry or not.
Here's the code that I am using now:
foreach (var stroke in inkCanvas.Strokes)
{
List<Point> pointsInside = new List<Point>();
Geometry sketchGeo = stroke.GetGeometry();
Rect strokeBounds = sketchGeo.Bounds;
for (int x = (int)strokeBounds.TopLeft.X; x < (int)strokeBounds.TopRight.X + 1; x++)
for (int y = (int)strokeBounds.TopLeft.Y; y < (int)strokeBounds.BottomLeft.Y + 1; y++)
{
Point p = new Point(x, y);
if (sketchGeo.FillContains(p))
pointsInside.Add(p);
}
}
You can use the StrokeCollection's HitTest method. I've compared the performance of your solution with this implementation and found the HitTest method performs better. Your mileage etc.
// get our position on our parent.
var ul = TranslatePoint(new Point(0, 0), this.Parent as UIElement);
// get our area rect
var controlArea = new Rect(ul, new Point(ul.X + ActualWidth, ul.Y + ActualHeight));
// hit test for any strokes that have at least 5% of their length in our area
var strokes = _formInkCanvas.Strokes.HitTest(controlArea, 5);
if (strokes.Any())
{
// do something with this new knowledge
}
You can find the documentation here:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.ink.strokecollection.hittest?view=netframework-4.7.2
Further, if you only care if any point is in your rect, you can use the code below. It's an order of magnitude faster than StrokeCollection.HitTest because it doesn't care about percentages of strokes so it does a lot less work.
private bool StrokeHitTest(Rect bounds, StrokeCollection strokes)
{
for (int ix = 0; ix < strokes.Count; ix++)
{
var stroke = strokes[ix];
var stylusPoints = stroke.DrawingAttributes.FitToCurve ?
stroke.GetBezierStylusPoints() :
stroke.StylusPoints;
for (int i = 0; i < stylusPoints.Count; i++)
{
if (bounds.Contains((Point)stylusPoints[i]))
{
return true;
}
}
}
return false;
}

Conways Game Of Life Rendering

I've finished a game of life implementation but I'm running into a issue when rendering the grid after applying the game rules. I have a game loop that looks like this:
while (gameIsRunning)
{
//Needed for accessing UIControls from the background
//thread.
if (InvokeRequired)
{
//Process the array.
MainBoard.Cells = engine.ApplyGameRules(MainBoard.Cells, MainBoard.Size.Height, MainBoard.Size.Width, BOARD_DIMENSIONS);
//Check if there is a state such as
//all states being dead, or all states being
//alive.
//Update the grid with the updated cells.
this.Invoke(new MethodInvoker(delegate
{
timeCounter++;
lblTimeState.Text = timeCounter.ToString();
pictureBox1.Invalidate();
pictureBox1.Update();
Thread.Sleep(100);
}));
}
}
and a draw function that looks like this:
for (int x = 0; x < MainBoard.Size.Height; x++)
{
for (int y = 0; y < MainBoard.Size.Width; y++)
{
Cell individualCell = MainBoard.Cells[y, x];
if (individualCell.IsAlive() == false)
{
e.Graphics.FillRectangle(Brushes.Red, MainBoard.Cells[y, x].Bounds);
}
//White indicates that cells are alive
else if (individualCell.IsAlive() == true)
{
e.Graphics.FillRectangle(Brushes.White, MainBoard.Cells[y, x].Bounds);
}
else if (individualCell.IsInfected() == true)
{
e.Graphics.FillRectangle(Brushes.Green, MainBoard.Cells[y, x].Bounds);
}
//Draws the grid background itself.
e.Graphics.DrawRectangle(Pens.Black, MainBoard.Cells[y, x].Bounds);
}
}
The problem that I'm running into is that I'm applying all of the game rules to every cell in the grid and then drawing that grid and then applying all the rules again so I never get the life form blobs that I should be seeing. Should the game rules be applied on a cell by cell basis so that its something along the lines of: Apply game rule to cell, draw grid, apply game rule to another cell, draw grid...?
It looks like the current intent of the program is correct.
What you should be doing is (pseudocode):
Board oldBoard = new Board(start cell definitions);
while(not finished) {
Board newBoard = calculate(oldBoard);
display(newBoard);
oldBoard = newBoard();
}
If you're not seeing the forms you expect, then either your display code is wrong, or your rule code is wrong.
In the pseudocode I'm throwing away the previous generation's board once it's no longer needed, and making a new board for each generation. calculate() contains a new Board() statement.
Of course if it's expensive to make a new board you could re-use one instead, and just flip back and forth between a "current" and "other" board. Just bear in mind that each time you write to a board, its new state must be 100% a function of the previous generation's state, and in no way affected by its own starting state. i.e. you must write to every cell.
An alternative method would be for each cell to hold two values. So instead of two boards with one value per cell, you have one board with each cell containing a "current" and "previous" value.
Board board = new Board(initial state);
while(not finished) {
board.calculate(); // fills "current" cells based on "previous" cells.
display(board);
board.tick(); // "current" becomes "previous".
// "previous" becomes current, but is "dirty" until calculated.
}
There are lots of ways you could do it. One way is:
public class Cell {
private boolean[] state = new boolean[2];
private int generation = 0;
public void setCurrentState(boolean state) {
state[generation] = state;
}
public void getCurrentState() {
return state[generation];
}
public void getLastState() {
return state[ (generation + 1) % 2 ];
}
public void tick() {
generation = (generation + 1) % 2;
}
}

Windows Form color changing

So i am attempting to make a MasterMind program as sort of exercise.
Field of 40 picture boxes (line of 4, 10 rows)
6 buttons (red, green, orange, yellow, blue, purple)
When i press one of these buttons (lets assume the red one) then a picture box turns red.
My question is how do i iterate trough all these picture boxes?
I can get it to work but only if i write :
And this is offcourse no way to write this, would take me countless of lines that contain basicly the same.
private void picRood_Click(object sender, EventArgs e)
{
UpdateDisplay();
pb1.BackColor = System.Drawing.Color.Red;
}
Press the red button -> first picture box turns red
Press the blue button -> second picture box turns blue
Press the orange button -> third picture box turns orange
And so on...
Ive had a previous similar program that simulates a traffic light, there i could assign a value to each color (red 0, orange 1, green 2).
Is something similar needed or how exactly do i adress all those picture boxes and make them correspond to the proper button.
Best Regards.
I wouldn't use controls, instead you can use a single PictureBox and handle the Paint event. This lets you draw inside that PictureBox so you can quickly handle all your boxes.
In code:
// define a class to help us manage our grid
public class GridItem {
public Rectangle Bounds {get; set;}
public Brush Fill {get; set;}
}
// somewhere in your initialization code ie: the form's constructor
public MyForm() {
// create your collection of grid items
gridItems = new List<GridItem>(4 * 10); // width * height
for (int y = 0; y < 10; y++) {
for (int x = 0; x < 4; x++) {
gridItems.Add(new GridItem() {
Bounds = new Rectangle(x * boxWidth, y * boxHeight, boxWidth, boxHeight),
Fill = Brushes.Red // or whatever color you want
});
}
}
}
// make sure you've attached this to your pictureBox's Paint event
private void PictureBoxPaint(object sender, PaintEventArgs e) {
// paint all your grid items
foreach (GridItem item in gridItems) {
e.Graphics.FillRectangle(item.Fill, item.Bounds);
}
}
// now if you want to change the color of a box
private void OnClickBlue(object sender, EventArgs e) {
// if you need to set a certain box at row,column use:
// index = column + row * 4
gridItems[2].Fill = Brushes.Blue;
pictureBox.Invalidate(); // we need to repaint the picturebox
}
I would use a panel as the container control for all the pic boxes, then:
foreach (PictureBox pic in myPanel.Controls)
{
// do something to set a color
// buttons can set an enum representing a hex value for color maybe...???
}
I wouldn't use pictureboxes, but instead would use a single picturebox, drawing directly onto it using GDI. The result is a lot faster, and it will set you up to write more complex games involving sprites and animation ;)
It's very easy to learn how.

Categories