Winforms Control with custom LayoutEngine - c#

In C# WinForms, if a control is docked then the margin is never used. In most cases the trick would be to place the control inside a panel with some padding. Now I'm looking for a solution without the container-panel, because my control is already a descendant of panel. So for the user it would be confusing an I would have route most of the properties from the container-panel to the inner panel.
My first try was a custom LayoutEngine but there is not much on that topic. But it kinda works. But I've some problems with the surrounding (sibling controls) they overlap the part which I made my control "bigger" (margin). In my custom layoutengine is make the control Size bigger (adding the margin) and moving the x/y accordingly
Image Custom Panel
The green control is my custom panel, which is docked bottom and has an margin of 10, the red control is a normal panel which is also docked bottom.
Now I could resize the sibling controls but I think this would make more problems? Is there any way to tell the other (sibling) controls that the size of my control has changed and that they should update their layout?
public class CustomLayoutEngine : LayoutEngine
{
public override bool Layout(object container, LayoutEventArgs layoutEventArgs)
{
bool result = base.Layout(container, layoutEventArgs);
Control parent = container as Control;
if (parent.Dock == DockStyle.Bottom && (parent.Margin.Left > 0 || parent.Margin.Right > 0))
{
Int32 newWidth = parent.Size.Width - (parent.Margin.Left + parent.Margin.Right);
Int32 newHeight = parent.Size.Height - (parent.Margin.Bottom);
parent.Size = new Size(newWidth, parent.Size.Height);
parent.Location = new Point(parent.Location.X + parent.Margin.Left, parent.Location.Y - parent.Margin.Bottom);
}
return result;
}
}

Related

Implementing control by control (not smooth) scrolling panel

I have a FlowLayoutPanel that contains User Controls from up to down with vertical scrollbar.
Like any other scrollable control, I can scroll it pixel by pixel.
Is there a way (.NET Framework or native API way) to scroll it User Control by User Control, in order to snap to next or previous User Control? They can have different height.
I would like to reproduce DataGridView or Excel/Calc row by row scrolling type.
I this question - How to make scrollviewer scroll pixels not components (wpf), the behavior you expect is unwanted. Thus, you could do what the user has done in the described question.
The essential difference between your question and the one referenced is to do with host control. You have used FlowLayoutPanel and in the referenced question StackPanel is used.
So, if by any chance your application is a WPF application can you change it to StackPanel?
Did setting these properties AutoScroll=True with WrapContent=True on FlowLayoutPanel not work?
Quick and dirty solution.
Set AutoScroll=false, add a VScrollBar, and put the following code:
vScrollBar1.Maximum = MyList.VerticalScroll.Maximum;
vScrollBar1.SmallChange = MyList.VerticalScroll.SmallChange;
vScrollBar1.LargeChange = MyList.VerticalScroll.LargeChange;
vScrollBar1.Scroll += (sender, args) =>
{
switch (args.Type)
{
case ScrollEventType.ThumbTrack:
var sum = 0;
Control prevCtrl = null;
foreach (Control control in MyList.Controls)
{
if (prevCtrl == null || control.Bottom > prevCtrl.Bottom)
{
if (args.OldValue >= sum && args.OldValue < sum + control.Height)
{
MyList.AutoScrollPosition = new Point(0, sum);
}
sum += control.Height;
}
prevCtrl = control;
}
break;
}
}

How to auto center textbox inside user control, when user control is resized?

This issue is concerning C# WinForms ... I have custom user control which basically has 3 things: A label, a text box, and a list box, all arranged vertically like this:
I have written a small Custom ControlDesigner class to prevent the user from changing the height of this user control at design time.
I also have set the textbox and listbox to anchor to left/right locations, so that when the user changes the width of the user control, these two internal controls resize beautifully.
I want to have the Label is the horizontal center as well, but anchoring that to left/right doesn't seem to work. I'm thinking I might have to written some sort of custom resize function inside the custom control designer class, which automatically places the label in the middle when the user control is resized ?
Also note that I want to label to still remain in the center when the label text is changed.
So how do I do this ?
Here is my custom control designer class till now:
public class DropDownTextBoxDesigner : System.Windows.Forms.Design.ControlDesigner
{
public override SelectionRules SelectionRules
{
get
{
SelectionRules retVal =
SelectionRules.Visible |
SelectionRules.Moveable |
SelectionRules.LeftSizeable |
SelectionRules.RightSizeable;
return retVal;
}
}
}
Write a SizeChanged of the User Control event like so:
private void UserControl1_SizeChanged(object sender, EventArgs e)
{
textBox1.Left = Width / 2 - textBox1.Width / 2;
textBox1.Top = Height / 2 - textBox1.Height / 2;
}
In the resize event, can you just set the left property of the label to center it in the container?
Label.Left = (Container.Width / 2) - (Label.Width / 2);

Make the size of a System.Windows.Forms.Control dependent on the size of the parent

I want to make some controls (specifically: Button, Label and Panel) to become dependent on the size of their parent. So a button might be 0.1 times the width of the parent, 0.05 the height, and be positioned in 0.3 times the width and 0.2 times the height.
Now I have 2 problems:
First I want to change the behaviour of the Control class into a sort of 'relative size and relative position'-Control. This would be very easy if the Control class had an 'onParentResized' method I could override, but it hasn't. So now my solution is this
class RelativeControl : Control
{
Control previousParent;
double relativeWidth, relativeHeight, relativeX, relativeY;
public RelativeControl(double RelativeWidth, double RelativeHeight, double RelativeX, double RelativeY)
{
// the arguments need to be between 0 and 1 normally, or the control is
// garanteed to be partially offscreen
this.relativeWidth = RelativeWidth;
this.relativeHeight = RelativeHeight;
this.relativeX = RelativeX;
this.relativeY = RelativeY;
}
protected override void OnParentChanged(EventArgs e)
{
if(previousParent != null)
{
previousParent.Resize -= new EventHandler(parentResized);
}
if(this.Parent != null)
{
this.Parent.Resize += parentResized;
}
this.previousParent = this.Parent;
}
private void parentResized(Object o, EventArgs e)
{
this.Width = (int)(this.Parent.Width * this.relativeWidth);
this.Height = (int)(this.Parent.Width * this.relativeHeight);
this.Location = new Point((int)(this.Parent.Width * this.relativeX), (int)(this.Parent.Height * this.relativeY));
}
}
Is this a good solution?
Second problem: I want to make the Button-class (as well as the Panel and Label class) to extend this new version of control. However this isn't possible as far as I know. My only option seems to be to make 3 classes and literally find-and-replace 'Control' by "Label", "Button" and "Panel" to get the result I want.
What should I be doing here?
I think you are after TableLayoutPanel control.
Happily on .NET platform do not have to worry anymore about child controls resizing or repositioning to the parent.
You have to make extensive use of the Dock and Anchor properties of the child controls.
You can start with the links but there are many tutorials about them on the web.
TableLayoutPanel
Anchor and Dock Child Controls
Create a Resizable Windows Form

UserControl not rendering within FlowLayoutPanel when dock changed

When I add my UserControls to a FlowLayoutPanel, they display properly. When I change the Dock or Anchor properties on the UserControls before adding them, they are still added but do not render.
According to "How to: Anchor and Dock Child Controls" this should be possible.
I can tell that the controls are added (despite not drawing) because adding enough of them causes a vertical scrollbar to appear.
Setting the "Dock" property of the UserControls to "Left" or "None" will cause them to render, but none of the other options.
Setting the "Anchor" property on the UserControls to anything but Top | Left does not render.
Setting the dock before or after adding the control makes no difference (Add, Dock vs. Dock, Add).
The FlowLayoutPanel is itself is docked (Fill), has FlowDirection set to TopDown, has WrapContents set to false, has AutoScroll set to true, and is otherwise default.
I am using .NET 3.5.
In answer to a comment, the two commented lines are the locations I tried to change the dock. The second spot definitely makes more sense, but I tried the other because it couldn't hurt.
public void CreateObjectControl( object o )
{
ObjectControl oc = new ObjectControl();
oc.MyObject = o;
//This was a spot I mentioned:
//oc.Dock = DockStyle.Fill;
ObjectDictionary.Add( o, oc );
flowLayoutPanel1.Controls.Add( oc );
//This is the other spot I mentioned:
oc.Dock = DockStyle.Fill;
}
try using SuspendLayout and Resumelayout function for the controls before making any amendments which need rendering for proper viewing.
You could see the code from Designer.cs for that particular control
Syntax
control.SuspendLayout();
{Your code for designer amendments}
control.resumeaLayout();
I think I may have found a workaround (read: dirty trick) ... this answer helped to point me in the right direction. Here's an excerpt from the MS article that you also linked to:
For vertical flow directions, the FlowLayoutPanel control calculates the width of an implied column from the widest child control in the column. All other controls in this column with Anchor or Dock properties are aligned or stretched to fit this implied column.
The behavior works in a similar way for horizontal flow directions. The FlowLayoutPanel control calculates the height of an implied row from the tallest child control in the row, and all docked or anchored child controls in this row are aligned or sized to fit the implied row.
This page does not specifically mention that you can't Dock/Anchor the tallest/widest control. But as this control defines the layout behaviour of the FlowLayoutPanel, and thus influences the way all other sibling controls are displayed, it is well possible that Dock and Anchor don't work properly for that 'master control'. Even though I can't find any official documentation regarding that, I believe it to be the case.
So, which options do we have? At runtime, we could add a panel control of height 0 and width of the FlowLayoutPanel client area before you add your usercontrol. You can even set that panel's visibility to false. Subscribing to some Resize/Layout events of the FlowLayoutPanel to keep that panel's size will to the trick. But this does not play nicely at design time. The events won't fire and thus you can't really design the surface the way you want it to look.
I'd prefer a solution that "just works" at design time as well. So, here's an attempt at an "invisible" control that I put together, to fix the controls resizing to zero width if no other control is present. Dropping this as first control onto the FlowLayoutPanel at design time seems to provide the desired effect, and any control subsequently placed on the FlowLayoutPanel is anchorable to the right without shrinking to zero width. The only problem is that, once this invisible control is there, it seems I can't remove it anymore via the IDE. It probably needs some special treatment using a ControlDesigner to achieve that. It can still be removed in the form's designer code though.
This control, once placed onto the FlowLayoutPanel, will listen for resize events of it's parent control, and resize itself according to the ClientSize of the parent control. Use with caution, as this may contain pitfalls that didn't occur to me during the few hours I played with this. For example, I didn't try placing controls that were wider than the FlowLayoutPanel's client area.
As a side note, what will still fail is trying to anchor to the bottom, but that wasn't part of the question ;-)
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
namespace ControlTest
{
public sealed class InvisibleControl : Control
{
public InvisibleControl()
{
TabStop = false;
}
#region public interface
// Reduce the temptation ...
public new AnchorStyles Anchor
{
get { return base.Anchor; }
set { base.Anchor = AnchorStyles.None; }
}
public new DockStyle Dock
{
get { return base.Dock; }
set { base.Dock = DockStyle.None; }
}
// We don't ever want to move away from (0,0)
public new Point Location
{
get { return base.Location; }
set { base.Location = Point.Empty; }
}
// Horizontal or vertical orientation?
private Orientation _orientation = Orientation.Horizontal;
[DefaultValue(typeof(Orientation), "Horizontal")]
public Orientation Orientation
{
get { return _orientation; }
set
{
if (_orientation == value) return;
_orientation = value;
ChangeSize();
}
}
#endregion
#region overrides of default behaviour
// We don't want any margin around us
protected override Padding DefaultMargin => Padding.Empty;
// Clean up parent references
protected override void Dispose(bool disposing)
{
if (disposing)
SetParent(null);
base.Dispose(disposing);
}
// This seems to be needed for IDE support, as OnParentChanged does not seem
// to fire if the control is dropped onto a surface for the first time
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ChangeSize();
}
// Make sure we don't inadvertantly paint anything
protected override void OnPaint(PaintEventArgs e) { }
protected override void OnPaintBackground(PaintEventArgs pevent) { }
// If the parent changes, we need to:
// A) Unsubscribe from the previous parent's Resize event, if applicable
// B) Subscribe to the new parent's Resize event
// C) Resize our control according to the new parent dimensions
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
// Perform A+B
SetParent(Parent);
// Perform C
ChangeSize();
}
// We don't really want to be resized, so deal with it
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
ChangeSize();
}
#endregion
#region private stuff
// Make this a default handler signature with optional params, so that this can
// directly subscribe to the parent resize event, but also be called without parameters
private void ChangeSize(object sender = null, EventArgs e = null)
{
Rectangle client = Parent?.ClientRectangle ?? new Rectangle(0, 0, 10, 10);
Size proposedSize = _orientation == Orientation.Horizontal
? new Size(client.Width, 0)
: new Size(0, client.Height);
if (!Size.Equals(proposedSize)) Size = proposedSize;
}
// Handles reparenting
private Control boundParent;
private void SetParent(Control parent)
{
if (boundParent != null)
boundParent.Resize -= ChangeSize;
boundParent = parent;
if (boundParent != null)
boundParent.Resize += ChangeSize;
}
#endregion
}
}

Resizing a PropertyGrid control

I have written code that will resize a Control and all of its controls, but there's a problem with the PropertyGrid. The user interface is a GroupBox that contains the TabControl tabContAll. In tabContAll is a TabPage that contains a PropertyGrid.
private void ResizeUI ()
{
ui.Location = new Point (this.ClientRectangle.Left, this.ClientRectangle.Top + menubar.Height);
ui.Size = new Size (this.ClientRectangle.Width, this.ClientRectangle.Height - menubar.Height);
ResizeControl (tabContAll, ui);
}
private void ResizeControl (Control control, Control parent)
{
control.Location = new Point (parent.ClientRectangle.Left, parent.ClientRectangle.Top);
control.Size = new Size (parent.ClientRectangle.Width, parent.ClientRectangle.Height);
foreach (Control child in control.Controls) {
ResizeControl (child, control);
}
}
This function is called when the form loads, and this is what it looks like compared to if I commented out the resizing in the loop so the PropertyGrid doesn't get resized:
Furthermore when it is resized, the description doesn't work. It just shows the name of the property.
I strongly recommend you to not write code for controls resizing unless you need a very very custom behaviour.
Set Control.Dock or Control.Anchor properties instead, and leave the rest to them.
For example, your case can be solved easily by setting the Dock property to DockStyle.Fill for both your TabControl and PropertyGrid (and obviously removing the custom resizing methods).
Here's a complete MSDN Walkthrough for WinForms custom controls design:
http://msdn.microsoft.com/en-us/library/6hws6h2t.aspx

Categories

Resources