So, I'm attempting to create a grid on the screen, and to do so, I've implemented a multidimensional array of Rectangles.
When the program starts, I use a for loop to increase the x and y coordinates to form the grid.
public Form1() {
InitializeComponent();
for (int x = 0; x < 12; x++) {
for (int y = 0; y < 12; y++) {
recArray[x, y] = new Rectangle(y * 50, x * 50, 100, 100);
}
Application.DoEvents();
}
}
My issue is trying to figure out when the user has clicked on a rectangle, and furthermore, which rectangle in the array that he/she has clicked on. As I will change the border to red when given the correct rectangle.
I'm using Visual Studio 2008, and here is my code so far.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Quoridor {
public partial class Form1 : Form {
private Pen pen = Pens.Black;
Rectangle[,] recArray = new Rectangle[12, 12];
public Form1() {
InitializeComponent();
for (int x = 0; x < 12; x++) {
for (int y = 0; y < 12; y++) {
recArray[x, y] = new Rectangle(y * 50, x * 50, 100, 100);
}
Application.DoEvents();
}
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
for (int x = 0; x < 12; x++) {
for (int y = 0; y < 12; y++) {
e.Graphics.DrawRectangle(pen, recArray[x, y]);
Application.DoEvents();
}
}
}
private void Form1_Click(object sender, EventArgs e) {
Point cursor = this.PointToClient(Cursor.Position);
Refresh();
}
}
}
I'm making this into a real game, with classes and all. But keep in mind, this is my second month programming, so don't be harsh ^_^
First, a couple of pointers:
You are storing your calculated y coordinate as the x position of your rectangle and vice versa. Switch the first two arguments of the Rectangle constructor to resolve this.
Use the MouseClick event instead of the Click event. The former provides you with a MouseEventArgs that contains the coordinate of the click relative to your Form.
Your rectangles are currently overlapping, since they are 100 x 100 but are positioned 50 pixels apart. This appears to be unintended (as a click would then usually land on 2 rectangles). This is also why your grid appears to be 13x13 instead of 12x12. To resolve this, pass 50 instead of 100 as the width and height of your Rectangles.
You could then determine the clicked Rectangle as follows:
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
Rectangle clickedRectangle = FindClickedRectangle(e.Location);
if (!clickedRectangle.IsEmpty)
Console.WriteLine("X: {0} Y: {1}", clickedRectangle.X, clickedRectangle.Y);
}
private Rectangle FindClickedRectangle(Point point)
{
// Calculate the x and y indices in the grid the user clicked
int x = point.X / 50;
int y = point.Y / 50;
// Check if the x and y indices are valid
if (x < recArray.GetLength(0) && y < recArray.GetLength(1))
return recArray[x, y];
return Rectangle.Empty;
}
Notice that I determine the clicked rectangle using a straightforward calculation, which is possible because your rectangles are positioned in a grid. If you intend to depart from the grid layout, looping over all rectangles and hit testing them using Rectangle.Contains(Point) is an alternative solution.
Related
I have a WPF user control with which the user can draw a rectangle on the map canvas to define an area for downloading background imagery tiles from a web service. The tiles are in Lat long 1x1 degree.
This is working, I pass a point as a parameter & download the tile. However I am now attempting to pass a List<Point> for each corner of the user defined rectangle & therefore determine which tiles intersect each point. This works to an extent however if the user defines a rectangle completely within a single tile then the same tile is downloaded 4 times (once for each point):
ForEach(point in rectanglePointsList)
{
DownloadTile(point);
}
I need to iterate over the points & determine whether to download the subsequent tile or not. This code is dumb to the tiles, I only have the point parameters that I'm passing in. A colleague suggested a nested for loop whereby I convert the X & Y from each point, find the min & max & then somehow determine whether a tile should be downloaded knowing that the tiles are always 1x1 degree. Is there an algorithm to achieve this? Don't really know where to start.
List<int>xValuesList = new List<int>();
List<int> yValuesList = new List<int>();
ForEach(point in RectanglePointsList)
{
xValuesList.Add(Convert.ToInt32(point.X);
yValuesList.Add(Convert.ToInt32(point.Y);
}
int maxX = xValuesList.Select(value => value.X).Max();
int maxY = yValuesList.Select(value => value.Y).Max();
//Lost after here...
Here's a quick sample :D
selects a group of tiles
adds them or not to the cache
shows which have been added or not
supports a single mouse click, no need to draw a rectangle
encompasses selected tiles no matter where you start/end
Here I've clicked on the first twos, this time I've drawn a rectangle encompassing the twos below, they haven't been added twice.
XAML:
<Window x:Class="WpfApplication1.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:local="clr-namespace:WpfApplication1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Window"
Title="MainWindow"
Width="525"
Height="350"
Background="Transparent"
SnapsToDevicePixels="True"
UseLayoutRounding="True"
mc:Ignorable="d">
<Grid />
</Window>
Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
private readonly HashSet<Int32Point> _set = new HashSet<Int32Point>();
private readonly int columns = 10;
private readonly int rows = 10;
private bool _down;
private Point _position1;
private Point _position2;
private Size size = new Size(500, 500);
public MainWindow()
{
InitializeComponent();
MouseDown += MainWindow_MouseDown;
MouseMove += MainWindow_MouseMove;
MouseUp += MainWindow_MouseUp;
}
private void MainWindow_MouseUp(object sender, MouseButtonEventArgs e)
{
_down = false;
InvalidateVisual();
// find rects selected
var x1 = (int) Math.Floor(_position1.X/(size.Width/columns));
var y1 = (int) Math.Floor(_position1.Y/(size.Height/rows));
var x2 = (int) Math.Ceiling(_position2.X/(size.Width/columns));
var y2 = (int) Math.Ceiling(_position2.Y/(size.Height/rows));
var w = x2 - x1;
var h = y2 - y1;
var builder = new StringBuilder();
for (var y = 0; y < h; y++)
{
for (var x = 0; x < w; x++)
{
var int32Point = new Int32Point(x1 + x, y1 + y);
var add = _set.Add(int32Point);
if (add)
{
// download image !!!
}
else
{
// image already downloaded, do something !
}
builder.AppendLine(string.Format("{0} : {1}", int32Point, (add ? "added" : "ignored")));
}
}
MessageBox.Show(builder.ToString());
}
private void MainWindow_MouseMove(object sender, MouseEventArgs e)
{
_position2 = e.GetPosition(this);
InvalidateVisual();
}
private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
{
_position1 = e.GetPosition(this);
_down = true;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext)
{
// draw a mini-map
for (var y = 0; y < rows; y++)
{
for (var x = 0; x < columns; x++)
{
var color = Color.FromRgb((byte) ((double) x/columns*255), (byte) ((double) y/rows*255), 255);
var brush = new SolidColorBrush(color);
var w = size.Width/columns;
var h = size.Height/rows;
var rect = new Rect(w*x, h*y, w, h);
drawingContext.DrawRectangle(brush, null, rect);
}
}
// draw selection rectangle
if (_down)
{
drawingContext.DrawRectangle(null, new Pen(new SolidColorBrush(Colors.White), 2.0),
new Rect(_position1, _position2));
}
}
private struct Int32Point
{
public readonly int X, Y;
public Int32Point(int x, int y)
{
X = x;
Y = y;
}
public override string ToString()
{
return $"X: {X}, Y: {Y}";
}
}
}
}
Go on and improve on that !
I am fully aware that I can load textures in OpenTK. But when I tried to render a picture using only points it shows really weird lines when ClientSize.Width is EXACTLY equal to the width of the picture being rendered. Like this:
And if I resize (enlarge) the ClientSize.Width of the window lines become kinda normal and there is actual reason they appear (they cover areas that can't be rendered):
This seems to occur regardless of the picture I open. Can somebody explain why there are lines in the first picture?
using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
namespace Miracle
{
class Program
{
public static void Main()
{
using (Window w = new Window())
w.Run(30);
}
}
class Window : GameWindow
{
private Color[,] pixels;
private int width, height;
private bool blink = true;
private int blinkcounter;
public Window() : base(1337, 666, GraphicsMode.Default, "Miracle", GameWindowFlags.Default) { }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Bitmap pic = new Bitmap("sample.png");
width = pic.Width;
height = pic.Height;
ClientSize = new Size(width, height);
pixels = new Color[width, height];
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
pixels[x, y] = pic.GetPixel(x, y);
GL.ClearColor(Color.FromArgb(0, 255, 0));
GL.Ortho(0, width, height, 0, -1, 1);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Title = ClientSize.Width + "x" + ClientSize.Height + (ClientSize.Width == width && ClientSize.Height == height ? " (original)" : "");
GL.Viewport(ClientSize);
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (blinkcounter == 6)
{
GL.ClearColor(blink ? Color.FromArgb(255, 0, 0) : Color.FromArgb(0, 255, 0));
blink = !blink;
blinkcounter = 0;
}
blinkcounter++;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.MatrixMode(MatrixMode.Projection);
GL.Begin(PrimitiveType.Points);
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
GL.Color4(pixels[x, y]);
GL.Vertex2(x, y);
}
GL.End();
SwapBuffers();
}
}
}
I will describe a solution to your problem and also why I think you're getting the problem.
First, why I think you're getting the problem:
The rasterization of points is weird and hardware dependent. This problem looks to be an error of floating point accuracy in the rasterization stage for points. The projected point is on the boundary of choosing between 2 pixels, and it chooses the wrong one due to FP limitations.
Solution:
Rasterizing an image using points is NOT recommended. That's not what point rasterization is used for. Instead, rasterize a full screen quad, give each vertex of the quad a texture coordinate, and in the fragment shader use the interpolated texture coordinate to fetch from the texture storing the image you want to render. This avoids the problem you were experiencing with the GL_POINTS because it ensures every pixel is drawn to once.
I am now drawing to a panel some dots to indicate a sort of dotted grid with 1% of margin of total panel width.
This is what I am doing now:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Pen my_pen = new Pen(Color.Gray);
int x,y;
int k = 1 ,t = 1;
int onePercentWidth = panel1.Width / 100;
for (y = onePercentWidth; y < panel1.Height-1; y += onePercentWidth)
{
for (x = onePercentWidth; x < panel1.Width-1; x += onePercentWidth)
{
e.Graphics.DrawEllipse(my_pen, x, y, 1, 1);
}
}
}
What is bothering me is that when the app starts I can see the dots being drawn on the panel. Even if it is very quick it still bothers me a lot.
Is it possible to draw the dots on the panel and load it directly drawn?
Thank you for the help
You could create a bitmap and draw it instead.
But before you do that: DrawEllipse is a little expensive. Use DrawLine with a Pen that has a dotted linestyle instead:
int onePercentWidth = panel1.ClientSize.Width / 100;
using (Pen my_pen = new Pen(Color.Gray, 1f))
{
my_pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
my_pen.DashPattern = new float[] { 1F, onePercentWidth -1 };
for (int y = onePercentWidth; y < panel1.ClientSize.Height - 1; y += onePercentWidth)
e.Graphics.DrawLine(my_pen, 0, y, panel1.ClientSize.Width, y);
}
Note that I am using using so I don't leak the Pen and ClientSize so I use only the inner width. Also note the exaplanation about the custom DashPattern on MSDN
So what I'm trying to do is create like a random image from panels of different colors. The user can choose how many panels (i.e. pixels) he wants to have and the number of different colors and then the program automatically generates that image. I'd really like to use panels for this because I will need this picture later on and need to modify every single pixel. As I'm comfortable with panels, I'd like to keep them and not use anything else.
So here's the code I'm using to create this panels:
//Creates two lists of panels
//Add items to list so that these places in the list can be used later.
//nudSizeX.Value is the user-chosen number of panels in x-direction
for (int a = 0; a < nudSizeX.Value; a++)
{
horizontalRows.Add(null);
}
//nudSizeY.Value is the user-chosen number of panels in y-direction
for (int b = 0; b < nudSizeY.Value; b++)
{
allRows.Add(null);
}
for (int i = 0; i < nudSizeY.Value; i++)
{
for (int j = 0; j < nudSizeX.Value; j++)
{
// new panel is created, random values for background color are assigned, position and size is calculated
//pnlBack is a panel used as a canvas on whoch the other panels are shown
Panel pnl = new Panel();
pnl.Size = new System.Drawing.Size((Convert.ToInt32(pnlBack.Size.Width)) / Convert.ToInt32(nudSizeX.Value), (Convert.ToInt32(pnlBack.Size.Height) / Convert.ToInt32(nudSizeY.Value)));
pnl.Location = new Point(Convert.ToInt32((j * pnl.Size.Width)), (Convert.ToInt32((i * pnl.Size.Height))));
//There are different types of panels that vary in color. nudTypesNumber iis the user-chosen value for howmany types there should be.
int z = r.Next(0, Convert.ToInt32(nudTypesNumber.Value));
//A user given percentage of the panels shall be free, i.e. white.
int w = r.Next(0, 100);
if (w < nudPercentFree.Value)
{
pnl.BackColor = Color.White;
}
//If a panel is not free/white, another rendom color is assigned to it. The random number determinig the Color is storede in int z.
else
{
switch (z)
{
case 0:
pnl.BackColor = Color.Red;
break;
case 1:
pnl.BackColor = Color.Blue;
break;
case 2:
pnl.BackColor = Color.Lime;
break;
case 3:
pnl.BackColor = Color.Yellow;
break;
}
}
//Every panel has to be added to a list called horizontal rows. This list is later added to a List<List<Panel>> calles allRows.
horizontalRows[j] = (pnl);
//The panel has also to be added to the "canvas-panel" pnl back. The advantage of using the canvas panel is that it is easier to determine the coordinates on this panel then on the whole form.
pnlBack.Controls.Add(pnl);
}
allRows[i] = horizontalRows;
}
As you might imagine, this is very slow when creating a checkerboard of 99x99 because the program has to loop through the process nearly 10000 times.
What would you to to improve performance? I said I'd like to keep doing it with panels because I'm comfortable with them, but if using panels is even more dumb than I thought, I'm open to other options. The program gets slower and slower the more panels it has already created. I guess that's because of the adding to the list that grows larger and larger?
This is how the output looks right now:
This is what I want to do with my "picture" later: I basically want to do Schellings model. That model shows how different groups of people (i.e. different colors) segregate when they want to have a certain percentage of people around them that belong to their group. That means that later on I have to be able to check for each of the panels/pixels what the neighbours are and have to be able to be able to change color of each pixel individually.
I don't want a ready solution, I'm just hoping for tips how to improve the speed of the picture-creating process.
Thank you very much
Instead of using Panels use a matrix to store your colors and other information you need.
In OnPaint event, use this matrix to draw the rectangles using GDI+.
Here is an example on how to draw 10x10 "pixels" if you have a matrix that contains colors:
private void myPanel_Paint(object sender, PaintEventArgs e)
{
for (var y=0; y < matrix.GetUpperBound(0); y++)
for (var x=0; x < matrix.GetUpperBound(1); x++)
{
var Brush = new SolidBrush(matrix[y,x]);
e.Graphics.FillRectangle(Brush, new Rectangle(x*10, y*10, 10, 10));
}
}
Use a picturebox to do your drawing. You've already got the code to see where each panel should be, just change it to draw a rectangle at each position. This way, you'll just be drawing a few rectangles on a board instead of working with 10.000 GUI objects.
Oh, keep your model/logic and view separated. Keep one matrix with all your information and just use a "Paint method" to draw it.
Your model could look something like this:
MyPanel[,] panels;
class MyPanel
{
Color color;
}
This way it's easy to check all neighbours of a panel, just check in the panels matrix.
And your view should just do something like this:
class View
{
Paint(MyPanel[,] panels)
{
//Draw
}
}
I think your best approach here is to write a custom Control class to draw the squares, and a custom collection class to hold the squares.
Your square collection class could look like this:
public sealed class ColouredSquareCollection
{
readonly int _width;
readonly int _height;
readonly Color[,] _colours;
public ColouredSquareCollection(int width, int height)
{
_width = width;
_height = height;
_colours = new Color[_width, _height];
intialiseColours();
}
public Color this[int x, int y]
{
get { return _colours[x, y]; }
set { _colours[x, y] = value; }
}
public int Width
{
get { return _width; }
}
public int Height
{
get { return _height; }
}
void intialiseColours()
{
for (int y = 0; y < _height; ++y)
for (int x = 0; x < _width; ++x)
_colours[x, y] = Color.White;
}
}
Then you write a custom control. To do so, add a new Custom control via Add new item -> Windows Forms -> Custom Control, and call it ColouredSquareHolder.
Then change the code to look like this. Notice how it is responsible for drawing all the squares:
public sealed partial class ColouredSquareHolder: Control
{
ColouredSquareCollection _squares;
public ColouredSquareHolder()
{
ResizeRedraw = true;
DoubleBuffered = true;
InitializeComponent();
}
public ColouredSquareCollection Squares
{
get
{
return _squares;
}
set
{
_squares = value;
Invalidate(); // Redraw after squares change.
}
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
if (_squares == null)
return;
int w = Width;
int h = Height;
int nx = _squares.Width;
int ny = _squares.Height;
var canvas = pe.Graphics;
for (int yi = 0; yi < ny; ++yi)
{
for (int xi = 0; xi < nx; ++xi)
{
int x1 = (xi*w)/nx;
int dx = ((xi + 1)*w)/nx - x1;
int y1 = (yi*h)/ny;
int dy = ((yi+1)*h)/ny - y1;
using (var brush = new SolidBrush(_squares[xi, yi]))
canvas.FillRectangle(brush, x1, y1, dx, dy);
}
}
}
}
Now you'll need to set up the square collection, add it to a ColouredSquareHolder and then add that to a form.
Firstly, add the ColouredSquareHolder to your test program and compile it so that it will show up in the Toolbox for the Windows Forms Editor.
Then create a new default Form called Form1, and from the Toolbox add a ColouredSquareHolder to it, and set the ColouredSquareHolder to Dock->Fill. Leave it called the default colouredSquareHolder1 for this demonstration.
Then change your Form1 class to look like this:
public partial class Form1: Form
{
readonly ColouredSquareCollection _squares;
readonly Random _rng = new Random();
public Form1()
{
InitializeComponent();
_squares = new ColouredSquareCollection(100, 100);
for (int x = 0; x < _squares.Width; ++x)
for (int y = 0; y < _squares.Height; ++y)
_squares[x, y] = randomColour();
colouredSquareHolder1.Squares = _squares;
}
Color randomColour()
{
return Color.FromArgb(_rng.Next(256), _rng.Next(256), _rng.Next(256));
}
}
Run your program and see how much faster it is at drawing the squares.
Hopefully this will give you the basis for something that you can build on.
Note: If you change the colours in the square collection, you will need to call .Invalidate() on the control in the form to make it redraw with the new colours.
well I suggest you using GDI+ instead , you can store your colors in a 2 dimensional array so you can draw the desired image based on that and also you can loop through them for further process , take a look at this code and also the demo project :
as you mentioned that you're not familiar with gdi+ , there is a demo project included so you can check it yourself and see how It's done in gdi+ :
demo project : ColorsTableDemoProject
Color[,] colorsTable;
Bitmap b;
Graphics g;
int size = 80; // size of table
int pixelWidth = 5; // size of each pixel
Random r = new Random();
int rand;
// CMDDraw is my Form button which draws the image
private void CMDDraw_Click(object sender, EventArgs e)
{
colorsTable = new Color[size, size];
pictureBox1.Size = new Size(size * pixelWidth, size * pixelWidth);
b = new Bitmap(size * pixelWidth, size * pixelWidth);
g = Graphics.FromImage(b);
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
rand = r.Next(0, 4);
switch (rand)
{
case 0: colorsTable[x, y] = Color.White; break;
case 1: colorsTable[x, y] = Color.Red; break;
case 2: colorsTable[x, y] = Color.Blue; break;
case 3: colorsTable[x, y] = Color.Lime; break;
default: break;
}
g.FillRectangle(new SolidBrush(colorsTable[x, y]), x * pixelWidth, y * pixelWidth, pixelWidth, pixelWidth);
}
}
pictureBox1.Image = b;
}
I have problem with my C# winform project.
I have function that draw squares:
public void DrawingSquares(int x, int y)
{
System.Drawing.Graphics graphicsObj;
graphicsObj = this.CreateGraphics();
Pen myPen = new Pen(System.Drawing.Color.Black, 5);
Rectangle myRectangle = new Rectangle(x, y, 100, 100);
graphicsObj.DrawRectangle(myPen, myRectangle);
}
private void button1_Click(object sender, EventArgs e)
{
z = Convert.ToInt16(textBox1.Text)-1;
k = Convert.ToInt16(textBox2.Text)-1;
DrawAllSquares();
}
private void DrawAllSquares()
{
int tempy = y;
for (int i = 0; i < z; i++)
{
DrawingSquares(x, y);
for (int j = 0; j < k - 1; j++)
{
tempy += 50;
DrawingSquares(x, tempy);
}
x += 50;
tempy = y;
}
}
In my project, I have a function that I use to move button around the form at runtime, but when the button is moved onto the drawing the drawing is deleted.
What can I do to make the drawing permanent?
If you need permanently (in terms of application life time), by any means, you need to use it inside you Control's (the Control where rectangle is have to be drawn), OnPaint method.
If you need an animation too: it could be resolved by using a timer and changing coordinates that you pass like a parameters to your DrawSquares.
Hope this helps.
EDIT
A pseudocode:
public class MyControl : Control
{
public override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
DrawingSquares(e.Graphics, valueX, valueY);
}
public void DrawingSquares(Graphics graphicsObj, int x, int y)
{
Pen myPen = new Pen(System.Drawing.Color.Black, 5);
Rectangle myRectangle = new Rectangle(x, y, 100, 100);
graphicsObj.DrawRectangle(myPen, myRectangle);
}
}
valueX and valueY are relative X and Y coordinates where you want the rectangle to be drawn.
These coordinates can be constant values, or you can change them from some timer and call Invalidate() on MyControl, so paint will be executed.