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.
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'm currently trying to create a little plot interactive editor, using WPF.
On maximized window the plot dragging with mouse is not responsive enough because of the plot grid.
I got a path for my plot grid lying inside a Canvas control (render transform just shifts it to the bottom of the canvas)
<Path Name="VisualGrid" RenderTransform="{StaticResource PlotTechnicalAdjust}" Style="{DynamicResource ResourceKey=GridStyle}" Panel.ZIndex="1"/>
Here is how grid is created; _curState has actual camera "viewport" metadata
if (_curState.Changes.ScaleStepXChanged)
{
foreach (TextBlock item in _xLabels)
{
DeleteLabel(item);
}
_xLabels.Clear();
double i = _curState.LeftEdgeLine;
_gridGeom.Children[(int)GridGeomIndexes.VerticalLines] = new GeometryGroup { Transform = _verticalLinesShift};
var verticalLines =(GeometryGroup)_gridGeom.Children[(int)GridGeomIndexes.VerticalLines];
while (i <= _curState.RightEdgeLine * (1.001))
{
verticalLines.Children.Add(new LineGeometry(new Point(i * _plotParameters.PixelsPerOneX, 0),
new Point(i * _plotParameters.PixelsPerOneX,
-_wnd.ContainerGeneral.Height)));
_xLabels.Add(CreateLabel(i, Axis.X));
i += _curState.CurrentScaleStepX;
}
_curState.Changes.ScaleStepXChanged = false;
}
if (_curState.Changes.ScaleStepYChanged)
{
foreach (TextBlock item in _yLabels)
{
DeleteLabel(item);
}
_yLabels.Clear();
double i = _curState.BottomEdgeLine;
_gridGeom.Children[(int)GridGeomIndexes.HorizontalLines] = new GeometryGroup { Transform = _horizontalLinesShift};
var horizontalLines = (GeometryGroup)_gridGeom.Children[(int)GridGeomIndexes.HorizontalLines];
while (i <= _curState.TopEdgeLine * (1.001))
{
horizontalLines.Children.Add(new LineGeometry(new Point(0, -i * _plotParameters.PixelsPerOneY),
new Point(_wnd.ContainerGeneral.Width,
-i * _plotParameters.PixelsPerOneY)));
_yLabels.Add(CreateLabel(i, Axis.Y));
i += _curState.CurrentScaleStepY;
}
_curState.Changes.ScaleStepYChanged = false;
}
Where Transforms are composition of TranslateTransform and ScaleTransform (for vertical lines I only use X components and only Y for horizontal lines).
After beeing created those GeometryGroups are only edited if a new line apears into camera or an existing line exits viewable space. Grid is only recreated when axis graduations have to be changed after zooming.
I have a dragging option implemented like this:
private Point _cursorOldPos = new Point();
private void OnDragPlotMouseMove(object sender, MouseEventArgs e)
{
if (e.Handled)
return;
Point cursorNewPos = e.GetPosition(ContainerGeneral);
_plotView.TranslateShiftX.X += cursorNewPos.X - _cursorOldPos.X;
_plotView.TranslateShiftY.Y += cursorNewPos.Y - _cursorOldPos.Y;
_cursorOldPos = cursorNewPos;
e.Handled = true;
}
This works perfectly smooth with a small window (1200x400 units) for a large amount of points (like 100+).
But for a large window (fullscreen 1920x1080) it happens pretty jittery even without any data-point controls on canvas.
The strange moment is that lags don't appear when I order my GridGenerator to keep around 100+ lines for small window and drag performance suffers when I got less than 50 lines on maximezed. It makes me think that it might somehow depend not on a number of elements inside a geometry, but on their linear size.
I suppose I should mention that OnSizeChanged I adjust the ContainerGeneral canvas' height and width and simply re-create the grid.
Checked the number of lines stored in runtime to make sure I don't have any extras. Tried using Image with DrawingVisual instead of Path. Nothing helped.
Appearances for clearer understanding
It was all about stroke dashes and WPF's unhealthy desire to count them all while getting hit test bounds for DrawingContext.
The related topic is Why does use of pens with dash patterns cause huge (!) performance degredation in WPF custom 2D drawing?
I have a TextBox. I want the user to be able to click inside it; when he does, a red marker line should appear between the two characters closest to the click position, and remain here. The user could do that multiple times.
I'm new to WPF, but I guess, as in Winforms, I will have to hack a messy OnRender method. So far, it's okay.
What I'd really like to know is: how to get the two closest characters to the click position?
I was about to do a pixel check but it seems pretty heavy.
You could try:
textBox.GetCharacterIndexFromPoint(point, true);
Well I found what I wanted to do, and it's way simpler than I thought (even though FINDING the actual way was a pain).
Just add this handler to your textbox's SelectionChanged event:
private void placeMarker(object sender, RoutedEventArgs e)
{
// txt_mark is your TextBox
int index = txt_mark.CaretIndex;
// I don't want the user to be able to place a marker at index = 0 or after the last character
if (txt_mark.Text.Length > first && first > 0)
{
Rect rect = txt_mark.GetRectFromCharacterIndex(first);
Line line = new Line();
// If by any chance, your textbox is in a scroll view,
// use the scroll view's margin instead
line.X1 = line.X2 = rect.Location.X + txt_mark.Margin.Left;
line.Y1 = rect.Location.Y + txt_mark.Margin.Top;
line.Y2 = rect.Bottom + txt_mark.Margin.Top;
line.HorizontalAlignment = HorizontalAlignment.Left;
line.VerticalAlignment = VerticalAlignment.Top;
line.StrokeThickness = 1;
line.Stroke = Brushes.Red;
// grid1 or any panel you have
grid1.Children.Add(line);
// set the grid position of the line to txt_mark's (or the scrollview's if there is one)
Grid.SetRow(line, Grid.GetRow(txt_mark));
Grid.SetColumn(line, Grid.GetColumn(txt_mark));
}
}
You might want to add some kerning or simply increase the font size for the markers not to ruin readability.
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 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