Winforms autoscroll getting lost when controls resize - c#

I have a user control that display a passenger car seating layout.
It simple draws several "SeatControl" in a matrix like fashion, depending on the passenger car size.
For better viewing, the main control resizes the "SeatControl" for fitting all space available, that means that the SeatControls will get bigger or smaller depending on the space available.
This works perfect.
But, when the client area gets too small, we avoid shrinking the controls too much, or they became deformed and impossible to read.
In this case we turn on auto scroll so the user can scroll around to see the entire layout.
But, if we start with a small screen (with scroll bar), maximize it (the scroll bar will hide and the seat controls increase size) and restore the window size back (scroll will come back and seat controls will shrink to minimum size), the scroll gets lost.
To make it clear, the same operation in images:
Maximize the window (only partial screen show to avoid a big image):
And restore it back (notice the scroll bar and the layout position on client area):
This resize is controlled by the code below:
private void FixSizes()
{
if (mModel == null)
return;
this.SuspendLayout();
Size clientSize = this.ClientSize;
Size minimumSize = new Size(SeatUserControl.MinimumDescentSize.Width, SeatUserControl.MinimumDescentSize.Height);
//Here we try to find the best size for the seat user control to fit all the client area
Size controlSize = new Size(
Math.Max(clientSize.Width / mModel.Length, minimumSize.Width),
Math.Max(clientSize.Height / mModel.Width, minimumSize.Height)
);
AutoScrollMinSize = new Size(controlSize.Width * mModel.Length, controlSize.Height * mModel.Width);
this.SetDisplayRectLocation(0, 0);
for (int row = 0; row < mModel.Width; ++row)
{
for (int col = 0; col < mModel.Length; ++col)
{
Control control = this.Controls[(row * mModel.Length) + col];
control.Location = new Point(col * controlSize.Width, row * controlSize.Height);
control.Size = controlSize;
}
}
this.ResumeLayout();
}
And this method is simple called by the OnClientSizeChanged method:
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
this.FixSizes();
}
I was able to determine that if I keep the SeatControl on a fixed size, the problem goes away, but the output is not so good, as we prefer the SeatControl to use the maximum space available.
So it looks like I am missing or forgetting to do something with autoscroll settings so it does not get lost. Any ideas?

Solution from comments:
Assuming you are using a parent container of some sort, such as a panel, or something that would restrict the maximized size of the SeatControl, use dock "Fill" (the center one). This will auto adjust the scrollbar for you in regards to the control.
For future readers, the result would look like this when using a Dock: Fill property:
Note the scrollbar that was auto-generated due the gridview control being larger than its parent container.

Related

Not being able to add large number of controls in a panel

I have a scenario in which I want to display numerous images in a panel using only horizontal scroll.
To enable only Horizontal Scroll only, I used the following code in the constructor
public Form()
{
InitializeComponent();
panelImageGallery.AutoScroll = true;
panelImageGallery.VerticalScroll.Enabled = false;
panelImageGallery.VerticalScroll.Visible = false;
}
And then to display and use images in the panel I wrote the following lines of code
int increment = 0;
lblCount.Text = "0/" + files.Length;
for (int i=0;i<files.Length;i++)
{
PanelPictureBox box = new PanelPictureBox();
box.IMAGE= files[i];
box.Location = new Point(panelImageGallery.Location.X + increment, panelImageGallery.Location.Y+10);
box.INDEX = i+1;
panelImageGallery.Controls.Add(box);
increment += 300;
}
Following is the result, and it can be seen that although, I have 350 images but NOT all images are in the panel as there are only 109 images and even both the horizontal and vertical scrolls are there.
Also, when I scrolled the panel all the way to the end then there are some display issues at the end too as the last image gets joined with the second last image.
Another thing that I observed was that when I increased the margin between the images, then fewer and fewer images got displayed inside the panel. So for example, when I set the increment to 500 then only 66 images got displayed in the panel instead of all the 350 images. My feeling is that there could be restriction on the maximum size of the panel, So what is actually happening here and how to resolve this issue ?
This is a limitation because of some of the Windows messages, for example as mentioned in documentations:
Note that the WM_HSCROLL message carries only 16 bits of scroll box
position data. Thus, applications that rely solely on WM_HSCROLL (and
WM_VSCROLL) for scroll position data have a practical maximum position
value of 65,535.
In general, it's not a good idea to try to host too many controls on the form.
In your case, you may want to try controls which performs automatic layout, like FlowLayoutPanel, TableLayoutPanel, Docking into the left of a panel or decrease the margin between your controls.
Or as a proper fix, you can rely on ListView control which support virtual mode, or implement a custom drawn control, or use paging to show a limited number of controls in each page.

How to place a label or a button exactly in the middle of the Form?

I can't find tools or properties to place a label or a button exactly in the middle of the Form. For example, on the X axis. VS 2015.
Design time :
In my VisualStudio2010 I have these 2 buttons to center horizontally and vertically:
Its located in the toolbar "Layout". If it isn't, you can add them by clicking the small button to the right. It is also in the Format menu.
To keep centered at Runtime: Turn off all anchoring.
Note:This will keep the control at its relative position as long as it doesn't change it Size. If it does, like autosize Labels are prone to, you will have to code the Resize event. Examples are here
For controls that may change in size, you need to catch the Resize event.
In my case I have a Panel, representing a page, inside another Panel which is the workspace. The workspace is set to autoscroll. In this scenario, it's important that the control is only centered when smaller than the container.
Whenever the form changes size or when I change the content, I call this function:
private void resetPagePos()
{
int wWS = pnlWorkspace.Width;
int hWS = pnlWorkspace.Height;
int wPage = pnlPage.Width;
int hPage = pnlPage.Height;
pnlPage.Location = new Point(Math.Max(0, (wWS - wPage) / 2), pnlPage.Top = Math.Max(0, (hWS - hPage) / 2));
}
The use of Math.Max(0, ...) makes sure that if the item doesn't fit, and the scrollbars are activates, then our page scrolls correctly. If the Left or Top are set to a negative number, you would get unwanted side-effects.

How to determine if control is outside of Form?

I am trying to determine if a dynamically added control is outside of the form.
At first, I thought it might be possible to calculate it by getting the height of the form, and location of the dynamically added control.
But I noticed that the Control.Location and Form.Height have "nothing" in common.
I don't think I really understand what the correlation is between Height and Location.
For example:
I thought that if your form has a height of 500, and I put the control at the bottom of the form, it should give the Location: X, 500 (X is width, not relevant here). But this is not correct, it shows me for example: X, 465. Am I missing something?
So I need to be able to recognize if the control is outside of the form, even if it's just one pixel.
I've found several similar questions here on SO, yet this does not really give me the answer that I need, unfortunately.
So, is there any way to do this? Is it possible to calculate it?
The Height of the form also includes the height of the title bar and borders.
You can use the ClientSize of the form:
From the documentation on MSDN:
The size of the client area of the form is the size of the form excluding the borders and the title bar. The client area of a form is the area within a form where controls can be placed. You can use this property to get the proper dimensions when performing graphics operations or when sizing and positioning controls on the form. To get the size of the entire form, use the Size property or use the individual properties Height and Width.
The position of the control is relative to its container, so (0,0) is the left upper corner inside the form.
I know this is an older thread, but you can try using this method:
public static bool IsOutofBounds(Form form, Control control)
{
int controlEnd_X = control.Location.X + control.ClientSize.Width;
int controlEnd_Y = control.Location.Y + control.ClientSize.Height;
if (form.ClientSize.Width < controlEnd_X || form.ClientSize.Height < controlEnd_Y)
{
return true;
}
else
{
return false;
}
}
It works for checking whether a control is out of bounds of its parent form.
You could use this code to check if controls is inside form:
var Inside = frm.ClientRectange.Intersect(ctrl.Bounds) == ctrl.Bounds;
the top left corner of a form is (0,0) lower right corner is (formHeight, fromWidth).
to check this place two text boxes on a form and write this code in the mouse move event to see how x and y change.
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
textBox1.Text = e.X.ToString();
textBox2.Text = e.Y.ToString();
}
Note that there is an difference between the number returned from the edge of the form and the size chosen by you. In my 500*500 form it is actually 460*483. the difference is always the same for any border style and any resolution.
To place a control on your form use the location structure in the form or use the top and left properties for the control; top = x, left = y.
Remember your offset from the actual height and width you measured and the dimension of the control.
To add a button with the following dimensions 80*30 in the bottom right corner I would right something like this:
button1.Location = new System.Drawing.Point(402, 430);
bottom left corner:
button1.Location = new System.Drawing.Point(0, 430);

Add images on chart control

I'm not sure if I titled the question correctly so it would be better if I explained what I'm trying to do. I want to add some images on chart control and
around their to draw graphics.
I want to display the layout of the sensors on the coordinate plane defined by coordinates, while noting the location of geographic objects (forest, river, etc.). These objects will be images which I want to add to the chart/
How can I do it? It is possible?
If you show us an example we may be able to help to find the best way.
There are several options.:
You can place image controls like PictureBox or Panel on the Chart by adding them to the chart's Controls collection
You can draw them in the Pre- or PostPaint event
You can assemble a BackImage that contains all the Images you want to place around the chart.
You can add ImageAnnotations to the chart. (Recommended)
The latter obviously is the one that is best integrated.
Here is an example:
We start by adding the images we want to use to the Chart's Images collection:
List<string> imgFiles = new List<string>()
{ yourImageFileName1, ...};
for (int i = 0; i < imgFiles.Count; i++)
{
Image img = Bitmap.FromFile(imgFiles[i]);
chart1.Images.Add(new NamedImage("Image" + i, img));
}
Note the NamedImage class used here. It allows you to refer to the images by a string; pick better names! Maybe Path.GetFileNameWithoutExtension(imgFiles[i]) - Also note that you must not Dispose of the Images or else they will disappear!
Next make make a little room at the right side of the chart by reducing the ChartArea's size:
ChartArea ca = chart1.ChartAreas[0];
ca.Position = new ElementPosition(5,5,70,90);
Note the values are percentages of the Chart's ClientSize, so they will grow and shrink when resizing the Chart!
Finally we can add them all. You will want to add them at specific positions. I add them at some space to the right and also make them moveable:
foreach (NamedImage img in chart1.Images)
{
ImageAnnotation ia = new ImageAnnotation();
ia.Image = img.Name;
ia.AllowMoving = true;
ia.X = 77;
ia.Y = 15 * chart1.Images.IndexOf(img) + 5;
chart1.Annotations.Add(ia);
}
Now you should see the Annotions. And if you add this event:
private void chart1_AnnotationPositionChanging(object sender,
AnnotationPositionChangingEventArgs e)
{
testLabel.Text = e.Annotation.X + " " + e.Annotation.Y;
}
..you will see just what the numbers for the best position are. Eventually you will not keep them moveable, of course..
Note the the Annotations' position is also in percentages, so they will move along nicely, when the chart get resized! You may also scale the Images by setting the Width and Height; this is a little tricky, as it will also be in percent (and not as the docs falsely state in pixels). You would probably want to loop over the ImageAnnotations and rescale them in the Resize event..: ia.Height = ia.Width * chart1.Width / chart1.Height;
Also note that there are other ways to position annotations, like anchored to datapoints, but this seem the best for a static adornment.

Fake-scrolling containers with very many controls

I'm trying to optimize populating and scrolling a FlowLayoutPanel, but I've had issues with similar controls before, where if they have too many controls inside, it takes a really long while for the container to populate and get ready for use (and the scroller gets shorter and shorter, you might be familiar with that).
I've read that you can use a pool of just the controls within visible boundaries of the container rectangle and simulate scrolling by repopulating them with corresponding contents, as if they would be without this optimization. So you scroll as usual but the population doesn't take nearly as long. But how do I implement that for a general case?
I'm using custom controls to populate the FlowLayoutPanel container, so I'm looking for a generic enough solution that can be applied to both my control and the standard .Net controls.
Display and scrolling performance are good reasons to try a virtual paging, although they can be overcome by replacing the Controls.Add with a Controls.AddRange call and a double-buffered container..
..but there is another: Any Winforms control is limited to 32k pixels in its display dimensions. Even if you make it larger nothing will be displayed beyond this limit.
Here is a quick list of things to do, when implementing virtual paging:
Use a double-buffered FlowLayoutPanel subclass to simplify the layout and make it flicker-free.
Turn off AutoSize and AutoScroll
Add a VScrollBar to the right of the FLP and keep its Height the same as the FLP's
Calculate the Height (plus Margins) of your UserControl. I assume you add your control wrapped up in a UC, to make things easier.
Calculate the paging numbers
Create a List<yourUserControlClass> theUCs
Now create your UCs but add them only to the list theUCs
Code a scrollTo(int ucIndex) function, which clears the FLP's controls and adds the right range from the list.
code KeyPreview for the FLP to allow scrolling with the keyboard.
Setting the right values for the VScrollBar's properties, i.e. Minimum, Maximum, Value, SmallChange, LargeChange is a little tricky and setting the page size must be done whenever the FLP is resized or elements are added to or removed from the list.
In my test the setting up and the scrolling results were instantaneous. Only complete UCs are visible from the top, which is fine with me. I have added 1000 UCs with a bitmap in a Panel, a Label and a CheckedListBox.
Here is how I calculate the setting for Maximum:
float pageSize = flowLayoutPanel2.ClientSize.Height /
(uc1.Height + uc1.Margin.Top + uc1.Margin.Bottom);
vScrollBar1.Maximum = (int)( 1f * theUCs.Count / (pageSize)) + 9;
The extra 9 is a workaround for the quirky offset of a ScrollBar's theoretical and actual Maximum values.
In the ValueChanged event I wrote:
private void vScrollBar1_ValueChanged(object sender, EventArgs e)
{
int pageSize = flowLayoutPanel1.ClientSize.Height / theUCs.First().Height;
int v = Math.Min(theUCs.Count, vScrollBar1.Value);
flowLayoutPanel1.SuspendLayout();
flowLayoutPanel1.Controls.Clear();
flowLayoutPanel1.Controls.AddRange(theUCs.Skip( (v- 1) * pageSize)
.Take(pageSize + 1).ToArray());
flowLayoutPanel1.ResumeLayout();
}
This scrolls to a certain item:
void scrollTo(int item)
{
int pageSize = flowLayoutPanel1.ClientSize.Height / theUCs.First().Height;
int p = item / pageSize + 1;
vScrollBar1.Value = p;
}
For even smoother scrolling use a DoubleBuffered subclass:
class DrawFLP : FlowLayoutPanel
{
public DrawFLP() { DoubleBuffered = true; }
}
This is probably a bit rough at the edges, but I hope it'll get you on the right track.

Categories

Resources