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);
}
Related
I have a windows forms project written in C#. The main form has a TabControl on it and there is a requirement for one of the users to be able to print one of the TabPages. The form is very long and I use a vertical scroll bar. The whole of the form needs to be able to be printed.
I have tried using the DrawToBitmap method to convert to a bitmap first, but this will only include the portion of the form that the user can see. Some other solutions I have tried involve screen capturing, which has the same issue.
How can I print out, or get an image, of the whole of the tab page, including the parts the user only sees when they scroll down?
This is rather simple for any control including TabControls and TabPages but not Forms.
All you need to do is enlarge the relevant controls enough to show all their content. (They don't have to be actually visible on screen.)
Here is an example:
tabControl1.Height = 10080;
tabPage2.Height = 10050;
dataGridView1.Height = 10000;
dataGridView1.Rows.Add(3000);
for (int i = 0; i < dataGridView1.Rows.Count; i++) dataGridView1[0, i].Value = i;
using (Bitmap bmp = new Bitmap(tabControl1.Width , tabControl1.Height ))
{
tabControl1.DrawToBitmap(bmp, tabControl1.ClientRectangle);
bmp.Save("D:\\xxxx.png", ImageFormat.Png);
}
This saves the full content of the DataGridView, the TabPage and the TabControl..
Note: that this will not work with forms, which can't much exceed the screen dimensions..
Update: Here is code that saves a form with vertical scrolling by patching several bitmaps together. It can, of course be expanded to include horizontal scrolling as well. I have coded a similar solution for larger Panels here.
static void saveLargeForm(Form form, string fileName)
{
// yes it may take a while
form.Cursor = Cursors.WaitCursor;
// allocate target bitmap and a buffer bitmap
Bitmap target = new Bitmap(form.DisplayRectangle.Width, form.DisplayRectangle.Height);
Bitmap buffer = new Bitmap(form.Width, form.Height);
// the vertical pointer
int y = 0;
var vsc = form.VerticalScroll;
vsc.Value = 0;
form.AutoScrollPosition = new Point(0, 0);
// the scroll amount
int l = vsc.LargeChange;
Rectangle srcRect = ClientBounds(form);
Rectangle destRect = Rectangle.Empty;
bool done = false;
// we'll draw onto the large bitmap with G
using (Graphics G = Graphics.FromImage(target))
{
while (!done)
{
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
int v = vsc.Value;
vsc.Value = vsc.Value + l;
form.AutoScrollPosition = new Point(form.AutoScrollPosition.X, vsc.Value + l);
int delta = vsc.Value - v;
done = delta < l;
y += delta;
}
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
}
// write result to disc and clean up
target.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
target.Dispose();
buffer.Dispose();
GC.Collect(); // not sure why, but it helped
form.Cursor = Cursors.Default;
}
It makes use of a helper function to determine the the net size of the virtual client rectangle, ie excluding borders, title and scrollbar:
static Rectangle ClientBounds(Form f)
{
Rectangle rc = f.ClientRectangle;
Rectangle rb = f.Bounds;
int sw = SystemInformation.VerticalScrollBarWidth;
var vsc = f.VerticalScroll;
int bw = (rb.Width - rc.Width - (vsc.Visible ? sw : 0) ) / 2;
int th = (rb.Height - rc.Height) - bw * 2;
return new Rectangle(bw, th + bw, rc.Width, rc.Height );
}
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);
}
}
I have a windows forms project written in C#. The main form has a TabControl on it and there is a requirement for one of the users to be able to print one of the TabPages. The form is very long and I use a vertical scroll bar. The whole of the form needs to be able to be printed.
I have tried using the DrawToBitmap method to convert to a bitmap first, but this will only include the portion of the form that the user can see. Some other solutions I have tried involve screen capturing, which has the same issue.
How can I print out, or get an image, of the whole of the tab page, including the parts the user only sees when they scroll down?
This is rather simple for any control including TabControls and TabPages but not Forms.
All you need to do is enlarge the relevant controls enough to show all their content. (They don't have to be actually visible on screen.)
Here is an example:
tabControl1.Height = 10080;
tabPage2.Height = 10050;
dataGridView1.Height = 10000;
dataGridView1.Rows.Add(3000);
for (int i = 0; i < dataGridView1.Rows.Count; i++) dataGridView1[0, i].Value = i;
using (Bitmap bmp = new Bitmap(tabControl1.Width , tabControl1.Height ))
{
tabControl1.DrawToBitmap(bmp, tabControl1.ClientRectangle);
bmp.Save("D:\\xxxx.png", ImageFormat.Png);
}
This saves the full content of the DataGridView, the TabPage and the TabControl..
Note: that this will not work with forms, which can't much exceed the screen dimensions..
Update: Here is code that saves a form with vertical scrolling by patching several bitmaps together. It can, of course be expanded to include horizontal scrolling as well. I have coded a similar solution for larger Panels here.
static void saveLargeForm(Form form, string fileName)
{
// yes it may take a while
form.Cursor = Cursors.WaitCursor;
// allocate target bitmap and a buffer bitmap
Bitmap target = new Bitmap(form.DisplayRectangle.Width, form.DisplayRectangle.Height);
Bitmap buffer = new Bitmap(form.Width, form.Height);
// the vertical pointer
int y = 0;
var vsc = form.VerticalScroll;
vsc.Value = 0;
form.AutoScrollPosition = new Point(0, 0);
// the scroll amount
int l = vsc.LargeChange;
Rectangle srcRect = ClientBounds(form);
Rectangle destRect = Rectangle.Empty;
bool done = false;
// we'll draw onto the large bitmap with G
using (Graphics G = Graphics.FromImage(target))
{
while (!done)
{
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
int v = vsc.Value;
vsc.Value = vsc.Value + l;
form.AutoScrollPosition = new Point(form.AutoScrollPosition.X, vsc.Value + l);
int delta = vsc.Value - v;
done = delta < l;
y += delta;
}
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
}
// write result to disc and clean up
target.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
target.Dispose();
buffer.Dispose();
GC.Collect(); // not sure why, but it helped
form.Cursor = Cursors.Default;
}
It makes use of a helper function to determine the the net size of the virtual client rectangle, ie excluding borders, title and scrollbar:
static Rectangle ClientBounds(Form f)
{
Rectangle rc = f.ClientRectangle;
Rectangle rb = f.Bounds;
int sw = SystemInformation.VerticalScrollBarWidth;
var vsc = f.VerticalScroll;
int bw = (rb.Width - rc.Width - (vsc.Visible ? sw : 0) ) / 2;
int th = (rb.Height - rc.Height) - bw * 2;
return new Rectangle(bw, th + bw, rc.Width, rc.Height );
}
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
I am designing a simple picture viewer with ability to do some basic image processing. At the moment I have the problem of keeping the PictureBox centered inside a TabPage all the time as well as keeping the picturebox width and size same as the picture its showing. So far I had no success.
I have the following code that I call in form constructor to position it in center. it works the first time to center the picturebox:
private void SetPictureBoxOriginalSizeAndLocation(bool makeImageNull = false, DockStyle dockStyle = DockStyle.None)
{
if (makeImageNull) picBoxView.Image = null;
picBoxView.Dock = dockStyle;
var xPoint = tabImageView.Location.X + ((splitContainer.Panel2.Width / 2) / 2);
var yPoint = tabImageView.Location.Y;
var width = tabImageView.Width / 2;
var height = (tabImageView.Height / 2) - toolStripImageView.Height;
if (picBoxView.Image == null) return;
//Resize image according to width
picBoxView.Image = ImageMethods.ResizeImage(picBoxView.Image.Tag.ToString(), width, height, false);
picBoxView.Location = new Point(xPoint, yPoint);
picBoxView.Width = width;
picBoxView.Height = height;
}
But it does not resize the picturebox to its image (you can see the black part that is back color for the picturebox control):
The problem is getting worse as soon as I resize the form, the picturebox position will goes to top:
I call the code above in form's resize event as well, no idea why it works when application starts. Would be nice if someone can tell me what properties I should take care of to achieve a nicely centered picturebox which always is as big as its image.
It's pretty easy if you just set the Anchor style to none:
picBoxView = new PictureBox();
picBoxView.SizeMode = PictureBoxSizeMode.AutoSize;
picBoxView.Anchor = AnchorStyles.None;
tabImageView.Controls.Add(picBoxView);
CenterPictureBox(picBoxView, myImage);
Then just center the PictureBox initially whenever you change the image of the PictureBox:
private void CenterPictureBox(PictureBox picBox, Bitmap picImage) {
picBox.Image = picImage;
picBox.Location = new Point((picBox.Parent.ClientSize.Width / 2) - (picImage.Width / 2),
(picBox.Parent.ClientSize.Height / 2) - (picImage.Height / 2));
picBox.Refresh();
}
Having the Anchor = None will center the PictureBox control for you whenever the parent container gets resized because it "isn't" anchored to the default Left and Top locations.
Givien a Form with TabControl, which has Dock set to Fill, below will keep your PictureBox in the centre. It also sets PictureBox size to Bitmap size:
public partial class Form1 : Form
{
Bitmap b = new Bitmap(320, 200);
public Form1()
{
InitializeComponent();
CenterTheBox();
}
private void Form1_Resize(object sender, EventArgs e)
{
CenterTheBox();
}
void CenterTheBox()
{
pictureBox1.Size = b.Size;
var left = (tabPage1.ClientRectangle.Width - pictureBox1.ClientRectangle.Width) / 2;
var top = (tabPage1.ClientRectangle.Height - pictureBox1.ClientRectangle.Height) / 2;
pictureBox1.Location = new Point(tabPage1.ClientRectangle.Location.X + left, tabPage1.ClientRectangle.Location.Y + top);
}
}
I believe your problem lies here
var xPoint = tabImageView.Location.X + ((splitContainer.Panel2.Width / 2) / 2);
var yPoint = tabImageView.Location.Y;
var width = tabImageView.Width / 2;
var height = (tabImageView.Height / 2) - toolStripImageView.Height;
ypoint is alwways set to tabImageView Y, althought it should be set to
tabImageView.Location.Y + (tabImageView.Size.Height - picBoxView.Size.Height)/2
should be almost the same with xPoint
tabImageView.Location.X + (tabImageView.Size.Width - picBoxView.Size.Width)/2
and
width = picBoxView.Image.Width;
height = picBoxView.Image.Height;