I have a matrix of bits 20x23.
I need to represent this matrix in a winform (GUI).
The idea is that the user will be able to change the content of specific cell by clicking the relevant button, that represent the specific cell in the matrix.
(When the user click a button, the relevant bit cell in the matrix is being inverted)
I have considered using GRID for this, but due to GUI (Design) issue, it is not possible to use it.
How can I create and manage 20x23 (=460) buttons effectively and keep it correlated to the real matrix ?
It is not that difficult, I would start with a method that will generate a button matrix for you. This matrix consists of Buttons, where the ID (ie. Tag) will correspond to the correct cellNumber (you might consider passing the coordinates as a Point instance as well, I will leave that up for you to decide).
So basically, it comes to this, where all the buttons are rendered on a panel (panel1):
...
#region Fields
//Dimensions for the matrix
private const int yDim = 20;
private const int xDim = 23;
#endregion
...
private void GenerateButtonMatrix()
{
Button[,] buttonMatrix = new Button[yDim, xDim];
InitializeMatrix(ref matrix); //Corresponds to the real matrix
int celNr = 1;
for (int y = 0; y < yDim; y++)
{
for (int x = 0; x < xDim; x++)
{
buttonMatrix[y,x] = new Button()
{
Width = Height = 20,
Text = matrix[y, x].ToString(),
Location = new Point( y * 20 + 10,
x * 20 + 10), // <-- You might want to tweak this
Parent = panel1,
};
buttonMatrix[y, x].Tag = celNr++;
buttonMatrix[y,x].Click += MatrixButtonClick;
}
}
}
As you can see, all 460 buttons have a custom EventHandler connected to the ClickEvent, called MatrixButtonClick(). This eventhandler will handle the ClickEvent and may determine on which button the user has clicked. By retrieving the tag again, you may calculate the correct coordinate which corresponds to the 'real' matrix.
private void MatrixButtonClick(object sender, EventArgs e)
{
if (sender is Button)
{
Button b = sender as Button;
//The tag contains the cellNr representing the cell in the real matrix
//To calculate the correct Y and X coordinate, use a division and modulo operation
//I'll leave that up to you :-)
.... Invert the real matrix cell value
}
}
I will not give away everything, since it is a nice practice for you to achieve :).
I would:
1) create an object with needed properties
2) fill a list and fill with values
3) iterate list creating buttons and assigning its click handler and button's name (for name something like button_rowindex_colindex)
4) inside click handler, assign value to object cell by detecting which button was clicked
Related
I'm adding buttons dynamically to a Form and am trying to lay the out side by side. I'm perfectly content with using the latest Button.Right as a starting point for the next button (with a margin left between of course), but the buttons have to adjust to fit the text.
So, what I'm doing is setting the AutoResize property to true and then storing the Right property, which however does not work because I guess the resizing doesn't happen until the button is drawn (I think). I tried Invalidate(), Refresh(), Update() and I think a couple more functions, and of course all together, but to no avail, I still get the old position and the next button starts beneath this one.
So the question is, after setting AutoResize to true on a Forms component, how do I force it to resize so I can grab the new Width/Right without waiting for the window to be redrawn?
Thanks in advance!
Note: If all else fails I'll do a rough approximation of the width of the buttons based on the string's length, so don't bother with something too fancy as a solution, it's not a requirement that it is perfect
You can use the Control's GetPreferredSize Method to obtain the final auto-sized dimensions. The Font property must either be explicitly set or the Control must be parented to a displayed control such that it can inherit the Font to use in the layout. In the following example, the control's Parent property is set so that it inherits the parent control's Font.
private Random rnd = new Random(1000);
private void button1_Click(object sender, EventArgs e)
{
const Int32 xDelta = 5; // the horizontal distance between the added Buttons
Int32 y = button1.Location.Y + 5 + button1.Height;
Int32 x = button1.Location.X;
Point loc = new Point(x, y);
this.SuspendLayout(); // this is Form that is the Parent container of the Buttons
for (Int32 i = 1; i <= 10; i++)
{
Button btn = new Button { Parent = this, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink };
btn.Text = new string('A', rnd.Next(1, 21));
btn.Location = loc;
Size sz = btn.GetPreferredSize(Size.Empty); // the size of btn based on Font and Text
loc.Offset(sz.Width + xDelta, 0);
}
this.ResumeLayout(true);
}
I use Chart to draw a graph with 2 lines. Now my aim is to set the LineColor of the MajorGrid of the second Y-axis to the color of the corresponding line. Here is my code:
public partial class Form1 : Form
{
List<double> values_1 = new List<double>();
List<double> values_2 = new List<double>();
public Form1()
{
InitializeComponent();
make_values();
for (int i = 0; i < values_1.Count; i++)
{
chart1.Series[0].Points.AddY(values_1[i]);
}
for (int i = 0; i < values_2.Count; i++)
{
chart1.Series[1].Points.AddY(values_2[i]);
}
// set the colour of grid to corresponding line
chart1.ChartAreas[0].AxisY2.MajorGrid.LineColor = chart1.Series[1].Color;
}
private void Form1_Load(object sender, EventArgs e)
{
}
public void make_values()
{
for (int i = 0; i < 600; i++)
{
values_1.Add(Math.Sin(i / 60.0));
values_2.Add(Math.Cos(i / 60.0));
}
}
}
Since the colours are chosen automatically for the 2 different series I though I could just grab the colour. But when debugging I see that the colour is (0,0,0):
So the grid colour does not change. But the colour of the second series is not (0,0,0) as can be seen when the window is loaded!:
If I force and set manually the colours of the 2 series before that. Everything works fine, and the grid gets the corresponding colour.
Does anyone know at which point in time I would have to grab the colour of the series to get the real value?
To access the Series Colors you need to call ApplyPaletteColors. This is necessary when you want to use them for other elements or when custom drawing. You should also call it again after changing the palette..
chart1.ApplyPaletteColors();
MSDN:
Remarks
When the Chart colors are automatically assigned at run time, there is
no way to know what the colors will be prior to the time when the
chart rendered; the Color property of an automatically assigned value
will return Empty at this time.
If you call the ApplyPaletteColors method, the colors for the series
and the data points will be set, which allows for programmatic access.
I'm working on a C# project using .NET 3.5 and Windows Forms. I need to design a decision step with multiple options that require a bit of explanatory text. For this, I want to have a set of RadioButtons to choose an option, followed by an additional Label each that contains the explanation.
I want to keep the label of the radio buttons and the label containing the explanatory text aligned - I've added red lines to the image to illustrate this. I could probably tweak some margins or other settings on the second label, but that would probably start to look weird as soon as the user chooses a different theme or changes some other settings. What is the canonical (and most robust) way to do this?
Your question boils down to two partial problems:
How large is the RadioButton (or the CheckBox when thinking ahead)..
How large is the gap between the glyph and the Text.
The first question is trivial:
Size s = RadioButtonRenderer.GetGlyphSize(graphics,
System.Windows.Forms.VisualStyles.RadioButtonState.CheckedNormal);
..using a suitable Graphics object. Note that I use the RadioButtonState CheckedNormal as I don't you want the Lables to align differently when the Buttons are checked or unchecked..
The second one is anything but trivial. The gap may or may not be constant and there is another gap to the left of the glyph! If I really wanted to get it right I guess I would write a routine to measure the text offset at startup:
public Form1()
{
InitializeComponent();
int gapRB = getXOffset(radioButton1);
int gapLB = getXOffset(label1);
label1.Left = radioButton1.Left + gapRB - gapLB;
}
Here is the measurement function. Note that is doesn't even use the Glyph measurement. Also note that it isn't enough to measure the text offset of the RadioButton. You also need to measure the offset of the Label!
int getXOffset(Control ctl)
{
int offset = -1;
string save = ctl.Text; Color saveC = ctl.ForeColor; Size saveSize = ctl.Size;
ContentAlignment saveCA = ContentAlignment.MiddleLeft;
if (ctl is Label)
{
saveCA = ((Label)ctl).TextAlign;
((Label)ctl).TextAlign = ContentAlignment.BottomLeft;
}
using (Bitmap bmp = new Bitmap(ctl.ClientSize.Width, ctl.ClientSize.Height))
using (Graphics G = ctl.CreateGraphics() )
{
ctl.Text = "_";
ctl.ForeColor = Color.Red;
ctl.DrawToBitmap(bmp, ctl.ClientRectangle);
int x = 0;
while (offset < 0 && x < bmp.Width - 1)
{
for (int y = bmp.Height-1; y > bmp.Height / 2; y--)
{
Color c = bmp.GetPixel(x, y);
if (c.R > 128 && c.G == 0) { offset = x; break; }
}
x++;
}
}
ctl.Text = save; ctl.ForeColor = saveC; ctl.Size = saveSize;
if (ctl is Label) { ((Label)ctl).TextAlign = saveCA; }
return offset;
}
Now the Texts do align pixel perfect..:
Note that I use two original controls from my form. Therefore much of the code is simply storing and restoring the properties I need to manipulate for the measurement; you can save a few lines by using two dummies.. Also note that I wrote the routine so that it can measure RadioButtons and Labels and probably CheckBoxes as well..
Is it worth it? You decide..!
PS: You could also owner-draw the RadioButton and the Label text in one.. this would have the interesting side-effect, that the whole text would be clickable..:
Here is a quick and dirty implementation of owner drawing a CheckBox: Prepare it by setting AutoSize = false and by adding the real text together with the extra text into the Tag, separated by a e.g. "§". Feel free to change this setup, maybe using the Label control..
I clear the Text to prevent it from drawing it and I decide on an offset. To measure it, you could use the GetGlyphSize from above.. Note how the DrawString method honors embedded '\n' characters.
The Tag contained this string:
A Rose is a Rose is a Rose..§A Rose is a rose is a rose is a rose is /
A rose is what Moses supposes his toes is / Couldn't be a lily or a
taffy daphi dilli / It's gotta be a rose cuz it rhymes with mose!
And I for the screenshot I actually used this line:
e.Graphics.DrawString(texts[1].Replace("/ ", "\n"), ...
Here is the Paint event:
private void checkBox1_Paint(object sender, PaintEventArgs e)
{
checkBox1.Text = "";
string[] texts = checkBox1.Tag.ToString().Split('§');
Font font1 = new Font(checkBox1.Font, FontStyle.Regular);
e.Graphics.DrawString(texts[0], checkBox1.Font, Brushes.Black, 25, 3);
if (texts.Length > 0)
{
SizeF s = e.Graphics.MeasureString(texts[1], checkBox1.Font, checkBox1.Width - 25);
checkBox1.Height = (int) s.Height + 30;
e.Graphics.DrawString(texts[1], font1, Brushes.Black,
new RectangleF(new PointF(25, 25), s));
}
}
The simplest out-of-the-box solution (it seems to me) would be to use 3 controls instead of 2: a radio button (with the text set to ""), a label (to go beside the radio button) and another label (to go below them). This would allow you easier configuration in designer, but (far more importantly) simpler run-time evaluation and adjustment, if necessary, to keep them in alignment should styles change.
I do understand that this takes away the benefit of clicking the label to select the radio button, but you could add that behavior in the label's Click event if you need it.
Alternatively, you could create a UserControl containing the text-free radio button and the label, and handle the behavior within that UserControl while exposing the label's location.
If you don't care about the radiobutton's text being bold, you could set it's label to a multiline string, and set CheckAlign to TopLeft:
radioButton2.CheckAlign = ContentAlignment.TopLeft;
radioButton2.Text = #"Radiobutton
Explanation text";
Don't know why I didn't think of this earlier, but the following approach seems to work:
Use a TableLayoutPanel with two columns that are set to adjust their width automatically.
Place all RadioButtons in the first column and set them to span both columns.
Place all Labels in the second column, setting all margins to 0.
Add a disabled, but visible (!) "spacer" RadioButton without text in an additional row at the end of the layout.
When displaying the form, convert the first column to a fixed size and hide the "spacer".
The key point seems to be that the "spacer" has to be visible initially - otherwise the column will get a size of 0.
This is my test form in the designer:
To change the layout, I used the following Load handler:
private void TestForm_Load(object sender, EventArgs e)
{
// find the column with the spacer and back up its width
int column = tableLayoutPanel.GetColumn(radioButtonSpacer);
int width = tableLayoutPanel.GetColumnWidths()[column];
// hide the spacer
radioButtonSpacer.Visible = false;
// set the column to the fixed width retrieved before
tableLayoutPanel.ColumnStyles[column].SizeType = SizeType.Absolute;
tableLayoutPanel.ColumnStyles[column].Width = width;
}
And this is the result at runtime:
You could add an invisible dummy label having the same text as the radiobutton. Then, get the length of that label and calculate the correct position of the explanation label.
labelDummy.Text = radioButton1.Text;
labelExplanation.Left = radioButton1.Right - labelDummy.Width;
However, this still appears to be some pixels off, even though I the label's margin to 0, maybe some additional tweaking can fix this. Here's a screenshot to show what I mean. The label's background is green to be able to see the extra margin.
I am making a program where you bassicly move from tile to tile in windows forms.
So in order to do that, I wanted to use panels each panel has a tag. To detect collision.
So I have an image of my map. and I divided into multiple tiles. However now I have to drag 900 tiles onto panels.
This isn't very effective in 2 ways. First loading 900 textures isn't really a smart idea. Also it would take ages. So i wanted to use a spritesheet or tilemap. But how would I do that in winforms. I believe I have seen some people use a grid view or whatever. However im not sure how to do what I want to do.
What would be the best solution?
Thanks in advance!
For any serious gaming project WinForms is not the best platform. Either WPF or XNA or Unity are able to deliver high performance use of DirectX.
But since you want to do it in Winforms here is a way to do it.
It creates a whopping number of 900 PictureBoxes and loads each with a fraction of an source image:
private void Form1_Load(object sender, EventArgs e)
{
int tileWidth = 30;
int tileHeight = 30;
int tileRows = 30;
int tileCols = 30;
using (Bitmap sourceBmp = new Bitmap("D:\\900x900.jpg"))
{
Size s = new Size(tileWidth, tileHeight);
Rectangle destRect = new Rectangle(Point.Empty, s);
for (int row = 0; row < tileRows; row++)
for (int col = 0; col < tileCols; col++)
{
PictureBox p = new PictureBox();
p.Size = s;
Point loc = new Point(tileWidth * col, tileHeight * row);
Rectangle srcRect = new Rectangle(loc, s);
Bitmap tile = new Bitmap(tileWidth, tileHeight);
Graphics G = Graphics.FromImage(tile);
G.DrawImage(sourceBmp, destRect, srcRect, GraphicsUnit.Pixel);
p.Image = tile;
p.Location = loc;
p.Tag = loc;
p.Name = String.Format("Col={0:00}-Row={1:00}", col, row);
// p.MouseDown += p_MouseDown;
// p.MouseUp += p_MouseUp;
// p.MouseMove += p_MouseMove;
this.Controls.Add(p);
}
}
}
When I tried it I was a bit worried about perfomance, but..
This takes under 1 second to load on my machine.
Starting the programm adds 10MB to VS memory usage. That is like nothing.
For a fun project this will do; for best performance one might use Panels but these will have to be filled and refilled in the Paint event. This solution saves you the hassle and since you don't change the tile picture all the time this works well enough.
Pleae note: I have added a Name and a Tag to each PictureBox, so you can later refer to it. These both contain info about the original position of the Picturebox. The Name looks like this: Col=23-Row=02 and the Tag is the original Location object.
Also: Dynamically added controls take a little extra to script since you can't create their method bodies in the designer. Instead you add them like above. In doing so Intellisense and the Tab key are your best friends..
I have added three event handlers for a few mouse events. When you uncomment them you will have to add the methods like e.g. this:
void p_MouseMove(object sender, MouseEventArgs e)
{
throw new NotImplementedException();
}
But maybe you want to use other events to play like Drag&Drop or keyboard events..
There are two ways to refer to these tiles. Maybe you want to try and/or use both of them: You can loop over the form's controls with a
foreach (Control ctl in this.Controls)
{ if (ctl is PictureBox ) this.Text = ((PictureBox)ctl).Name ; }
It tests for the right type and then casts to PictureBox. As an example it displays the name of the tile in the window title.
Or you can have a variable and set it in the MouseDown event:
PictureBox currentTile;
void p_MouseDown(object sender, MouseEventArgs e)
{
currentTile = (PictureBox ) sender;
}
I am creating a dynamic hexgrid, 8 columns of 10 hexes, with an ellipse centered in random hexes.
I had originally thought the event handlers were not loading. It now appears they are loading, but are reacting very slowly. As I mouse over the generated grid after a minute or two, the hexes start randomly filling and the tooltips start showing (but not until the event handler for that hex responds). It actually takes several minutes for the last hex to respond to the mouseover event.
private void CreateHexGrid(int columns, int rows, double length)
{
//I create a jagged array of points,
//hexpoint[# of columns, # of rows, 6 points]
//the base (0,0) hex is generated point by point
//mathematically based on the length of one side
//then each subsequent hex is mathematically
//created based on the points of the previous hex.
//Finally, I instantiate each Polygon hex and fill
//its points collection using the array.
PointCollection points = new PointCollection();
SolidColorBrush blackBrush = new SolidColorBrush(Colors.Black);
SolidColorBrush clearBrush = new SolidColorBrush(Colors.Transparent);
Polygon hex = new Polygon();
hex.Stroke = blackBrush;
hex.StrokeThickness = 1;
hex.Name = "Hex" + column.ToString() + row.ToString();
for (int point = 0; point < 6; point++)
{
points.Add(HexPoint[column, row, point]);
}
hex.Points = points;
ToolTipService.SetToolTip(hex, hex.Name);
hex.MouseEnter += new MouseEventHandler(hex_MouseEnter);
cnvsHexGrid.Children.Add(hex);
//....
}
Currently I have the handler simply trying to change the fill color of the polygon/ellipse to test. I would eventually like to generate an interactive pop-up window if a hex is clicked on.
void hex_MouseEnter(object sender, MouseEventArgs e)
{
var hex = sender as Polygon;
SolidColorBrush blueFill = new SolidColorBrush(Colors.Blue);
hex.Fill = blueFill;
}
What am I doing wrong to cause such slow, slow, slow reaction?
Adding a handler in the Load_Main seemed to help clear this up.