Container UserControl - Handle and Modify Added Controls - c#

I'm trying to create a custom container as UserControl.
My Goal: I want to be able to drag controls inside the designer and handle incoming controls inside the code of my usercontrol.
Example: I place my container somewhere and then add a button. In this momemt I want my usercontrol to automatically adjust the width and position of this button. Thats the point where Im stuck.
My code:
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public partial class ContactList : UserControl
{
public ContactList()
{
InitializeComponent();
}
private void ContactList_ControlAdded(object sender, ControlEventArgs e)
{
e.Control.Width = 200; // Nothing happens
e.Control.Height = 100; // Nothing happens
MessageBox.Show("Test"); // Firing when adding a control
}
}
The MessageBox is working well. The set width and height is ignored.
The question is just "why?".
EDIT
I've just noticed, when placing the button and recompiling with F6 the button gets resized to 200x100. Why isnt this working when placing?
I mean... the FlowLayoutPanel handles added controls right when you place it. Thats the exact behaviour im looking for.

Using OnControlAdded
To fix your code, when you drop a control on container and you want to set some properties in OnControlAdded you should set properties using BeginInvoke, this way the size of control will change but the size handles don't update. Then to update the designer, you should notify the designer about changing size of the control, using IComponentChangeService.OnComponentChanged.
Following code executes only when you add a control to the container. After that, it respects to the size which you set for the control using size grab handles. It's suitable for initialization at design-time.
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (this.IsHandleCreated)
{
base.BeginInvoke(new Action(() =>
{
e.Control.Size = new Size(100, 100);
var svc = this.GetService(typeof(IComponentChangeService))
as IComponentChangeService;
if (svc != null)
svc.OnComponentChanged(e.Control,
TypeDescriptor.GetProperties(e.Control)["Size"], null, null);
}));
}
}

Related

How does a child control get notified when its parent changes a visual property in the designer?

I have a UserControl that hosts a DataGridView component with a header and one data row.
I'm having an issue trying to notify the DataGridView when its container changes certain visual properties in the designer.
I override the BackColor property of the base UserControl to set the BackColor of the data row in the grid. The goal is that when I set the BackColor of the UserControl’s container it will trickle down to the Grid.
If I explicitly set the UserControl’s BackColor property, the design and runtime DataGridControl follows correctly.
If I just change the BackColor in the container what I have works at runtime, but is intermittent at design time.
Dropping the control on the container the data row BackColor follows the container color.
Changing the container’s BackColor doesn’t call the UserControl’s property setter to change the data row in the designer.
Changing the container does change other custom components I have created using label on a TableLayoutPanel. I don't set all labels to match the container so it must be using the property setter to get it right. They if I put a MessageBox.Show("Setter Called"); in the property setter it doesn't always happen. in either the working or my target non-working control.
When I run the project the executable does change the row to match. As a bonus the designer color changes right after the executable is displayed and it remains the matching color even after I end the program.
I also tried the ParentChanged event on the UserControl but it only gets called when the program runs or I add or delete the control from the form. Just like the other properties. Never from changing the container in the IDE.
Using the Load event made the code work at RunTime.
Current code:
public JobPanel()
{
InitializeComponent();
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
dataGridView1.RowCount = 1;
dataGridView1.BorderStyle = System.Windows.Forms.BorderStyle.None;
}
[BrowsableAttribute(true)]
public override Color BackColor { get { return base.BackColor; } set { SetBackColor(value); } }
private void SetBackColor(Color value)
{
if (BackColor != value) { //Makes no difference if we remove this test
base.BackColor = value;
if (dataGridView1 != null) {
dataGridView1.Rows[0].DefaultCellStyle.BackColor = BackColor;
dataGridView1.Rows[0].DefaultCellStyle.SelectionBackColor = BackColor;
}
}
}
private void JobPanel_Load(object sender, EventArgs e)
{
dataGridView1.DefaultCellStyle.BackColor = base.BackColor;
dataGridView1.DefaultCellStyle.ForeColor = base.ForeColor;
}

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

Custom control resize C#

I would like to resize custom control according to items it content
This dont work for me:
public CustomControl()
{
InitializeComponent();
if (ErrorLimits == false && Range == false)
{
this.Size = new Size(100, 100);
this.Invalidate();
}
else
{
this.Size = new Size(250,250);
this.Invalidate();
}
}
It changing nothing, How can I achieve it?
Thanks!
The containing form will instantiate CustomControl and then set its properties in the form's InitializeComponent function. The property values set in the form's designer are applied after the constructor to CustomControl has finished (which, if you think about it, they'd have to be).
Since you are setting your custom sizes in the control's constructor, they're probably getting overridden by the designer values immediately afterwards before the form is displayed.
A better place to set the size is the UserControl.Load event, which occurs after the designer properties have been set.
An even better option would be to properly support auto sizing.

smooth scrolling .net forms

Hi I am using forms in .net and i am adding lots of linked labels dynamically during runtime,
I am adding these linklabels to panel and adding that panel to the winform. When the no of linklabels increases the form puts out an auto scrollbar(vertical)...
Now when i scroll down using that autoscroll the form is not updating its view as i scroll, the form gets refreshed only when i stop scrolling...
Also when it refresh it looks too bad.. i can see how it draws slowly....
Has anyone dealt with this before??
I tried form.refresh() in scroll event handler but that doesn't seem to help..
Any clues?
Pop this into your class (UserControl, Panel, etc) , then it will work with thumb drag.
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
protected override void WndProc (ref Message m)
{
if ((m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL)
&& (((int)m.WParam & 0xFFFF) == 5))
{
// Change SB_THUMBTRACK to SB_THUMBPOSITION
m.WParam = (IntPtr)(((int)m.WParam & ~0xFFFF) | 4);
}
base.WndProc (ref m);
}
If you don't want to use WinAPI calls, you can do this:
// Add event handler to an existing panel
MyPanel.Scroll += new EventHandler(MyPanelScroll_Handler);
// Enables immediate scrolling of contents
private void MyPanelScroll_Handler(System.Object sender, System.Windows.Forms.ScrollEventArgs e)
{
Panel p = sender As Panel;
if (e.ScrollOrientation == ScrollOrientation.HorizontalScroll) {
p.HorizontalScroll.Value = e.NewValue;
} else if (e.ScrollOrientation == ScrollOrientation.VerticalScroll) {
p.VerticalScroll.Value = e.NewValue;
}
}
Try setting your form's DoubleBuffered property to True.
Update: actually, that probably won't do anything since your controls are on a Panel on your Form. The built-in Panel control doesn't have an exposed DoubleBuffered property, so the way to do it is to add a UserControl name DBPanel to your project, and change the code so that it inherits from Panel instead of UserControl (you can change this manually in the CS file after you add it). When you add the UserControl, the code will look like this:
public partial class DBPanel : UserControl
{
public DBPanel()
{
InitializeComponent();
}
}
Edit it so that it looks like this (change UserControl to Panel and add the "this.DoubleBuffered = true;" line to the constructor):
public partial class DBPanel : Panel
{
public DBPanel()
{
InitializeComponent();
this.DoubleBuffered = true;
}
}
When you build the project, the compiler will barf on a line that begins with "this.AutoScaleMode ... ". Delete this line and rebuild.
You can now use the DBPanel control on your form in place of a regular Panel, and this should take care of your flicker problem.
Update 2: sorry, I didn't read your question closely enough. You're right, the Panel doesn't redraw itself until you let go of the scrollbar's thumb. I think to achieve this effect you'll just have to create your own UserControl.
Basically you'd just have a UserControl with a VScrollBar docked on the right, and a Panel with AutoScroll = false docked on the left taking up the remainder of the space. The Scroll and ValueChanged events of the VScrollBar fire as you move the thumb up and down, so after adding a bunch of LinkLabels to the inner Panel you can use these events to change the Top position of the Panel, and thus achieve the dynamic scrolling effect you're looking for.
It's kind of irritating that the Panel doesn't work this way by default, or even have a setting that enables it.
The simplest way is to refresh the panel during the scroll event.
private void panel1_Scroll(object sender, ScrollEventArgs e)
{
panel1.Refresh();
}

How do I get a control which looks like a TabControl with no tabs?

We have a form which displays media items in tab pages of a tab control, and I'm implementing a feature which allows users to 'pop out' the tab pages into their own forms.
However, when I add the media player to a form rather than a TabPage, the background switches from the gradient fill of a tab page to the plain SystemColors.Control background of the parent form. I need to add the the media player to a control which has the same background as a TabControl, but which doesn't display a tab at the top. I tried adding the media player to the TabControl's control collection, but that just throws an exception.
How do I get a control which looks like a TabControl with no tabs? Should I keep trying to add the media player to a TabControl, or should I try to write a Panel with a custom-drawn background? If the latter, how do I make sure that works with all possible themes?
The questions seems to be about the UseVisbleBackgroundStyle. AFAIK only buttons and TabPages have this property.
The following is a very dirty hack, just to get you started:
1) derive a customControl from Panel and add "using System.Windows.Forms.VisualStyles;"
2) Add the following code
//warning: incomplete, add error checking etc
private readonly VisualStyleElement element = VisualStyleElement.Tab.Body.Normal;
public bool UseVisbleBackgroundStyle { get; set; }
protected override void OnPaint(PaintEventArgs pe)
{
if (UseVisbleBackgroundStyle)
{
var x = new VisualStyleRenderer(element);
x.DrawBackground(pe.Graphics, this.ClientRectangle);
}
else
{
base.OnPaint(pe);
}
}
Thanks to Henk - I eventually went with:
protected override void OnPaintBackground(PaintEventArgs e)
{
if (TabRenderer.IsSupported && Application.RenderWithVisualStyles)
{
TabRenderer.DrawTabPage(pe.Graphics, this.ClientRectangle);
}
else
{
base.OnPaintBackground(pe);
ControlPaint.DrawBorder3D(pe.Graphics, this.ClientRectangle, Border3DStyle.Raised);
}
}
Try creating your own customer UserControl
This answer is modified from another answer site. It does the trick rather cleanly.
In the load event for the window containing the tab control, try:
// TabControl is the name of the tab control in this window.
TabControl.Appearance = TabAppearance.FlatButtons;
TabControl.Multiline = false;
TabControl.SizeMode = TabSizeMode.Fixed;
TabControl.ItemSize = new Size(0,1);
// The tabs are now gone. Select the panel you want to display
TabControl.SelectTab("InProgressTab");

Categories

Resources