I have created application where user can drag and drop the controls during run-time. I use my ss function to align the controls in a table like fashion using 2D-array. I want the application to show/highlight the position where the control will be placed when its brought near certain coordinate.
struct IconPanel
{
public int left;
public int top;
}
static void ss(Control control)
{
int row, col, nearestCol = 0, nearestRow = 0, rowDist = 100, diff, colDist = 100;
for (row = 0; row < iconPanels.GetLength(0); row++)
{
for (col = 0; col < iconPanels.GetLength(1); col++)
{
diff = Math.Abs(control.Left - iconPanels[row, col].left);
if (diff < colDist)
{
colDist = diff;
nearestCol = col;
}
diff = Math.Abs(control.Top - iconPanels[row, col].top);
if (diff < rowDist)
{
rowDist = diff;
nearestRow = row;
}
}
}
control.Left = iconPanels[nearestRow, nearestCol].left;
control.Top = iconPanels[nearestRow, nearestCol].top;
}
You probably don't want these position markers to persist, so this is one of the rare cases where I would use
using ( Graphics G = theParentContainer.CreateGraphics())
foreach(Rectangle rect in yourPositions) G.DrawRectangle(Pens.Red, rect);
Clean them up after the drop or after leaving the control etc.. by calling theParentContainer.Invalidate();
This assumes that you know the possible position and can create a
List<Rectangle> yourPositions = new List<Rectangle>();
holding them.
As we all know ;-) graphics created with a Graphics object that was instantiated with Control.CeateGraphics won't persist and will dissappear with the next Paint event. But for such an interactive helper graphics this is perfect. Other examples are rubberband-drawing or cursor tracking..
On control that you would like to be highlited subscribe to Paint event.
private void highlightControl_Paint(object sender, System.Windows.Forms.PaintEventArgs e) {
if(mouse is over or other control is over) {
Graphics g = e.Graphics;
g.FillRectangle(Brushes.Red, 0, 0, this.Width, this.Height);
}
}
Related
I need to know how to fill empty rows in datagridview's rest of grayed area, after datagridview has bind the data. Anyone have a simple method for this please comment here.
Adding Rows to a DataGridView to fill up the space has a number of problems:
The most obvious is that you can't add them as your DGV is DataBound. So you would have to add rows to your DataSource. Which is not really nice.
Less obvious: The rows would not only look like rows, they would also be real rows and behave like real rows i.e. be clickable, selectable etc.. This is confusing for users even if you prevent editing it will still invite interactions that are not really plausible.
Finally: You would still have to take care of the grey areas to the right, at least if there is some or if the users can resize columns.
Here is what I suggest doing instead:
To make the grey area look unobtrusive simply set the DataGridView's BackColor to the color of the normal cells:
yourDGV.BackgroundColor = yourDGV.DefaultCellStyle.BackColor;
Setting the background color to transparent or the same color as your form would be a good thing, but if you don't want to do that, you'll have to create a custom datagridview for that. You should probably check this Thread
Create your Custom DataGridView, I've tested it works kind of glitchy for me on other AutoSizeColumnsMode, so set it to DataGridViewAutoSizeColumnsMode.Fill;, but anyway:
public class GridLineDataGridView : DataGridView
{
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
int rowHeight = this.RowTemplate.Height;
int h = this.ColumnHeadersHeight + rowHeight * this.RowCount;
int imgWidth = this.Width - 2;
Rectangle rFrame = new Rectangle(0, 0, imgWidth, rowHeight);
Rectangle rFill = new Rectangle(1, 1, imgWidth - 2, rowHeight);
Rectangle rowHeader = new Rectangle(2, 2, this.RowHeadersWidth - 3, rowHeight);
Pen pen = new Pen(this.GridColor, 1);
Bitmap rowImg = new Bitmap(imgWidth, rowHeight);
Graphics g = Graphics.FromImage(rowImg);
g.DrawRectangle(pen, rFrame);
g.FillRectangle(new SolidBrush(this.DefaultCellStyle.BackColor), rFill);
g.FillRectangle(new SolidBrush(this.RowHeadersDefaultCellStyle.BackColor), rowHeader);
Bitmap rowImgAAlternative = rowImg.Clone() as Bitmap;
Graphics g2 = Graphics.FromImage(rowImgAAlternative);
rFill.X += this.RowHeadersWidth - 1;
g2.FillRectangle(new SolidBrush(this.AlternatingRowsDefaultCellStyle.BackColor), rFill);
int w = this.RowHeadersWidth - 1;
for (int j = 0; j < this.ColumnCount; j++)
{
g.DrawLine(pen, new Point(w, 0), new Point(w, rowHeight));
g2.DrawLine(pen, new Point(w, 0), new Point(w, rowHeight));
w += this.Columns[j].Width;
}
int loop = (this.Height - h) / rowHeight;
for (int j = 0; j < loop + 1; j++)
{
int index = this.RowCount + j;
if (index % 2 == 0)
{
e.Graphics.DrawImage(rowImg, 1, h + j * rowHeight);
}
else
{
e.Graphics.DrawImage(rowImgAAlternative, 1, h + j * rowHeight);
}
}
}
}
change your private DataGridView dataGridView; to private GridLineDataGridView dataGridView;
Im in winforms and trying to make a tileengine in it for school reasons. I have a trouble with drawing the tileset in the Panel. It's instead drawing in the top left of the Form and behind the Panel.
This is the code:
private void gfxPanel_Paint(object sender, PaintEventArgs e)
{
using (Bitmap sourceBmp = new Bitmap("../../assets/art/Tileset5.png"))
{
Size s = new Size(level.TileWidth, level.TileHeight);
Rectangle destRect = new Rectangle(Point.Empty, s);
for (int row = 0; row <= level.MapHeight; row++)
for (int col = 0; col < level.MapWidth; col++)
{
PictureBox p = new PictureBox();
p.Size = s;
Point loc = new Point(level.TileWidth * col, level.TileHeight * row);
Rectangle srcRect = new Rectangle(loc, s);
Bitmap tile = new Bitmap(level.TileWidth, level.TileHeight);
Graphics G = Graphics.FromImage(tile);
G.DrawImage(sourceBmp, destRect, srcRect, GraphicsUnit.Pixel);
p.Image = tile;
p.Location = loc;
this.Controls.Add(p);
}
}
}
Whats going on here and what am I doing wrong?
You are adding the picture boxes to the form (this). Add them to the panel instead:
gfxPanel.Controls.Add(p);
Note: You are adding the controls in the paint event, which means that you will be adding multiple sets of the same control. The paint even is called whenever the control needs to be redrawn on the screen. You should only add the controls once, perhaps in the form load event. If you want to use the paint event you should use the graphics object that is send in the event arguments to draw directly on the screen, not add controls that contain what you want to draw.
If you really just want to draw the tiles without interaction you can change the Paint event like this:
private void gfxPanel_Paint(object sender, PaintEventArgs e)
{
using (Bitmap sourceBmp = new Bitmap("../../assets/art/Tileset5.png"))
{
Size s = new Size(levelTile.Width, levelTile.Height);
for (int row = 0; row <= levelMap.Height; row++)
for (int col = 0; col < levelMap.Width; col++)
{
Rectangle destRect = new Rectangle(
col * levelTile.Width, row * levelTile.Height,
levelTile.Width, levelTile.Height);
Point loc = new Point(levelTile.Width * col, levelTile.Height * row);
Rectangle srcRect = new Rectangle(loc, s);
e.Graphics.DrawImage(sourceBmp, destRect, srcRect, GraphicsUnit.Pixel);
}
}
}
Note that I didn't fully rewrite the code. If the tiles are meant to be drawn in the same order and size as they appear in the TileSet the whole TileSet could be drawn directly..
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 need to display a persistent grid in a TabPage. My problems would be instantly solved if I could draw to the entire non-visible portion of the TabPage and prevent graphics from being erased when scrolling.
The only other solution I can think of is tracking the scroll position in the tab and basing the grid drawn from that.
To get this to draw in the first place, I had to create an EventHandler for TabPage.Paint.
//Code removed
This method draws vertical and horizontal lines to create a grid within the visible tab, but it continues to draw whenever a Paint event occurs (i.e. scrolling), so it creates overlapping lines and aren't aligned to anything but the size of the current visible area of the tab.
Maybe something like this will work for you:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
const int gridSpacing = 20;
const int lineThickness = 1;
Bitmap bmp = new Bitmap(gridSpacing, gridSpacing);
using (System.Drawing.Pen pen = new System.Drawing.Pen(Color.Blue, lineThickness))
{
using (Graphics G = Graphics.FromImage(bmp))
{
G.Clear(this.BackColor);
G.DrawLine(pen, 0, bmp.Height - pen.Width, bmp.Width, bmp.Height - pen.Width); // horizontal
G.DrawLine(pen, bmp.Width - pen.Width, 0, bmp.Width - pen.Width, bmp.Height); // vertical
}
}
foreach (TabPage TP in tabControl1.TabPages)
{
TP.BackgroundImage = bmp;
TP.BackgroundImageLayout = ImageLayout.Tile;
}
}
}
Keep in mind that this solution is just pseudo. You also have to respond to scrolling.
void form_draw()
{
spacingX = offsetX % scale * -1;
spacingY = offsetY % scale * -1;
if (form.HorizontalPosition != lastXPosition && form.VerticalPosition == lastYPosition)
lastStartX += spacingX;
else if (tab.HorizontalScroll.Value == lastXPosition && form.VerticalPosition != lastYPosition)
lastStartY += spacingY;
lastYPosition = form.VerticalPosition;
lastXPosition = form.HorizontalPosition;
for (int i = lastStartY; i < formHeight; i += scale)
form.draw(0, i, formWidth, i);
for (int i = lastStartX; i < formWidth; i += scale)
form.draw(i, 0, i, formWidth);
}