I am trying to create a custom panel where I can change the properties using Properties -> Buttons -> [(Collection) ...]:
The following is what I have tried
public class CustomPanel : Panel
{
private List<Button> buttons = new List<Button>();
public List<Button> Buttons
{
get { return buttons; }
set
{
buttons = value;
this.Controls.Clear();
foreach (var button in buttons)
{
button.Size = new Size(200, 30);
this.Controls.Add(button);
}
}
}
}
But when I add a new button using Properties -> Buttons -> [(Collection) ...]: it is not adding it to my panel.
Assuming a CustomPanel class has been added as a custom control, and its base class changed to Panel, you can try the following code.
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
using System.Windows.Forms;
namespace TEST
{
public partial class CustomPanel : Panel
{
public CustomPanel()
{
InitializeComponent();
}
[Editor(typeof(ArrayEditor), typeof(UITypeEditor))]
public Button[] Buttons
{
get { return this.Controls.OfType<Button>().ToArray(); }
set
{
SuspendLayout();
try
{
this.Controls.Clear();
foreach (var button in value)
{
button.Size = new Size(200, 30);
this.Controls.Add(button);
}
}
finally { ResumeLayout(); }
}
}
}
}
Make sure the project has a reference to System.Design.dll, for ArrayEditor to be recognized.
Keep in mind that:
Every Button control added by the designer through the Buttons property will be serialized like it was added in the usual design way.
Adding other (non-Button) controls is still possibile in the usual design way, or programmatically.
this.Controls.Clear will remove any control, even the ones added without using Buttons; you can delete this instruction if you want to preserve them, but you will need a way to recognize the previously existing buttons and replace or keep them as needed.
All properties of any Button object can be edited inside the ArrayEditor form.
You need to handle positioning of the Button controls (both newly added and existing ones) in order to avoid overlapping.
I suggest to use a child FlowLayoutPanel for automatic positioning of the buttons and to prevent anyone to mess directly with the collection of buttons. With this approach, this.Controls will become flowLayoutPanel1.Controls (or similar).
Otherwise, you should override OnControlAdded and OnControlRemoved to be notified when any control is added or removed.
Related
I am deep into using the Winforms designer (System.ComponentModel.Design namespace) in my C#/.NET solution so that my users have access to a form designer within my running application. Much of it works well, but I ran into a very specific problem: I encountered a property on a Microsoft control that appears only during design-time--i.e., for the design-time instance of the control. I want to suppress that property so that users cannot modify it when they place an instance of that control on my program's implementation of the Winform design surface.
Details: When a user drag-and-drops a control from the toolbox to the designer surface, I ensure that the newly added designer instance of the control is selected (so that it present resize handles and so the property grid displays that control's design-time properties). I bind the selected objects on the designer surface to the property grid by using the selection service's GetSelectedComponents() method and assigning the property grid's SelectedObjects property to the result.
Many of the controls on my toolbox are .NET controls. One of them is the .NET Winforms TableLayoutPanel control. When you place an instance of that control on a designer surface, you will see a Columns property in the bound PropertyGrid. I would like to suppress that property so that it doesn't appear in the PropertyGrid.
The issue is that this Columns property doesn't appear to exist in the properties list for the TableLayoutPanel type--so using selectedComponents[0].GetType().GetProperties(), for example, doesn't contain a Columns property. Also, I cannot create a new or override for the existing Columns property because it doesn't appear as an exposed property for the TableLayoutPanel control at design time--thus I cannot decorate it with the Browsable(false) attribute.
I can't seem to leverage PreFilterProperties or PostFilterProperties because I can't interact and customize the TableLayoutPanel's designer.
How can I suppress the Columns designer property for the TableLayoutPanel so that it doesn't appear in the PropertyGrid without having to write my own designer?
If you are trying to avoid writing TableLayoutPanelDesigner yourself, then here is a workaround that I can suggest.
ITypeDescriptorFilterService is the interface which is responsible for calling PreFilterProperties method of the designer. The DesignSurface class has a an instance of an implementation of this interface registered in its ServiceContainer. So you can remove the existing registered instance and register a new instance of your own implementation of ITypeDescriptorFilterService.
In the new implementation, override FilterProperties and do whatever you think is suitable, for example you can check if the type of the component is TableLayoutPanel, then don't call its designer PreFilterProperties.
Then to wrap up the solution, you need to create your own design surface by deriving from DesignSurface class and removing the registered ITypeDescriptorFilterService and registering the desired instance which you created.
Example
Just for your information, the name of the property which you are looking for is ColumnStyles and it has Browsable(false) attribute by default. But the default designer of TableLayoutPanel replaces this property with a browsable version.
What I've done in this example is stopping the designer from making those properties Visible.
First provide a custom implementation of ITypeDescriptorFilterService. The following one is in fact the existing implementation in System.Design assembly which I've changed its FilterProperties method and checked if the component is TableLayoutPanel, I've asked to do nothing.
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
public class TypeDescriptorFilterService : ITypeDescriptorFilterService
{
internal TypeDescriptorFilterService()
{
}
private IDesigner GetDesigner(IComponent component)
{
ISite site = component.Site;
if (site != null)
{
IDesignerHost service = site.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (service != null)
return service.GetDesigner(component);
}
return (IDesigner)null;
}
bool ITypeDescriptorFilterService.FilterAttributes(IComponent component, IDictionary attributes)
{
if (component == null)
throw new ArgumentNullException("component");
if (attributes == null)
throw new ArgumentNullException("attributes");
IDesigner designer = this.GetDesigner(component);
if (designer is IDesignerFilter)
{
((IDesignerFilter)designer).PreFilterAttributes(attributes);
((IDesignerFilter)designer).PostFilterAttributes(attributes);
}
return designer != null;
}
bool ITypeDescriptorFilterService.FilterEvents(IComponent component, IDictionary events)
{
if (component == null)
throw new ArgumentNullException("component");
if (events == null)
throw new ArgumentNullException("events");
IDesigner designer = this.GetDesigner(component);
if (designer is IDesignerFilter)
{
((IDesignerFilter)designer).PreFilterEvents(events);
((IDesignerFilter)designer).PostFilterEvents(events);
}
return designer != null;
}
bool ITypeDescriptorFilterService.FilterProperties(IComponent component, IDictionary properties)
{
if (component == null)
throw new ArgumentNullException("component");
if (properties == null)
throw new ArgumentNullException("properties");
if (typeof(TableLayoutPanel).IsAssignableFrom(component.GetType()))
return true;
IDesigner designer = this.GetDesigner(component);
if (designer is IDesignerFilter)
{
((IDesignerFilter)designer).PreFilterProperties(properties);
((IDesignerFilter)designer).PostFilterProperties(properties);
}
return designer != null;
}
}
Then create a design surface by deriving from DesignSurface:
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
public class MyDesignSurface : DesignSurface
{
public MyDesignSurface() : base()
{
this.ServiceContainer.RemoveService(typeof(ITypeDescriptorFilterService));
this.ServiceContainer.AddService(typeof(ITypeDescriptorFilterService), new TypeDescriptorFilterService());
}
}
Then for example initialize the design surface this way:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var surface = new MyDesignSurface();
var host = (IDesignerHost)surface.GetService(typeof(IDesignerHost));
var selectionService = (ISelectionService)surface.GetService(typeof(ISelectionService));
surface.BeginLoad(typeof(Form));
var root = (Form)host.RootComponent;
var tableLayoutPanel1 = (Control)host.CreateComponent(typeof(TableLayoutPanel), "tableLayoutPanel1");
root.Controls.Add(tableLayoutPanel1);
var view = (Control)surface.View;
view.Dock = DockStyle.Fill;
this.Controls.Add(view);
selectionService.SetSelectedComponents(new[] { tableLayoutPanel1 });
var propertyGrid1 = new PropertyGrid() { Dock = DockStyle.Right, Width = 200 };
this.Controls.Add(propertyGrid1);
propertyGrid1.SelectedObjects = selectionService.GetSelectedComponents().Cast<object>().ToArray();
}
}
Then run your designer application and you will see Columns and Rows properties are hidden as expected.
You need to hide ColumnCount and RowCount properties and also the verbs assigned to editing/adding/removing columns and rows.
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);
}));
}
}
I am new to C#. I am using windows forms and I have Form1 which contains 2 buttons ( one to create user control at run time and the other creates buttons on user control at run time).
This code creates user control and FlowLayoutPanel (to organize button position) if you click add_UserControl button. And then it creates buttons on FlowLayoutPanel if you click Add_Buttons button and it is all done at run time.
Now in Form1 let's say I created user control and FlowLayoutPanel and then created 5 buttons , how can I save the properties/details of this user control with its FlowLayoutPanel and 5 buttons in SQL database so I can use them later when I run the program? I have been thinking about an idea and I reached the internet but no luck.
Any idea? Please help me. Thank you
public partial class Form1 : Form
{
FlowLayoutPanel FLP = new FlowLayoutPanel();
UserControl uc = new UserControl();
private void add_UserControl_Click(object sender, EventArgs e)
{
uc.Height = 700;
uc.Width = 900;
uc.BackColor = Color.Black;
Controls.Add(uc); //add UserControl on Form1
FLP.Height = 600;
FLP.Width = 800;
FLP.BackColor = Color.DimGray;
uc.Controls.Add(FLP); // add FlowLayoutPanel to UserControl
}
private void Add_Buttons_Click(object sender, EventArgs e)
{
//####### add buttons to FlowLayoutPanel ############
Button dynamicButton = new Button();
dynamicButton.Height = 50;
dynamicButton.Width = 200;
dynamicButton.BackColor = Color.Green;
dynamicButton.ForeColor = Color.Blue;
dynamicButton.Text = "";
FLP.Controls.Add(dynamicButton);
}
}
OK, First you need to create a class that will represent one of the buttons with the properties you need.
class MyButton
{
public string ButtonText {get;set;}
}
Everytime you click and create a button, you actually create an object of this class and add it to a collection or list. Then you would have some other code watching over the collection, and every time it gets a new entry, it creates a new button and sets its Button text to the text property. when a member of list is gone, it removes the button.
If you need more properties to be remembered (color, size, font, ...) you add them to the class as well. If you need for other controls, as well, .... you can always create common parent controls.
Simple.
If you want to be able to reload it, you could define the MyButton class as serializable and store it in xml file, and upon build, reload it.
You should watch into WPF and it's MVVM pattern. It's pretty much similar to it. Also have a look into command pattern, usefull pattern when it commes to this.
You can remember the FlowLayoutsPanels in one SQL table and in another table you could save the buttons which belong to these FlowLayoutPanels.
On Form Load or Application Load, you could check if there are already FlowLayoutPanels and correspending Buttons do exist in the SQL db and if yes then create them, else do nothing.
I'm looking to make a custom ContextMenu item which is composed of simply a FlowLayoutPanel containing a Label and a ComboBox. I want to be able to access the properties and events of my custom control at design-time in the designer of visual studio 2013. I want to be able to use my custom control in a MenuStrip, in a ToolStripMenuItem or in a ContextMenu. My actual problem is that everything is working fine if I insert the control directly in a MenuStrip but I can't access the property window of my control when it is in a ToolStripMenuItem or in a ContextMenu. The property window is all white and the control is glitchy in the designer.
So Here is my code(I didn't implement any properties nor any events yet but I should have access to inherited properties and events in property window anyway. In fact, I do when I insert the control in a MenuStrip but not in the other two.)
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace CustomControlLib
{
[ToolStripItemDesignerAvailabilityAttribute(ToolStripItemDesignerAvailability.MenuStrip | ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.ContextMenuStrip)]
public class LblCboxStripControl : ToolStripControlHost
{
private Label controlName;
private ComboBox controlComboBox;
private FlowLayoutPanel controlPanel;
public LblCboxStripControl()
: base(CreateControlInstance())
{
controlPanel = (FlowLayoutPanel)base.Control;
controlPanel.AutoSize = true;
controlPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
controlPanel.BackColor = Color.Transparent;
controlName = new Label();
controlName.BackColor = Color.Transparent;
controlName.AutoSize = true;
controlName.Dock = DockStyle.None;
controlName.Text = "Control name";
controlComboBox = new ComboBox();
controlPanel.Controls.Add(controlName);
controlPanel.Controls.Add(controlComboBox);
}
public static FlowLayoutPanel CreateControlInstance()
{
FlowLayoutPanel flp = new FlowLayoutPanel();
return flp;
}
}
}
I've seen a similar post here:
.NET Custom Control (ToolStripControlHost) Wreaks Havoc on the Designer
but the only solution there is to create a class which inherits form ToolStripControlHost and provides a constructor without arguments doesn't solve my problem at all. I also tried creating new empty projects, restarting VS and putting the class inside the form project or in an extern library doesn't solve it either. I'd also add that there is nothing unusual in the Form.designer file.
I loaded 4 radios buttons and when I run the program and click on them, if i click one, and click another the other ones goes away. What if I wanted to ahve two radio buttons but they did different things?
Group the different choice sets in separate group boxes (or panels, or other container controls, but group boxes are probably what you're after).
MSDN:
Windows Forms RadioButton controls are designed to give users a choice among two or more settings, of which only one can be assigned to a procedure or object. For example, a group of RadioButton controls may display a choice of package carriers for an order, but only one of the carriers will be used. Therefore only one RadioButton at a time can be selected, even if it is a part of a functional group.
You group radio buttons by drawing them inside a container such as a Panel control, a GroupBox control, or a form.
This assumes you have four or more radio buttons on your form. If you have only two, say, and you want to allow the user the possibility of selecting both, use a set of checkboxes.
You should use GroupBox control.
All radio buttons in a GroupBox are mutually exclusive - put 2 radio buttons in one GroupBox, and the other 2 in another GroupBox.
If you don't like your container to be visible - use Panel control instead of GroupBox.
(Just drag and drop)
As you can probably guess, the upper one is a GroupBox, the lower one (which is invisble, but allows only one readioButton within it to be selected) is a panel.
HTH.
I like the concept of grouping RadioButtons in WPF. There is a property GroupName that specifies which RadioButton controls are mutually exclusive (http://msdn.microsoft.com/de-de/library/system.windows.controls.radiobutton.aspx).
So I wrote a derived class for WinForms that supports this feature:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Windows.Forms.VisualStyles;
using System.Drawing;
using System.ComponentModel;
namespace Use.your.own
{
public class AdvancedRadioButton : CheckBox
{
public enum Level { Parent, Form };
[Category("AdvancedRadioButton"),
Description("Gets or sets the level that specifies which RadioButton controls are affected."),
DefaultValue(Level.Parent)]
public Level GroupNameLevel { get; set; }
[Category("AdvancedRadioButton"),
Description("Gets or sets the name that specifies which RadioButton controls are mutually exclusive.")]
public string GroupName { get; set; }
protected override void OnCheckedChanged(EventArgs e)
{
base.OnCheckedChanged(e);
if (Checked)
{
var arbControls = (dynamic)null;
switch (GroupNameLevel)
{
case Level.Parent:
if (this.Parent != null)
arbControls = GetAll(this.Parent, typeof(AdvancedRadioButton));
break;
case Level.Form:
Form form = this.FindForm();
if (form != null)
arbControls = GetAll(this.FindForm(), typeof(AdvancedRadioButton));
break;
}
if (arbControls != null)
foreach (Control control in arbControls)
if (control != this &&
(control as AdvancedRadioButton).GroupName == this.GroupName)
(control as AdvancedRadioButton).Checked = false;
}
}
protected override void OnClick(EventArgs e)
{
if (!Checked)
base.OnClick(e);
}
protected override void OnPaint(PaintEventArgs pevent)
{
CheckBoxRenderer.DrawParentBackground(pevent.Graphics, pevent.ClipRectangle, this);
RadioButtonState radioButtonState;
if (Checked)
{
radioButtonState = RadioButtonState.CheckedNormal;
if (Focused)
radioButtonState = RadioButtonState.CheckedHot;
if (!Enabled)
radioButtonState = RadioButtonState.CheckedDisabled;
}
else
{
radioButtonState = RadioButtonState.UncheckedNormal;
if (Focused)
radioButtonState = RadioButtonState.UncheckedHot;
if (!Enabled)
radioButtonState = RadioButtonState.UncheckedDisabled;
}
Size glyphSize = RadioButtonRenderer.GetGlyphSize(pevent.Graphics, radioButtonState);
Rectangle rect = pevent.ClipRectangle;
rect.Width -= glyphSize.Width;
rect.Location = new Point(rect.Left + glyphSize.Width, rect.Top);
RadioButtonRenderer.DrawRadioButton(pevent.Graphics, new System.Drawing.Point(0, rect.Height / 2 - glyphSize.Height / 2), rect, this.Text, this.Font, this.Focused, radioButtonState);
}
private IEnumerable<Control> GetAll(Control control, Type type)
{
var controls = control.Controls.Cast<Control>();
return controls.SelectMany(ctrl => GetAll(ctrl, type))
.Concat(controls)
.Where(c => c.GetType() == type);
}
}
}
Try placing similar options in a container, like GroupBox
Typically a group of radio buttons is used when only one option applies. If it is valid to select multiple options simultaneously, use Checkboxes instead.
Put all radio buttons into a group box.
Now, on all radio button Properties, set Auto Check to false.
Alternatively, you can set
radioButton1->AutoCheck = false;
Inside the radioButton1_Click function, handle as you needed. Example:
Void radioButton1_Click(Object^ sender, EventArgs^ e) {
radioButton1->Enabled = true;
radioButton2->Enabled = true;
radioButton3->Enabled = true;
radioButton1->Checked = true;
radioButton2->Checked = true;
radioButton3->Checked = true;
}
This should make those selected radio buttons to check.