I have a user control which consists of two controls and four buttons, arranged on a Form: the two controls are on the sides and the buttons in the middle in a vertical row.
When using the control in an app, I place it on a form.
Now, when re-sizing the form horizontally, the two controls just move left or right w/o changing their size.
What I need is that the controls stay anchored to the middle of the form and grow to the sides (sorry about the lack of clarity, I prepared screenshots but the site wouldn't let me attach them).
Is there a way to accomplish this without overriding the Resize event?
Use a TableLayoutPanel as base for your user control.
You need 3 columns and one row. The middle column needs to have a fixed size, and the other 2 you set to 50%. Not to worry, .Net is smart enough to calculate the percent they actually take.
Inside the right and left columns you put your controls and set the Dock property of both to fill. In the middle column you put a panel and set it's Dock property to fill as wall, and In that panel you put the buttons in the middle.
Set your table layout panel Dock to fill as well, and when adding the user control to the form use Dock top, bottom or fill as well.
Erratum:
The above code works most of the time, but it fails for certain Move-Resize sequences. The solution is to respond to the Move and Resize events of the parent form (the consumer of the control), not of the control itself.
One more thing: due to the event firing order (Move first followed by Resize, had to move the working code from Resize() to Move(), which seems counterintuitive but it seems the right way nevertheless.
It seems indeed that it cannot be done in the Designer, but here is the solution using overrides.
It works ok, except for some control flickering which I haven't been able to overcome.
public partial class SB : UserControl
{
//variables to remember sizes and locations
Size parentSize = new Size(0,0);
Point parentLocation = new Point (0,0);
......
// we care only for horizontal changes by dragging the left border;
// all others take care of themselves by Designer code
public void SB_Resize(object sender, EventArgs e)
{
if (this.Parent == null)
return;//we are still in the load process
// get former values
int fcsw = this.parentSize.Width;//former width
int fclx = this.parentLocation.X;//former location
Control control = (Control)sender;//this is our custom control
// get present values
int csw = control.Parent.Size.Width;//present width
int clx = control.Parent.Location.X;//present location
// both parent width and parent location have changed: it means we
// dragged the left border or one of the left corners
if (csw != fcsw && clx != fclx)
{
int delta = clx - fclx;
int lw = (int)this.tableLayoutPanel1.ColumnStyles[0].Width;
int nlw = lw - delta;
if (nlw > 0)
{
this.tableLayoutPanel1.ColumnStyles[0].Width -= delta;
}
}
this.parentSize = control.Parent.Size;//always update it
this.parentLocation = control.Parent.Location;
}
//contrary to documentation, the Resize event is not raised by moving
//the form, so we have to override the Move event too, to update the
//saved location
private void SB_Move(object sender, EventArgs e)
{
if (this.Parent == null)
return;//we are still in the load process
this.parentSize = this.Parent.Size;//always update it
this.parentLocation = this.Parent.Location;
}
}
The above code works most of the time, but it fails for certain Move-Resize sequences. The solution is to respond to the Move and Resize events of the parent form (the consumer of the control), not of the control itself.
One more thing: due to the event firing order (Move first followed by Resize, had to move the working code from Resize() to Move(), which seems counterintuitive but it seems the right way nevertheless.
Related
Before you jump to conclusions and ask "Why don't you just Hide() the form?" let me explain.
I have the following:
A PDF Viewer control PDFViewer.
A form containing a TextBox TextBoxForm.
A parent form containing PDFViewer and TextBoxForm (with Owner property set to this parent).
TextBoxForm is an overlay to PDFViewer and should be hidden when outside the bounds of PDFViewer, which can happen if the user scrolls. Here's how I achieve that:
PDFViewer_OnScroll(object sender, HandledMouseEventArgs e){
TextBoxForm.SetBounds(GetBounds(PDFViewer));
TextBoxForm.Top = [some value];
}
Ìnside TextBoxForm I do the following:
protected override void OnMove(EventArgs e)
{
base.OnMove(e);
UpdateFormRegion(); // Offset bounds so it's relative to the form itself instead of the screen
Invalidate();
}
This works fine when TextBoxForm is completely or partially inside the bounds of PDFViewer, but as soon as it's moved completely outside, whichever part of TextBoxForm was rendered before will still be rendered (I assumed it's because Windows decides there is no need to re-paint the form, when it's not inside it's own bounds).
It's easy enough to make a check for when this happens, but the reason I don't want to call TextBoxForm.Hide() is because that causes the TextBox to lose focus.
Any way I can get the form hidden without losing focus?
I just thought of a possible solution (not a very elegant one, however):
if (Right < _bounds.Left || Left > _bounds.Right || Bottom < _bounds.Top || Top > _bounds.Bottom)
{
Top = -20000;
Left = -20000;
}
Basically, whenever the form is moved/resized I check if it is now completely outside the supplied bounds and move it way off screen if true.
A less "hacky" solution is still welcome.
I am working with a SplitterPanel in winforms where on the right hand side I want a custom dropdown list control which displays a 2 columns dropdown list.
The problem is that with there being two columns I want to be able to have a larger dropdown list area than the actual dropdown, and therefore overlap the SplitterPanel if the list doesn't fit in the split area.
I have tried using .BringToFront();, however this does not work on the SplitterPanel and the control is hidden. I come from a web background where I would have used z-index for this but I am stumped with winforms. See below image of my issue.
Does anyone know how I can resolve this?
The z-index will determine which child controls sit higher and can overlap which others child controls. But it never helps when you want a (real as opposed to drop downs or menues) child overlap its own container. This never happens; and since the CheckedListBox is the child of the split panel it will never overlap it.
You will need to make the CheckedListBox sit not inside the splitter panel but in its Parent so the they are 'brethren'. Let's assume the SplitContainer sits in a TabPage tabPage8. Then you can show it fully by moving it to that tabPage:
moveCtlToTarget(tabPage8, checkedListBox1);
using this function.
void moveCtlToTarget(Control target, Control ctl)
{
// get screen location of the control
Point pt = ctl.PointToScreen(Point.Empty);
// move to the same location relative to the target
ctl.Location = target.PointToClient(pt);
// add to the new controls collection
ctl.Parent = target;
// move all up
ctl.BringToFront();
}
As I don't know how you create and show it, resetting it is up to you. Note that as it now is no longer in the split panel it will not move when you move the splitter..
You may want to do this only the first time and later align it with the ComboBox..
TaW's answer above helped my solve my issue. I modified it slightly for my situation where I moved the parameters into the method as I already had my checkbox control set as a property of the control and got the target by looping up the parents until I got to the top.
private void moveCtlToTarget()
{
Control Target = Parent;
while (Target.Parent != null)
Target = Target.Parent;
Point pt = CheckBox.PointToScreen(Point.Empty);
CheckBox.Location = Target.PointToClient(pt);
CheckBox.Parent = Target;
CheckBox.BringToFront();
}
.NET Framework / C# / Windows Forms
I'd like the FlowLayoutPanel to automatically adjust its width or height depending on number of controls inside of it. It also should change the number of columns/rows if there is not enough space (wrap its content). The problem is that if I set autosize then the flowlayoutpanel doesn't wrap controls I insert. Which solution is the best?
Thanks!
Set the FlowLayoutPanel's MaximumSize to the width you want it to wrap at. Set WrapContents = true.
Have you tried using the TableLayoutPanel? It's very useful for placing controls within cells.
There is no such thing like impossible in software development. Impossible just takes longer.
I've investigated the problem. If there is really need for Flow Layout, it can be done with a bit of work. Since FlowLayoutPanel lays out the controls without particularly thinking about the number of rows/columns, but rather on cumulative width/height, you may need to keep track of how many controls you've already added. First of all, set the autosize to false, then hook your own size management logic to the ControlAdded/ControlRemoved events. The idea is to set the width and height of the panel in such a way, that you'll get your desired number of 'columns' there
Dirty proof of concept:
private void flowLayoutPanel1_ControlAdded(object sender, ControlEventArgs e)
{
int count = this.flowLayoutPanel1.Controls.Count;
if (count % 4 == 0)
{
this.flowLayoutPanel1.Height = this.flowLayoutPanel1.Height + 70;
}
}
if the panel has initially width for 4 controls, it will generate row for new ones. ControlRemoved handler should check the same and decrease the panel height, or get all contained controls and place them again. You should think about it, it may not be the kind of thing you want. It depends on the usage scenarios. Will all the controls be of the same size? If not, you'd need more complicated logic.
But really, think about table layout - you can wrap it in a helper class or derive new control from it, where you'd resolve all the control placing logic. FlowLayout makes it easy to add and remove controls, but then the size management code goes in. TableLayout gives you a good mechanism for rows and columns, managing width and height is easier, but you'd need more code to change the placement of all controls if you want to remove one from the form dynamically.
If possible, I suggest you re-size the FlowLayoutPanel so that it makes use of all the width that is available and then anchor it at Top, Left and Right. This should make it grow in height as needed while still wrapping the controls.
I know this is an old thread but if anyone else wonders on here then here's the solution I produced - set autosize to true on the panel and call this extension method from the flow panel's Resize event:
public static void ReOrganise(this FlowLayoutPanel panel)
{
var width = 0;
Control prevChildCtrl = null;
panel.SuspendLayout();
//Clear flow breaks
foreach (Control childCtrl in panel.Controls)
{
panel.SetFlowBreak(childCtrl, false);
}
foreach (Control childCtrl in panel.Controls)
{
width = width + childCtrl.Width;
if(width > panel.Width && prevChildCtrl != null)
{
panel.SetFlowBreak(prevChildCtrl, true);
width = childCtrl.Width;
}
prevChildCtrl = childCtrl;
}
panel.ResumeLayout();
}
Are you adding the controls dynamically basing on the user's actions? I'm afraid you'd need to change the FlowLayout properties on the fly in code, when adding new controls to it, then refreshing the form would do the trick.
I'm trying to center a fixed size control within a form.
Out of interest, is there a non-idiotic way of doing this? What I really want is something analogous to the text-align css property.
At the moment, I'm setting the padding property of the surrounding form to a suitable size and setting the Dock property of the control to fill.
You could achieve this with the use of anchors. Or more precisely the non use of them.
Controls are anchored by default to the top left of the form which means when the form size will be changed, their distance from the top left side of the form will remain constant. If you change the control anchor to bottom left, then the control will keep the same distance from the bottom and left sides of the form when the form if resized.
Turning off the anchor in a direction will keep a control centered when resizing, if it is already centered. In general, a control not anchored maintains its proportional position to the dialog. E.g. If you put a control at X=75% of the dialog width and turn off left/right anchors, the control will maintain its center at X=75% of the dialog width.
NOTE: Turning off anchoring via the properties window in VS2015 may require entering None (instead of default Top,Left)
myControl.Left = (this.ClientSize.Width - myControl.Width) / 2 ;
myControl.Top = (this.ClientSize.Height - myControl.Height) / 2;
Since you don't state if the form can resize or not there is an easy way if you don't care about resizing (if you do care, go with Mitch Wheats solution):
Select the control -> Format (menu option) -> Center in Window -> Horizontally or Vertically
I found a great way to do this and it will work with multiple controls. Add a TableLayout with 3 columns. Make the center column an absolute size (however much room you need). Set the two outside columns to 100%. Add a Panel to the center column and add any controls you need and place them where you want. That center panel will now remain centered in your form.
To center Button in panel o in other container follow this step:
At design time set the position
Go to properties Anchor of the button and set this value as the follow image
In addition, if you want to align it to the center of another control:
//The "ctrlParent" is the one on which you want to align "ctrlToCenter".
//"ctrlParent" can be your "form name" or any other control such as "grid name" and etc.
ctrlToCenter.Parent = ctrlParent;
ctrlToCenter.Left = (ctrlToCenter.Parent.Width - ctrlToCenter.Width) / 2;
ctrlToCenter.Top = (ctrlToCenter.Parent.Height - ctrlToCenter.Height) / 2;
You can put the control you want to center inside a Panel and set the left and right padding values to something larger than the default. As long as they are equal and your control is anchored to the sides of the Panel, then it will appear centered in that Panel. Then you can anchor the container Panel to its parent as needed.
It involves eyeballing it (well I suppose you could get out a calculator and calculate) but just insert said control on the form and then remove any anchoring (anchor = None).
you can put all your controls to panel and then write a code to move your panel to center of your form.
panelMain.Location =
new Point(ClientSize.Width / 2 - panelMain.Size.Width / 2,
ClientSize.Height / 2 - panelMain.Size.Height / 2);
panelMain.Anchor = AnchorStyles.None;
To keep the control centered even the form or parent control were resize.
Set the following properties of the parent element (you can set it through the property window):
parentControl.AutoSize = true;
parentControl.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
Put this code in the Resize event of the form or the parent control (if the control is inside of another control).
controlToCenter.Left = (parentControl.Width- controlToCenter.Width) / 2;
controlToCenter.Top = (parentControl.Height - controlToCenter.Height) / 2;
If the parent control is docked to the form, add this line of code.
//adjust this based on the layout of your form
parentControl.Height = this.ClientSize.Height;
So, I am currently working on a pagination control and I came up with the following to achieve the below result.
Add a PanelLayout to your container (e.g. form or usercontrol)
Set the PanelLayout properties:
Dock: Bottom (or Top)
AutoSize: False
This will center the PanelLayout horizontally.
Now, in your codebehind, do something like this:
public MyConstructor()
{
InitializeComponent();
for (var i = 0; i<10; i++)
{
AddButton(i);
}
}
void AddButton(int i)
{
var btn = new Button();
btn.Width = 30;
btn.Height = 26;
btn.Text = i.ToString();
this.flowLayoutPanel1.Controls.Add(btn);
btn.Anchor = AnchorStyles.None;
}
There is a ceveat, however. If I make my form too small (horizontally) buttons will "disappear" outside of the viewport. In my case, that's not a problem, but you could take care of this by writing code that listens to the Resize event, and remove elements (buttons) based on the viewport Width.
I'm writing a WinForms application and one of the tabs in my TabControl has a SplitContainer. I'm saving the SplitterDistance in the user's application settings, but the restore is inconsistent. If the tab page with the splitter is visible, then the restore works and the splitter distance is as I left it. If some other tab is selected, then the splitter distance is wrong.
There`s a more easy solution. If Panel1 is set as the fixed panel in SplitContainer.FixedPanel property it all behaves as expected.
I found the problem. Each tab page doesn't get resized to match the tab control until it gets selected. For example, if the tab control is 100 pixels wide in the designer, and you've just set it to 500 pixels during load, then setting the splitter distance to 50 on a hidden tab page will get resized to a splitter distance of 250 when you select that tab page.
I worked around it by recording the SplitterDistance and Width properties of the SplitContainer in my application settings. Then on restore I set the SplitterDistance to recordedSplitterDistance * Width / recordedWidth.
As it was mentioned, control with SplitContainer doesn't get resized to match the tab control until it gets selected. If you handle restoring by setting SplitterDistance in percentage (storedDistance * fullDistance / 100) in case of FixedPanel.None, you will see the splitter moving in some time because of precision of calculations.
I found another solution for this problem. I subscribes to one of the events, for example Paint event. This event comes after control’s resizing, so the SplitContainer will have correct value. After first restoring you should unsubscribe from this event in order to restore only once:
private void MainForm_Load(object sender, EventArgs e)
{
splitContainerControl.Paint += new PaintEventHandler(splitContainerControl_Paint);
}
void splitContainerControl_Paint(object sender, PaintEventArgs e)
{
splitContainerControl.Paint -= splitContainerControl_Paint;
// Handle restoration here
}
For handling all cases of FixedPanel and orientation, something like the following should work:
var fullDistance =
new Func<SplitContainer, int>(
c => c.Orientation ==
Orientation.Horizontal ? c.Size.Height : c.Size.Width);
// Store as percentage if FixedPanel.None
int distanceToStore =
spl.FixedPanel == FixedPanel.Panel1 ? spl.SplitterDistance :
spl.FixedPanel == FixedPanel.Panel2 ? fullDistance(spl) - spl.SplitterDistance :
(int)(((double)spl.SplitterDistance) / ((double)fullDistance(spl))) * 100;
Then do the same when restoring
// calculate splitter distance with regard to current control size
int distanceToRestore =
spl.FixedPanel == FixedPanel.Panel1 ? storedDistance:
spl.FixedPanel == FixedPanel.Panel2 ? fullDistance(spl) - storedDistance :
storedDistance * fullDistance(spl) / 100;
I had the same problem. In my particular case, I was using forms, that I transformed into tabpages and added to the tab control.
The solution I found, was to set the splitter distances in the Form_Shown event, not in the load event.
Save the splitter distance as a percentage of the split container height. Then restore the splitter distance percentage using the current split container height.
/// <summary>
/// Gets or sets the relative size of the top and bottom split window panes.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[UserScopedSetting]
[DefaultSettingValue(".5")]
public double SplitterDistancePercent
{
get { return (double)toplevelSplitContainer.SplitterDistance / toplevelSplitContainer.Size.Height; }
set { toplevelSplitContainer.SplitterDistance = (int)((double)toplevelSplitContainer.Size.Height * value); }
}
Restoring splitter distances has given me a lot of grief too. I have found that restoring them from my user settings in the form (or user control) Load event gave much better results than using the constructor. Trying to do it in the constructor gave me all sorts of weird behaviour.
Answer is time synchrinizations. You must set SplitterDistance when window is done with size changing. You must then flag for final resize and then set SplitterDistance. In this case is all right
Set the containing TabPage.Width = TabControl.Width - 8 before setting the SplitContainer.SplitDistance