Winforms SplitterPanel, z-index of child overlap split - c#

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();
}

Related

How can I stop the automatic resizing of an Element in a Horizontal Layout Group in Unity?

here is my problem:
I want to have a bunch of Text-GameObjects to be evenly distributed across the screen horizontally.
So I took a Layout-Group and added it onto a Panel, which is streched across the screen, with the following settings:
Panel Settings
(The Layout Element on the Panel is not important here, I think, but the Panel itself is controlled by another Object to stretch across the Screen)
Now, I add the Text-GameObjects via Script. That looks like that:
days[i].transform.SetParent(GameObject.Find("Day Panel").transform);
days[i].AddComponent<Text>();
days[i].transform.GetComponent<Text>().font = (Font)Resources.GetBuiltinResource(typeof(Font), "Arial.ttf");
days[i].transform.GetComponent<Text>().fontSize = 25;
days[i].transform.GetComponent<Text>().color = Color.black;
days[i].transform.GetComponent<Text>().alignment = TextAnchor.UpperCenter;
days[i].transform.GetComponent<Text>().text = shownDates[i].Day.ToString();
days[i].transform.localScale = new Vector3(1, 1, 1);
days[i].transform.localPosition = new Vector3(0, 0, 0);
Now I have the problem, that the width of the text-GameObjects is influenced by the length of the string I show with them. So if a text-GameObject has a 3-char-long string in it, it is wider than a text-GameObject next to it with a one-char-long string. But I need to distribute the width "fairly" between the Text-Objects, independently from what's inside.
I hope, that you can help me, Thanks :D
Making my comment an answer as it answers your question. You have two options to solve your issue.
The first I provided is not as robust and not modular that will work if you know the number of child objects at compile time. You can utilize the field minWidth of a LayoutElement. By setting this value as the width each element needs to be to evenly fill the space, the text object will be no smaller than the value you give it. However, if you ever add any new objects to your layout group at runtime or at any other time, you would need to recalculate these values so it is not a great solution.
The second solution which would allow for almost no work by you is to add an empty parent RectTransform to fill the space above the text object. With the layout group forcing the width and height to expand to the container, each RectTransform will fill the space evenly, while the childed text can be whatever size it needs to be. Here is the hierarchy setup of this solution:
And here is the solution working:
In the example, the parent objects are panels with Image components. I only did this to show that there is a divide between each object like you want. You can remove this component and still have the object retain its structure.

Resizing User Control - anchor controls to center of form

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.

Win 8.1 ScrollViewer issues with a GridView

I have a VariableSizeGridView (aka GridView with a VariableSizedWrapGrid) and since the framework doesn't support ISupportIncrementalLoading on VariableSizedWrapGrid I implemented my own by "listening" to the GridView parent ScrollViewer (this specific case a Hub control)
Everything works fine, when I reach on the end of the scrollviewer my code calls a routine to get more data.
The problem occurs when the new data gets on the screen, the scrollviewer automatically scrolls to the end and the load process starts again, ending up in an infinite loop if I don't move the scrollbar elsewhere.
Is there any way to prevent that the scrollviewer automatically goes to the end after I added some items?
Thanks
You can create a Custom-control that overrides from VariableSizeGridView and listen, as you do, to the scrollviewer. Since you have access to the latest element of the GridView, after starting loading more elements, you can set the scrollviewer to the that latest position position. Get a visual reference to that element and then call this code:
FrameworkElement focusedElement = FocusManager.GetFocusedElement() as FrameworkElement;
GeneralTransform focusedVisualTransform = parent.TransformToVisual(_scrollViewer);
ApplyHorizontalScrolling(focusedElement, focusedVisualTransform);
private void ApplyHorizontalScrolling(FrameworkElement focusedElement, GeneralTransform focusedVisualTransform)
{
Rect rectangle = focusedVisualTransform.TransformBounds(new Rect(new Point(focusedElement.Margin.Left, focusedElement.Margin.Top), focusedElement.RenderSize));
double horizontalOffset = _scrollViewer.HorizontalOffset + (rectangle.Left);
_scrollViewer.ChangeView(horizontalOffset, 0, _scrollViewer.ZoomFactor);
}
Using and tunning this code, will help you preventing the scrollbar to go to the latest position.

FlowLayoutPanel autowrapping doesn't work with autosize

.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.

Centering controls within a form in .NET (Winforms)?

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.

Categories