I have TableLayoutPanel for dynamic creation of controls with AutoScroll = true. It's work fine when I add new controls. But when I remove and all controls are visible, vertical scroll is visible.
Some screenshots here:
Expected/correct scroll visibility:
Incorrect visibility:
Any ideas?
Update:
Here is some code
tableLayoutPanel1.SuspendLayout();
tableLayoutPanel1.RowCount = 0;
tableLayoutPanel1.RowStyles.Clear();
tableLayoutPanel1.AutoScroll = true;
tableLayoutPanel1.Padding = new Padding(0, 0, SystemInformation.VerticalScrollBarWidth, 0);
foreach (var item in objects)
{
tableLayoutPanel1.RowCount++;
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tableLayoutPanel1.Controls.Add(CreateNewItem(item));
}
tableLayoutPanel1.RowCount++;
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tableLayoutPanel1.Controls.Add(CreateAddButton());
tableLayoutPanel1.ResumeLayout();
and code for deleting
tableLayoutPanel1.SuspendLayout();
tableLayoutPanel1.Controls.Remove(item);
tableLayoutPanel1.RowStyles.RemoveAt(0);
tableLayoutPanel1.RowCount--;
tableLayoutPanel1.ResumeLayout();
AutoSize is true, AutoSizeMode is GrowAndShrink
The problem concerns TableLayoutPanel scrolling.
You have to use a Panel for scrolling instead of TableLayoutPanel.
Here is an example to solve this problem (for vertical scrolling) :
Set your TableLayoutPanel properties as follow :
Dock = DockStyle.Top
AutoSize = true
AutoSizeMode = AutoSizeMode.GrowAndShrink
AutoScroll = false.
Put your TableLayoutPanel into a Panel with properties :
Dock = DockStyle.Fill
AutoScroll = true
AutoSize = false.
when you remove the dynamic controls, you need to remove the extra rows that was inserted during the addition and re-size the table layout panel height to smaller than scroll container height.
During the addition the table layout panel height would have increased, which handled by the scroll container; but when you remove the controls, the table layout panel height doesn't reduce it's height to fit the scroll container.
One way to do this is to give fixed height to the rows and set the table layout panel seize set to "Auto".
One of the easiest and funniest solution is to just disable and enable tableLayoutPanel1.AutoScroll
In your Deleting procedure code add at the end these codes :
tableLayoutPanel1.AutoScroll = False
tableLayoutPanel1.AutoScroll = True
I inserted tableLayoutPanel to XtraScrollableControl(Devexpress control). tableLayoutPanel.Dock set to Top and XtraScrollableControl.Dock to Fill. This solution did not solves this problem, but I got behavior that I need.
I counted the number of rows in my TableLayoutPanel to see how many would fit. Below the amount that fit I set AutoScroll = false for the add and delete methods. The scroll will appear for large sets and disappear on small sets.
if (tableLayoutPanel.RowCount < 15)
{
panel1.AutoScroll = false;
}
else
{
panel1.AutoScroll = true;
}
I had a TableLayoutPanel on a UserControl, docked in Fill mode, with all rows on the TableLayoutPanel set to AutoSize. This UserControl would then dynamically get put on a panel, again in Fill mode, to show it to the user when needed. I put the UserControl on AutoScroll, but that alone did not solve it.
In the end, I solved it by going over all controls in the TableLayoutPanel, storing the extremities, and baking that into a Size to put in my UserControl's AutoScrollMinSize:
private void AdjustPanelSize(ScrollableControl panel, TableLayoutPanel tableLayoutPanel)
{
int maxX = 0;
int maxY = 0;
foreach (Control c in tableLayoutPanel.Controls)
{
maxX = Math.Max(maxX, c.Location.X + c.Width);
maxY = Math.Max(maxY, c.Location.Y + c.Height);
}
panel.AutoScrollMinSize = new Size(maxX, maxY);
}
This worked, and it also has the advantage that it can be called if there would ever be controls dynamically added or removed from the TableLayoutPanel.
Related
I am trying to make the height of my DataGridView AutoSize based on the amount of rows it contains. Currently, I was able to accomplish this with the following line:
dataGridView_SearchResults.AutoSize = true;
However this makes the Horizontal scroll bar disappear, the the DataGridView gets cut off.
How can I autosize the height without losing the horizontal scroll bar?
Option 1 - Overriding GetPreferredSize
You can override GetPreferredSize method of DataGridView and call the base method using new proposed size new Size(this.Width, proposedSize.Height). This way, the current width of control will remain untouched while the auto-size rules will apply on its height:
using System.Drawing;
using System.Windows.Forms;
public class MyDataGridView : DataGridView
{
public override Size GetPreferredSize(Size proposedSize)
{
return base.GetPreferredSize(new Size(this.Width, proposedSize.Height));
}
}
Option 2 - Setting the Height based on Height of Calculated Auto-Size
If you don't want to derive from DataGridView, you can calculate the auto-size by calling its GetPreferredSize passing new Size(0, 0) then set the height of DataGridView to the height of result, this way you only change the height of DataGridView. You should set the auto-height in RowsAdded, RowsRemoved, some other events if you need:
void AutoHeightGrid(DataGridView grid)
{
var proposedSize = grid.GetPreferredSize(new Size(0, 0));
grid.Height = proposedSize.Height;
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.RowsAdded += (obj, arg) => AutoHeightGrid(dataGridView1);
dataGridView1.RowsRemoved += (obj, arg) => AutoHeightGrid(dataGridView1);
//Set data source
//dataGridView1.DataSource = something;
}
If you want to make sure that all changes in grid including changing Font, height of rows will cause resizing grid, you can call the method in Paint event.
Option 3 - Setting MaximumSize
Also as mentioned by Hans, if you don't want to derive from DataGridView, you can use MaximumSize property of the grid. You can set it to new Size(this.dataGridView1.Width, 0):
dataGridView1.MaximumSize = new Size(this.dataGridView1.Width, 0);
dataGridView1.AutoSize = true;
Note
Since using MaximumSize is not so friendly when the user wants to let the grid width change by left and right anchors, I prefer to use Option 1 or Option 2.
I tried every option proposed by Reza Aghaei using .NET Framework 4.7.2. All the times I got an extra space between the last row and the DataGridView bottom border. So I tried a different approach, and it works!
Using the event you prefer, write the following line:
dataGridView1.Height = DataGridView1.Rows.GetRowsHeight(DataGridViewElementStates.Visible)
+ (dataGridView1.ScrollBars.HasFlag(ScrollBars.Horizontal) ? SystemInformation.HorizontalScrollBarHeight : 0)
+ 3;
You'd change the last +3 according to the style of your choice. Just change it from +1 to +5 depending of your likes.
I have a form with three panels.
I want the top two to be a fixed height, and bottom one to fill the rest of the space.
The dialog is resizable, so all should change width on resixze, and bottom one should change height.
This is important, the user must be able to stretch the form, as well as the program through code.
If I set a panel to visible = false, I want the form height to shrink so the others stay the same height.
If I set a panel to visible = true, I want the form height to grow by the height of the panel.
I will control the hiding/showing of the panels with buttons. The idea is I show certain panels for "advanced" mode in my form, and hide them for "simple" mode. I cannot have a bunch of blank space if I hide a panel, and I want the form to shrink a bunch for simple mode.
I tried doing this with panels docked to top, but a form resize by the user will not change a panel height. So that is the main trick I am asking for help on.
Anyone in this kind of situation can use the below styling:
Use 2-3 Panel to Group different Controls and place them in a single parent control say a GroupBox.
Make all the child panels Dock to the same side say "Top".
if any of the panel is jumping over another due to similar docking. In Visual Studio, go to menu View > Other Windows > Document Outline and Set the display order. (https://msdn.microsoft.com/en-us/library/46xf4h0w%28v=vs.80%29.aspx)
Set the Panel property as follows:
AutoSize: true
AutoSizeMode: GrowAndShrink
Once the panel is not visible, other panel will take away the empty space.
Hope this helps!!!
the other posts are close, there is a detail missing that I included in the solution I came up with. Its a flag to tell if the sized event was caused by a user form resize, or the program doing it when a panel is shown or hidden.
For this solution, make a form with 4 panels.
Set dock to top for all panels. Do not set any autosizing for panels or form.
Also make two buttons and place on top panel, or any panel that you will not be hiding.
The code below shows how to handle the resized event, and the showing hiding buttons.
I made them hide/show panel 2, but the code should work for any panel.
namespace ProgTesting {
public partial class Form5 : Form {
private bool doNothing = false;
public Form5() {
InitializeComponent();
cmdAdvanced.Visible = false;
}
private void cmdSimple_Click(object sender, EventArgs e) {
if (panel2.Visible) {
panel2.Visible = false;
doNothing = true;
this.MinimumSize = new Size(this.Width, this.Height - panel2.Height);
this.Height = this.Height - panel2.Height;
doNothing = false;
cmdSimple.Visible = false;
cmdAdvanced.Visible = true;
}
}
private void cmdAdvanced_Click(object sender, EventArgs e) {
if (!panel2.Visible) {
panel2.Visible = true;
doNothing = true;
this.Height = this.Height + panel2.Height;
this.MinimumSize = new Size(this.Width, this.Height);
doNothing = false;
cmdAdvanced.Visible = false;
cmdSimple.Visible = true;
}
}
private void Form5_SizeChanged(object sender, EventArgs e) {
if (!doNothing)
if (panel2.Visible)
panel3.Height = this.ClientSize.Height - panel1.Height - panel2.Height - panel4.Height;
else
panel3.Height = this.ClientSize.Height - panel1.Height - panel4.Height;
}
}
}
You do have to manage the heights going on, which is a pain but gives you control. Some shots of it working:
It sounds like what you are looking for is an Expander. Essentially a control that consists of a header and a content area, where clicking on the header toggles the content area between visible and collapsed.
I'm not 100% certain if an Expander control will automatically adjust the height of a form when it expands/collapses though. You may need to hook up an event handler to the Expander to manually adjust the height of your form when the Expander collapses.
Try looking at the Stack Overflow Question Add an expander (collapse/expand) to a Panel WinForm. There are a number of links from that question about implementing an Expander in Windows Forms, including some that provide full source code.
Update:
I've knocked up a quick demo that will accomplish what you want, in a very primitive way, not using any of the various Expander controls out there. I think that while you will find the Expanders useful, I doubt they will adjust the size of the container they are inside, unless you write some adjustment code like I have below.
Create a form that looks like this:
The two text boxes are anchored Left, Top, Right.
The toggle button is anchored Left, Top, named btnTogglePanel.
The magenta panel is anchored Left, Top, Right, Bottom, named pnlToggled.
The bottom button is anchored Right, Bottom.
Then in the code behind for the form:
public partial class ToggleableExpanderForm : Form
{
public ToggleableExpanderForm()
{
InitializeComponent();
SizeChanged += (s, args) =>
{
if (_LastSize.HasValue)
{
var diff = Size - _LastSize.Value;
if (_LastHeight.HasValue
&& !IgnoreNextResizeForLastHeightAdjustment)
{
_LastHeight += diff.Height;
}
}
_LastSize = Size;
};
}
private Size? _LastSize;
private bool IgnoreNextResizeForLastHeightAdjustment = true;
private int? _LastHeight;
private void btnTogglePanel_Click(object sender, EventArgs e)
{
var newVisibility = !pnlToggled.Visible;
int heightAdjustment = 0;
if (newVisibility)
{
if (_LastHeight.HasValue)
{
heightAdjustment = _LastHeight.Value;
_LastHeight = null;
}
}
else
{
_LastHeight = pnlToggled.Height;
heightAdjustment = -pnlToggled.Height;
}
pnlToggled.Visible = newVisibility;
IgnoreNextResizeForLastHeightAdjustment = true;
Height += heightAdjustment;
}
}
Essentially you adjust the height of the form manually when the toggle button is clicked, based on whether or not the panel is hiding/showing. You also need to take into account of what will happen when the user resizes the form when the panel is invisible, which is where the cruft around listening to the SizeChanged event comes in. You need the answer to "how MUCH has the size changed" and then you adjust your previously stored panel height as appropriate.
I don't imagine the above code is perfect, and I haven't tested it in every use case, but it gets the basic job done.
One way to go about it is to use a TableLayoutPanel for the top two panels.
The steps below will give you the resizing desired when the user resizes the form.
Start by created a TableLayoutPanel (TLP) and shrink it down from the default 2 columns, 2 rows to just 2 columns, 1 row.
Anchor it to Top, Left, and Right
Now size the TLP to fit your top two panels and place each panel in a cell of the TLP.
Anchor these two panels to all sides.
Position the 3rd panel (Panel3) below the TLP to your liking and anchor it to all sides.
To handle the hiding of Panel3 some logic will need to be added to the appropriate button_Click event. Determine what size you would like the "minimized" height to be and then just store the form size when Panel3 is visible and restore the height when it's clicked again. It should look something like this.
private void button1_Click(object sender, EventArgs e)
{
if (panel3.Visible)
{
// make invisible
panel3.Visible = false;
// storedHeight is a private member of the Form
storedHeight = Form1.ActiveForm.Height;
Form1.ActiveForm.Height = minimumHeight; // Set to predetermined minimum height
}
else
{
// make visible
panel3.Visible = true;
Form1.ActiveForm.Height = storedHeight;
}
}
Now when you hide Panel3 the form will shrink to the size of the TLP, then grow again when Panel3 is visible. Also the top panels will expand in width, but not height. You will need to change the MinSize properties of the form and its value will have to be dynamically adjusted depending on whether Panel3 is visible or not.
I am trying to make a Panel scrollable, but only vertically (so AutoScroll won't work because the child controls go past the left edge and must).
So how is this done?
Try this instead for 'only' scrolling vertical.
(auto scroll needs to be false before it will accept changes)
mypanel.AutoScroll = false;
mypanel.HorizontalScroll.Enabled = false;
mypanel.HorizontalScroll.Visible = false;
mypanel.HorizontalScroll.Maximum = 0;
mypanel.AutoScroll = true;
Assuming you're using winforms, default panel components does not offer you a way to disable the horizontal scrolling components. A workaround of this is to disable the auto scrolling and add a scrollbar yourself:
ScrollBar vScrollBar1 = new VScrollBar();
vScrollBar1.Dock = DockStyle.Right;
vScrollBar1.Scroll += (sender, e) => { panel1.VerticalScroll.Value = vScrollBar1.Value; };
panel1.Controls.Add(vScrollBar1);
Detailed discussion here.
Panel has an AutoScroll property. Just set that property to True and the panel will automatically add a scroll bar when needed.
AutoScroll is really the solution!
You just have to set AutoScrollMargin to 0, 1000 or something like this, then use it to scroll down and add buttons and items there!
Below is the code that implements custom vertical scrollbar. The important detail here is to know when scrollbar is needed by calculating how much space is consumed by the controls that you add to the panel.
panelUserInput.SuspendLayout();
panelUserInput.Controls.Clear();
panelUserInput.AutoScroll = false;
panelUserInput.VerticalScroll.Visible = false;
// here you'd be adding controls
int x = 20, y = 20, height = 0;
for (int inx = 0; inx < numControls; inx++ )
{
// this example uses textbox control
TextBox txt = new TextBox();
txt.Location = new System.Drawing.Point(x, y);
// add whatever details you need for this control
// before adding it to the panel
panelUserInput.Controls.Add(txt);
height = y + txt.Height;
y += 25;
}
if (height > panelUserInput.Height)
{
VScrollBar bar = new VScrollBar();
bar.Dock = DockStyle.Right;
bar.Scroll += (sender, e) => { panelUserInput.VerticalScroll.Value = bar.Value; };
bar.Top = 0;
bar.Left = panelUserInput.Width - bar.Width;
bar.Height = panelUserInput.Height;
bar.Visible = true;
panelUserInput.Controls.Add(bar);
}
panelUserInput.ResumeLayout();
// then update the form
this.PerformLayout();
3 steps:
1- just set AutoScroll property to true
2- in Form load()add the following:
my Panel Vertical Scroll Maximum = 10000
3- after my Panel controls Add(item) add the following:
Invalidate();
Done!
Add to your panel's style code something like this:
<asp:Panel ID="myPanel" runat="Server" CssClass="myPanelCSS" style="overflow-y:auto; overflow-x:hidden"></asp:Panel>
My tablelayout panel has one column and three rows. (one docked to Fill panel in each cell.)
Now I would like to be able to hide/show the rows . I want only one row to be visible at any time ( based on a user selection of some radio buttons) and I want to to get resized so it fills all the area of the TableLayoutPanel.
How can I do that? Any thoughts?
If rows in your TableLayoutPanel is autosized then hiding content panel will hide cell where panel placed too.
I would suggest setting the other rows heights to 0 is the easiest way:
Row one:
this.tableLayoutPanel1.RowStyles[1].Height = 0;
Try this
TableLayoutPanel1.ColumnStyles[1].SizeType = SizeType.Absolute;
TableLayoutPanel1.ColumnStyles[1].Width = 0;
So why did you use a TableLayoutPanel?
Just put three Panels on your form, fill in everyone the content of each row and set the Dock property of all three panels to Fill. Set two panels Visible = false and one to true.
If you like to see another panel, just make it visible and hide the other two (based on your radio button settings).
My scenario is similar. I needed a TableLayoutPanel with 4 rows each of which needed to be visible according to a checkbox selection. So instead of only showing one row at a time, I can show 1 - 4.
After designing the layout with 1 column and 4 rows, the controls were added and Dock set to Fill for each one.
Then in a single CheckedChanged event handler for the checkboxes, I coded as shown below. It's kind of a brute force method, but, Hey...it works!
private void checkBox_CheckedChanged(object sender, EventArgs e)
{
this.SuspendLayout();
int seldCount = checkBox1.Checked ? 1 : 0;
seldCount += checkBox2.Checked ? 1 : 0;
seldCount += checkBox3.Checked ? 1 : 0;
seldCount += checkBox4.Checked ? 1 : 0;
float pcnt = 0;
if (seldCount == 1)
pcnt = 1;
if (seldCount == 2)
pcnt = 0.5f;
if (seldCount == 3)
pcnt = 0.33f;
if (seldCount == 4)
pcnt = 0.25f;
int newHeight = (int)(tableLayoutPanel1.Height * pcnt);
if (checkBox1.Checked)
{
tableLayoutPanel1.RowStyles[0].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[0].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[0].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[0].Height = 0;
}
if (checkBox2.Checked)
{
tableLayoutPanel1.RowStyles[1].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[1].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[1].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[1].Height = 0;
}
if (checkBox3.Checked)
{
tableLayoutPanel1.RowStyles[2].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[2].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[2].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[2].Height = 0;
}
if (checkBox4.Checked)
{
tableLayoutPanel1.RowStyles[3].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[3].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[3].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[3].Height = 0;
}
this.ResumeLayout();
}
To hide row try this!!
tableLayoutPanel1.RowStyles[1].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[1].Height = 0;
I had similar task to do and my solution is following:
Add a TableLayoutPanel to your form (or any container).
Set TableLayoutPanel's columns and rows count to 1 and size to 100%.
Set Dock to Fill.
Set GrowStyle to fixedSize.
Set AutoSize to true.
Then programmatically add all of three forms/controls, one of which you have to show depending on radio button choice. Be sure that only one of them is visible. That could be done with initial FirstControl.Show(); and then on each RadioButton event hide the current one and show another. you may "remember" in local variable (say: "currentlyVisibleControl" the reference which is currently visible)
note: if you will .Show() more than one at time. then TableLayoutPanel wil fire the exception that it is full and can't add any more item.
P.S. In My own example I have TableLayoutPanel in MDI window and three forms which substitute each other on button clicks on them so I think copying my source code will complicate the "verbal" example.
P.P.S. From my experience Visual Studio does some weird things in design mode sometimes. I had to remove and re-add the TableLayoutPanel to set properties correctly and get the results both in designer and in runtime. So if either autosize or absolute/percent values are not depicted on designer screen it may be designers problem rather that yours. JUST DELETE IT AND RETRY.
I tried fooling around with the Height and SizeType properties, but it was giving me odd results. For example, the Labels on the target row were being hidden, but the TextBoxes were not.
Here is an extension class that I came up with using #arbiter's suggestion of hiding the children Controls of the row.
// these methods only works on rows that are set to AutoSize
public static class TableLayoutPanelExtensions
{
public static void HideRows(this TableLayoutPanel panel, params int[] rowNumbers)
{
foreach (Control c in panel.Controls)
{
if (rowNumbers.Contains(panel.GetRow(c)))
c.Visible = false;
}
}
public static void ShowRows(this TableLayoutPanel panel, params int[] rowNumbers)
{
foreach (Control c in panel.Controls)
{
if (rowNumbers.Contains(panel.GetRow(c)))
c.Visible = true;
}
}
}
Is there any way to Use the Builtin ScrollBars that comes with each .Net ScrollableControl without setting the AutoScroll Property to Enable?
Here is the issue, even If I Enable, set to Visible and declare min and max values as well as the smallChange and LargeChange for the HorizontalScrollBar and VerticalScrollBar they show up in the borders of the Control but they are useless. When clicked the thumb doesn't moves and the Scroll Event of the control doesn't bring any usefull information when scroll is clicked ( OldValue and NewValue are both 0)
This is how I tried to set up the Scroll Bars Values:
HorizontalScroll.Enabled = true;
HorizontalScroll.Value = 80;
HorizontalScroll.Minimum = 0;
HorizontalScroll.Maximum = 300;
HorizontalScroll.SmallChange = 2;
HorizontalScroll.LargeChange = 4;
HorizontalScroll.Visible = true;
(And did the same thing to the Vertical Scroll)
Any ideas? or do I have to add to new ScrollBars by myself to my control?
Well I couldn't find a way to reuse the same ScrollBars integrated with each Net Scrollable Control. Finally I made my own Scrolls and very Important ... override the AutoScroll Property if it is a Control someone else is going to reuse.
public override bool AutoScroll
{
get { return false; }
set
{
if (value)
throw new Exception("Auto Scroll not supported in this control");
base.AutoScroll = false;
}
}