easier way to create grid than creating 900 controls c#.net - c#

I'm trying to write a program that creates a 30x30 grid of 20px x 20px boxes. When you click on a box in the grid it changes the color of the box and stores the RGB value of it and the x,y coordinates (hidden in the box). So basically I'm making a simple paint program. The purpose of it is to assist me in programming LED RGB animations for a 30x30 pixel grid. So once I draw something I export the X,Y,R,G,B of each pixel in the image.
So my question is, is there an easy way to do this. With out creating 900 buttons and putting them together? I've got something sorta working :
Panel BU = new Panel();
BU.AutoSize = false;
BU.Location = new System.Drawing.Point(xpos, ypos);
BU.BackColor = System.Drawing.Color.Transparent;
BU.Font = new System.Drawing.Font("Microsoft Sans Serif", 5, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
BU.Name = row_num + " x " + col_num;
BU.Size = new System.Drawing.Size(20, 20);
BU.MouseDown +=new MouseEventHandler(BU_MouseDown);
BU.MouseEnter +=new EventHandler(BU_MouseEnter);
this.Controls.Add(BU);
xpos = xpos + 20;
px_num++;
col_num++;
if (col_num == 30)
{
col_num = 0;
ypos = ypos + 20;
row_num++;
xpos = 0;
};
But it takes WAAAAAY to long to load.

Yes.
Create one panel, and handle the grid painting and mouse events inside that.
Super simple example, optimized for nothing and flicker happy:
private Color[,] _Colors = new Color[30, 30];
private void panel1_Paint(object sender, PaintEventArgs e) {
int left = 0;
int top = 0;
for (int y = 0; y < 30; y++) {
left = 0;
for (int x = 0; x < 30; x++) {
Rectangle r = new Rectangle(left, top, 20, 20);
using (SolidBrush sb = new SolidBrush(_Colors[x, y]))
e.Graphics.FillRectangle(sb, r);
ControlPaint.DrawBorder3D(e.Graphics, r, Border3DStyle.Raised, Border3DSide.Left | Border3DSide.Top | Border3DSide.Right | Border3DSide.Bottom);
left += 20;
}
top += 20;
}
}
private void panel1_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
int left = 0;
int top = 0;
for (int y = 0; y < 30; y++) {
left = 0;
for (int x = 0; x < 30; x++) {
Rectangle r = new Rectangle(left, top, 20, 20);
if (r.Contains(e.Location)) {
_Colors[x, y] = Color.Red;
panel1.Invalidate();
}
left += 20;
}
top += 20;
}
}
}

A custom user control that renders 20x20 boxes would do the trick. To get the location of a mouse click, take the x and y values, divide by 20.
Something very similar here: https://github.com/i-e-b/DBSS/blob/master/DBSS/BigGrid/SheetView.cs

Well, instead of creating 900 controls, no difference if it's made in WindowsForms, or WPF with HD accelaration, it will be slow.
What can do, if you really need always have all controls visible on the screen, so potentially at some points also have 900 contemporary, is just draw rectangles. You can do an
emulation of the button.
Draw Rectangle, where Left and Top lines are darker then Right and Bottom, will give 3D filling to user, make it inverse colors of lines, and it will give to user a filling of pushed button. For sure you need to handle all mouse interactions, like MouseMove, MouseDown, MouseUp on your panel (cause the panel will be the only control present at this point), and figure out on what rectangle event happens and draw that rectangle accordingly.

I typically use a Bitmap object for this and use one of the overrides in Graphics.DrawImage to draw it zoomed in. Then I draw grid lines over top of the zoomed image.
I'm digging up some source code for you now.
Edit
Sorry, no source code handy at the moment. But basically what you want to do is create a new system.drawing.bitmap that is 30x30 pixels. In the paint even of teh control you want to draw it in, use the passed in Graphics Object (usually e.graphics) and call .DrawImage. Use one of the overloads that allows you to specify the output size so that you can scale it out by whatever zoom factor you want. You will also need to set the .PixelOffsetMode on the graphics object to .none, or else things will be offset by 1/2 your zoom. Set .InterpolationMode to .NearestNeighbor so that the output isn't blurry. This should give you a perfectly aligned "pixelated" zoomed image. Then just loop and draw horizontal and vertical grid lines.
To handle the mouse clicks just add a handler to the controls mouse down event, and divide the input position by your zoom factor to get the real x and y coords. then update that pixel of the source image and call .invalidate on the control you are drawing to. That will cause it to repaint with the updated image.

Related

How to scroll an zoomed image C#

I have a picturebox and i can zoom it.
When i zoom the picturebox i need to scroll left or right the image.
Can you tell me why when i zoom the image the left side is fixed and i can't scroll on left?
I have activated the AutoScroll on the main panel, but i can scroll only to right, but my image is centered and i need to scoll also to left.
What i want is like when you zoom and scroll an image inside a Photo app in Win 10. But i'm using the scoll bar and not the mouse pointer.
The structure is like in the next image, in red the main panel, and i green the picturebox.
private void image_Scroll(object sender, EventArgs e)
{
int pbWidth = pbImg.Image.Width + (pbImg.Image.Width * ztb.Value / 100);
int pbHeight = pbImg.Image.Height + (pbImg.Image.Height * ztb.Value / 100);
int mpWidth = mainPanel.Width:
int mpHeight = mainPanel.Height;
int pbX = (mpWidth - pbWidth) / 2;
int pbY = (mpHeight - pbHeight) / 2;
pbImg.Size = new Size(pbWidth, pbHeight);
Point p = new Point(pbX, pbY);
pictureBox.Location = p;
}

C# - scroll down a Chart without first ChartArea

The results I want are one main chart and several sub charts which are synchronized with the main chart. (Sub charts must share the same x-axis with the main chart.)
At first, I tried it using multiple chartAreas. I can sync sub chartAreas with the main chartArea. (Using this: How to align two (or more) charts in size, scroll and grid) This one is what I want exactly. But I can't only vertical scroll the sub-chartAreas. If I scroll, all the charts are scrolled. I want to vertical scroll only sub-chartAreas. (to show the main chartArea at top anytime even when the scroll bar is down)
So, I reverse a decision to use multiple charts (not chart Areas). I can place them into TableLayoutPanel. One chart per row. Then, I cannot sync their x-axes...
Is there any way to sync an x-axis with multiple charts?
Or to scroll only sub-chartAreas using multiple chartAreas?
By 'scroll' I mean Vertical scroll. See here:
From the image and the comments I gather that you actually want to scroll down a chart with several ChartArea but keep one fixed at the top.
((If that is so you should correct the question title!))
By default a Chart can only scroll the data within a zoomed ChartArea, not several ChartAreas within the Chart.
Here is an example of faking both ChartArea scrolling and freeezing the top ChartArea.
Here are the necessary steps:
We need a VerticalScrollbar control and anchor it to the right of the Chart.
We need to set its Minumum and Maximum fields to suitable values; I left the minimum at 0 and calculate the max with a few params..
Then we can code its Scroll event..:
private void vScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
float h = (100 - yOffTop - yOffBottom) / visibleCount;
ChartArea main = chart1.ChartAreas[0];
for (int i = 1; i < chart1.ChartAreas.Count; i++)
{
ChartArea ca = chart1.ChartAreas[i];
ca.Position = new ElementPosition(0, h * i - vScrollBar1.Value + mainExtra, 80, h);
ca.Visible = ca.Position.Y >= main.Position.Bottom ;
}
}
visibleCount controls how many ChartAreas are visible. In this example I have one for the year, fixed at the top and 12 more for the months..:
For this to work you need to set up the chart so that the chartareas are intialized in a suitable way.
I used this piece of code, do use your own code for stuff like right space (I left 20% for the legend) etc..:
int visibleCount = 5;
float yOffTop = 0; // no extra top space for a chart title
float yOffBottom = 0; // no extra bottom space either
float mainExtra = 6; // a few extra% for the main CA's axis labels
private void Init_button_Click(object sender, EventArgs e)
{
chart1.Series.Clear();
chart1.ChartAreas.Clear();
chart1.BackColor = Color.Snow;
float h = (100 - yOffTop - yOffBottom) / visibleCount;
for (int i = 0; i < 13; i++)
{
float yOff = i != 0 ? mainExtra : 0;
float yExtra = i == 0 ? mainExtra : 0;
ChartArea ca = chart1.ChartAreas.Add("ca" + i);
ca.Position = new ElementPosition(0, h * i + yOff , 80, h + yExtra);
ca.BackColor = Color.FromArgb(i * 20, 255 - i * 3, 255);
ca.AxisX.IntervalOffset = 1;
Series s = chart1.Series.Add("s" + i);
s.ChartArea = ca.Name;
for (int j = 1; j < 30; j++)
{
s.Points.AddXY(j, rnd.Next(100) - rnd.Next(20));
}
chart1.ChartAreas[0].BackColor = Color.Silver;
ca.AxisY.Title = i == 0 ? "Year" :
DateTimeFormatInfo.CurrentInfo.GetMonthName(i);
ca.AxisX.Enabled = (i == 0) ? AxisEnabled.True : AxisEnabled.False;
}
vScrollBar1.Minimum = 0;// (int)( h);
vScrollBar1.Maximum = (int)((chart1.ChartAreas.Count - visibleCount + 0.5f) * h
+ mainExtra );
}
I draw extra rectangles around each CA, just for testing; do ignore them!
Note: Working with those percentages is always a little tricky and may take some tweaking!

Charting - Set grid above the graph

I'm working with the System.Windows.Forms.DataVisualization.Charting library of C# in Visual Studio.
Creating the graphs themselves is no problem, however, since I'm using SeriesChartType.StackedArea100 for my serieses (which always fills the vertical graph space 100%), the grid (X & Y) is completely covered by the graphs.
However, I want the X-grid to be above the graphs, so it's easier to see which point belongs to what.
Am I missing something obvious here?
Gridlines are always drawn under the DataPoints.
One option is to make the Colors of the DataPoints semi-transparent.
Here is an example:
chart1.ApplyPaletteColors(); // necessary to access the original colors
if (checkBox1.Checked)
{
foreach (Series s in chart1.Series) s.Color = Color.FromArgb(192, s.Color);
}
You can raise alpha to 224 and still see the lines.
Or you could owner-draw GridLines in one of the xxxPaint events; but that of course is a little more complicated. OK, a lot more..
The drawing itself is regular GDI+ drawing with DrawLine calls in two loops.
But to get the loops and the coordinates right you need to :
Make sure you know/control the Minimum, Maximum & Interval for the axes. If they are not set but still on their auto-values you need to find a way to get at them.
know the Rectangle of the InnerPlotPosition in pixels(!). See here for two functions that will help you !
Here is an example:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (checkBox1.Checked) return;
ChartArea ca = chart1.ChartAreas[0];
RectangleF ipar = InnerPlotPositionClientRectangle(chart1, ca);
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
Color gc = ax.MajorGrid.LineColor;
Pen pen = new Pen(gc);
double ix = ax.Interval == 0 ? 1 : ax.Interval; // best make sure to set..
double iy = ay.Interval == 0 ? 50 : ay.Interval; // ..the intervals!
for (double vx = ax.Minimum; vx <= ax.Maximum; vx+= ix)
{
int x = (int)ax.ValueToPixelPosition(vx) + 1;
e.ChartGraphics.Graphics.DrawLine(pen, x, ipar.Top, x, ipar.Bottom);
}
for (double vy = ay.Minimum; vy <= ay.Maximum; vy += iy)
{
int y = (int)ay.ValueToPixelPosition(vy) + 1;
e.ChartGraphics.Graphics.DrawLine(pen, ipar.Left, y, ipar.Right, y);
}
pen.Dispose();
}
You should disable the GridLines and maybe even make the the Axes transparent:
chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisX.LineColor = Color.Transparent;
chart1.ChartAreas[0].AxisY.LineColor = Color.Transparent;

Text drawing "bold"

So I'm writing a program that generates a chart and saves it to PNG. From what I've read, if I were drawing to a window, it doesn't behave this way, but I'm not doing that.
The problem is that when I pass the brush I use to draw the label to another method to do the drawing, sometimes the text comes out looking bold. Also, the Y coordinate seems to have something to do with it, since it happens on every other row of the chart I'm drawing. And it's not a nice bold, either, it's like a gritty, messy looking bold. Some people have suggested changing the text rendering hint to antialiased, and it solves the "bolding" problem, but it doesn't look as nice as ClearType.
Note that none of this happens if I do everything in one method without passing the brush around, which is the most puzzling part of this. Any ideas?
Here's some of the code:
// Draw the timeline.
int y = 0;
bool shadeRow = true;
foreach (TimelineRow row in timeline.chart)
{
int rowHeight = row.height + TimelineRow.ROW_GAP;
if (shadeRow)
{
g.FillRectangle(shadeBrush, 0, y, chartWidth, rowHeight);
}
// Draw name labels, guidelines, and timeline row.
g.DrawString(row.name, labelFont, labelBrush, PADDING, (int)Math.Ceiling(y + (float)PADDING / 2));
for (int i = 0; i < row.years.Length; i++)
{
int blockX = labelsWidth + i * TimelineRow.DEFAULT_HEIGHT;
g.DrawLine(i % 5 == 0 ? yearGridDark : yearGridLight, blockX, y, blockX, y + rowHeight);
}
DrawRow(row, g, labelsWidth, y + 8);
y += rowHeight;
shadeRow = !shadeRow;
}
// Draw the year labels
int x = labelsWidth;
for (int year = timeline.startYear; year <= timeline.endYear; year += 5)
{
string yearString = Convert.ToString(year);
int width = (int)g.MeasureString(yearString, labelFont).Width;
g.DrawString(yearString, labelFont, labelBrush, x - width / 2, y);
x += 5 * TimelineRow.DEFAULT_HEIGHT;
}
I've had similar issues with drawing strings. In my cases, clearing the image FIRST with the background color has fixed the problem.
Wow, that actually did it.
Use Graphics.Clear() to set the initial color:
Bitamp bmp = new Bitmap(...);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.White);
// ... now draw with "g" ...

Connect 4 C# (How to draw the grid)

I've worked out most of the code and have several game classes. The one bit I'm stuck on at the moment, it how to draw the actual Connect 4 grid. Can anyone tell me what's wrong with this for loop? I get no errors but the grid doesn't appear. I'm using C#.
private void Drawgrid()
{
Brush b = Brushes.Black;
Pen p = Pens.Black;
for (int xCoor = XStart, col = 0; xCoor < XStart + ColMax * DiscSpace; xCoor += DiscSpace, col++)
{
// x coordinate beginning; while the x coordinate is smaller than the max column size, times it by
// the space between each disc and then add the x coord to the disc space in order to create a new circle.
for (int yCoor = YStart, row = RowMax - 1; yCoor < YStart + RowMax * DiscScale; yCoor += DiscScale, row--)
{
switch (Grid.State[row, col])
{
case GameGrid.Gridvalues.Red:
b = Brushes.Red;
break;
case GameGrid.Gridvalues.Yellow:
b = Brushes.Yellow;
break;
case GameGrid.Gridvalues.None:
b = Brushes.Aqua;
break;
}
}
MainDisplay.DrawEllipse(p, xCoor, yCoor, 50, 50);
MainDisplay.FillEllipse(b, xCoor, yCoor, 50, 50);
}
Invalidate();
}
The code in Drawgrid() needs to be executed when the window is redrawing itself.
The Invalidate() call tells the application that it needs to redraw the window contents (it triggers a redraw of your window). This code (with the exception of the Invalidate() call) should be in your overridden OnPaint() method, otherwise whatever gets drawn by this code will be immediately overwritten by the default drawing code in OnPaint() (which, by default, will probably draw a white background) when you make the Invalidate() call.
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
// (your painting code here...)
}

Categories