I wrote User Control (yay!). But I want it to behave as a container. But wait! I know about
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design",
typeof(IDesigner))]
Trick.
The problem is - I don't want all of my control to behave like container, but only one part. One - de facto - panel ;)
To give wider context: I wrote a control that has Grid, some common buttons, labels and functionalities. But it also has a part where the user is supposed to drop his custom buttons/controls whatever. Only in this particular part of the control, nowhere else.
Anyone had any idea?
You should do the following :
For your user control, you need to create a new designer which enables the inner panel on design-time by calling EnableDesignMode method.
For the inner panel, you need to create a designer which disables moving, resizing and removes some properties from designer.
You should register the designers.
Example
You can read a blog post about this topic here and clone or download a working example:
r-aghaei/ChildContainerControlDesignerSample
Download Zip
Code
Here is the code for different elements of the solution.
Your user control
[Designer(typeof(MyUserControlDesigner))]
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
TypeDescriptor.AddAttributes(this.panel1,
new DesignerAttribute(typeof(MyPanelDesigner)));
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Panel ContentsPanel
{
get { return panel1; }
}
}
Designer for the inner panel
public class MyPanelDesigner : ParentControlDesigner
{
public override SelectionRules SelectionRules
{
get
{
SelectionRules selectionRules = base.SelectionRules;
selectionRules &= ~SelectionRules.AllSizeable;
return selectionRules;
}
}
protected override void PostFilterAttributes(IDictionary attributes)
{
base.PostFilterAttributes(attributes);
attributes[typeof(DockingAttribute)] =
new DockingAttribute(DockingBehavior.Never);
}
protected override void PostFilterProperties(IDictionary properties)
{
base.PostFilterProperties(properties);
var propertiesToRemove = new string[] {
"Dock", "Anchor", "Size", "Location", "Width", "Height",
"MinimumSize", "MaximumSize", "AutoSize", "AutoSizeMode",
"Visible", "Enabled",
};
foreach (var item in propertiesToRemove)
{
if (properties.Contains(item))
properties[item] = TypeDescriptor.CreateProperty(this.Component.GetType(),
(PropertyDescriptor)properties[item],
new BrowsableAttribute(false));
}
}
}
Designer for your user control
public class MyUserControlDesigner : ParentControlDesigner
{
public override void Initialize(IComponent component)
{
base.Initialize(component);
var contentsPanel = ((MyUserControl)this.Control).ContentsPanel;
this.EnableDesignMode(contentsPanel, "ContentsPanel");
}
public override bool CanParent(Control control)
{
return false;
}
protected override void OnDragOver(DragEventArgs de)
{
de.Effect = DragDropEffects.None;
}
protected override IComponent[] CreateToolCore(ToolboxItem tool, int x,
int y, int width, int height, bool hasLocation, bool hasSize)
{
return null;
}
}
Related
I'm trying to learn how to use ITemplate for nicer custom controls. I have it mostly working but I haven't been able to figure out how to access any properties of the container from the page.
Here is my templated control:
[ParseChildren(true)]
[PersistChildren(false)]
public partial class Example : UserControl
{
private ITemplate _CustomPanelContainer;
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(CustomPanelContainer))]
[TemplateInstance(TemplateInstance.Single)]
public virtual ITemplate CustomPanel
{
get { return _CustomPanelContainer; }
set { _CustomPanelContainer = value; }
}
protected override void CreateChildControls()
{
Controls.Clear();
if (_CustomPanelContainer != null)
{
var p = new Panel();
p.ID = "CustomPanel";
Controls.Add(p);
_CustomPanelContainer.InstantiateIn(p);
}
base.CreateChildControls();
}
public class CustomPanelContainer : Panel, INamingContainer
{
private string _Test = "TESTING!";
public string TextTest
{
get
{
return _Test;
}
set
{
_Test = value;
}
}
}
}
Here is the page implementation:
<uc1:Example runat="server" ID="Example1">
<CustomPanel>
<strong>Test: </strong> <%# Container.TextTest %>
</CustomPanel>
</uc1:Example>
It is mostly working but the problem is that <%# Container.TextTest %> always returns an empty string. When I run it on the debugger, I put a breakpoint at the line inside the TextTest property of CustomPanelContainer and the breakpoint is never hit, so the property is never actually being accessed.
What am I missing here? How do I enable access to the container's public properties via <%#Container ?
I finally figured out how to make it act the way I want.
I removed ITemplate as the type of the Container and set the type as the actual type and added a DataBind() command to CreateChildControls().
Maybe not quite the correct way to do this, but it works.
Keeping the question open for a bit to see if anyone offers any critique or a better approach, since I really don't know what I'm doing here yet.
Simplified Working code:
[ParseChildren(true)]
[PersistChildren(false)]
public partial class Example : UserControl
{
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateInstance(TemplateInstance.Single)]
public virtual CustomPanelContainer Template { get; set; }
protected override void CreateChildControls()
{
Controls.Clear();
if (Template != null)
{
Template.DataBind();
Controls.Add(Template);
}
base.CreateChildControls();
}
public class CustomPanelContainer : Panel, INamingContainer
{
public string TextTest
{
get { return "TESTING!"; }
}
}
}
Page Implementation:
<uc1:Example runat="server" ID="Example">
<Template>
<strong>Test: </strong><span><%# Container.TextTest %></span>
</Template>
</uc1:Example>
EDIT: This also works when needing to hide the type of the template.
i,e., the code above exposes the type of Template to allow manipulating properties of the Panel as attributes of Template, whereas the code below hides the type of Template to block manipulation of its properties.
[ParseChildren(true)]
[PersistChildren(false)]
public partial class Example : UserControl
{
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateInstance(TemplateInstance.Single)]
[TemplateContainer(typeof(CustomPanelContainer))]
public virtual ITemplate Template { get; set; }
protected override void CreateChildControls()
{
Controls.Clear();
if (Template != null)
{
var p = new CustomPanelContainer();
Template.InstantiateIn(p);
p.DataBind();
Controls.Add(p);
}
base.CreateChildControls();
}
public class CustomPanelContainer : Panel, INamingContainer
{
public string TextTest
{
get { return "TESTING!"; }
}
}
I wrote User Control (yay!). But I want it to behave as a container. But wait! I know about
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design",
typeof(IDesigner))]
Trick.
The problem is - I don't want all of my control to behave like container, but only one part. One - de facto - panel ;)
To give wider context: I wrote a control that has Grid, some common buttons, labels and functionalities. But it also has a part where the user is supposed to drop his custom buttons/controls whatever. Only in this particular part of the control, nowhere else.
Anyone had any idea?
You should do the following :
For your user control, you need to create a new designer which enables the inner panel on design-time by calling EnableDesignMode method.
For the inner panel, you need to create a designer which disables moving, resizing and removes some properties from designer.
You should register the designers.
Example
You can read a blog post about this topic here and clone or download a working example:
r-aghaei/ChildContainerControlDesignerSample
Download Zip
Code
Here is the code for different elements of the solution.
Your user control
[Designer(typeof(MyUserControlDesigner))]
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
TypeDescriptor.AddAttributes(this.panel1,
new DesignerAttribute(typeof(MyPanelDesigner)));
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Panel ContentsPanel
{
get { return panel1; }
}
}
Designer for the inner panel
public class MyPanelDesigner : ParentControlDesigner
{
public override SelectionRules SelectionRules
{
get
{
SelectionRules selectionRules = base.SelectionRules;
selectionRules &= ~SelectionRules.AllSizeable;
return selectionRules;
}
}
protected override void PostFilterAttributes(IDictionary attributes)
{
base.PostFilterAttributes(attributes);
attributes[typeof(DockingAttribute)] =
new DockingAttribute(DockingBehavior.Never);
}
protected override void PostFilterProperties(IDictionary properties)
{
base.PostFilterProperties(properties);
var propertiesToRemove = new string[] {
"Dock", "Anchor", "Size", "Location", "Width", "Height",
"MinimumSize", "MaximumSize", "AutoSize", "AutoSizeMode",
"Visible", "Enabled",
};
foreach (var item in propertiesToRemove)
{
if (properties.Contains(item))
properties[item] = TypeDescriptor.CreateProperty(this.Component.GetType(),
(PropertyDescriptor)properties[item],
new BrowsableAttribute(false));
}
}
}
Designer for your user control
public class MyUserControlDesigner : ParentControlDesigner
{
public override void Initialize(IComponent component)
{
base.Initialize(component);
var contentsPanel = ((MyUserControl)this.Control).ContentsPanel;
this.EnableDesignMode(contentsPanel, "ContentsPanel");
}
public override bool CanParent(Control control)
{
return false;
}
protected override void OnDragOver(DragEventArgs de)
{
de.Effect = DragDropEffects.None;
}
protected override IComponent[] CreateToolCore(ToolboxItem tool, int x,
int y, int width, int height, bool hasLocation, bool hasSize)
{
return null;
}
}
I'm building custom control by extending ScrollableControl.
Problem is that my custom control acts as container - I can drag controls into it:
My question is how can I disable container functionality in class that extends ScrollableControl
Below are two test controls, one extends Control, second ScrollableControl
public class ControlBasedControl : Control
{
protected override Size DefaultSize
{
get { return new Size(100, 100); }
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(Brushes.LightCoral, ClientRectangle);
}
}
public class ScrollableControlBasedControl : ScrollableControl
{
public ScrollableControlBasedControl()
{
AutoScrollMinSize = new Size(200, 200);
}
protected override Size DefaultSize
{
get { return new Size(100, 100); }
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(Brushes.LawnGreen, ClientRectangle);
}
}
You get "acts-like-a-container" behavior at design time from the [Designer] attribute. Copy-pasting from the Reference Source:
[
ComVisible(true),
ClassInterface(ClassInterfaceType.AutoDispatch),
Designer("System.Windows.Forms.Design.ScrollableControlDesigner, " + AssemblyRef.SystemDesign)
]
public class ScrollableControl : Control, IArrangedElement {
// etc...
}
It is ScrollableControlDesigner that gets the job done. Doesn't do much by itself, but derived from ParentControlDesigner, the designer that permits a control to act as a parent for child controls and gives it container-like behavior at design time.
Fix is easy, you just have to use your own [Designer] attribute to select another designer. Add a reference to System.Design and make it look like this:
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms.Design; // Add reference to System.Design
[Designer(typeof(ControlDesigner))]
public class ScrollableControlBasedControl : ScrollableControl {
// etc...
}
There is probably more than one way to accomplish this, but here is what I would do...
First create a read-only version of ControlCollection
public class ReadOnlyControlCollection : Control.ControlCollection
{
public ReadOnlyControlCollection(Control owner)
: base(owner)
{
}
public override bool IsReadOnly
{
get { return true; }
}
public override void Add(Control control)
{
throw new ArgumentException("control");
}
}
Then make your ScrollableControlBasedControl create an instance of ReadOnlyControlCollection in stead of the default ControlCollection
public class ScrollableControlBasedControl : ScrollableControl
{
protected override Control.ControlCollection CreateControlsInstance()
{
return new ReadOnlyControlCollection(this);
}
// The rest of your class goes here...
}
I use Visual Studio 2010 and when I drop a control on an ScrollableControlBasedControl the control is magically moved back to where it came from, as if the action was cancelled.
I have created a custom NSView that i would like to place over the top of the content of a window to block any interaction while all the content is loading. The problem i was having is that i could click through the NSView to the controls below though that has now been fixed. The new problem is that even though i cannot click on the controls, when i move the mouse over text controls, the mouse switches to the I Beam icon.
How do i make the NSView completely block all interaction with everything below it?
The NSView i created is below:
[Register("StupidView")]
public class StupidView : NSView
{
public StupidView()
{
// Init
Initialize();
}
public StupidView(IntPtr handle) : base (handle)
{
// Init
Initialize();
}
[Export("initWithFrame:")]
public StupidView(CGRect frameRect) : base(frameRect) {
// Init
Initialize();
}
private void Initialize()
{
this.AcceptsTouchEvents = true;
this.WantsLayer = true;
this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}
public override void DrawRect(CGRect dirtyRect)
{
var ctx = NSGraphicsContext.CurrentContext.GraphicsPort;
ctx.SetFillColor(new CGColor(128, 128, 128, 0.7f));
ctx.FillRect(dirtyRect);
}
public override void MouseDown(NSEvent theEvent)
{
if (Hidden)
{
base.MouseDown(theEvent);
}
}
public override void MouseDragged(NSEvent theEvent)
{
if (Hidden)
{
base.MouseDragged(theEvent);
}
}
public override bool AcceptsFirstResponder()
{
return !this.Hidden;
}
public override bool AcceptsFirstMouse(NSEvent theEvent)
{
return !this.Hidden;
}
public override NSView HitTest(CGPoint aPoint)
{
return Hidden ? null : this;
}
}
I had the same problem a few weeks ago, and here is how I could manage this :
First, to prevent user interactions on the superview placed below, I added a transparent button which was there only to catch the mouse click and, if you don't have to do anything, do nothing :
private void Initialize()
{
this.AcceptsTouchEvents = true;
this.WantsLayer = true;
this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
//Add button to prevent user interactions
NSButton buttonToPreventUserInteraction = new NSButton();
buttonToPreventUserInteraction.Bordered = false;
buttonToPreventUserInteraction.Transparent = true;
buttonToPreventUserInteraction.TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview(buttonToPreventUserInteraction);
//If you want to add some constraints on the button, for it to resize and keep the same size of your subview
var dicoViews = new NSMutableDictionary();
dicoViews.Add((NSString)"buttonToPreventUserInteraction", buttonToPreventUserInteraction);
NSLayoutConstraint[] buttonToPreventUserInteractionHorizontalConstraints = NSLayoutConstraint.FromVisualFormat("H:|[buttonToPreventUserInteraction]|", NSLayoutFormatOptions.DirectionLeadingToTrailing, null, dicoViews);
NSLayoutConstraint[] buttonToPreventUserInteractionVerticalConstraints = NSLayoutConstraint.FromVisualFormat("V:|[buttonToPreventUserInteraction]|", NSLayoutFormatOptions.DirectionLeadingToTrailing, null, dicoViews);
AddConstraints(buttonToPreventUserInteractionHorizontalConstraints);
AddConstraints(buttonToPreventUserInteractionVerticalConstraints);
}
For your other problem, which is the mouse cursor changing from the content in your superview placed below, you can add a NSTrackingArea on your subview, and implement the override method "MouseMoved" to change the cursor. You can do something like this :
First Add the NSTrackingArea on your subview (you can put this code in your "Initialize" method)
NSTrackingAreaOptions opts = ((NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.InVisibleRect));
var trackingArea = new NSTrackingArea(new CGRect(0, 0, FittingSize.Width, FittingSize.Height), opts, Self, null);
AddTrackingArea(trackingArea);
And then implement the override method :
public override void MouseMoved(NSEvent theEvent)
{
//You can choose the type of cursor you want to use here
NSCursor.ArrowCursor.Set();
}
This made it for me, hope it will for you too
I've created my custom control. It has a property that is called "Tab." This property adds a collection of "FloorsInformation" controls that are inherited from "DockContainerItem" class to my custom control.
Now, I want to add "FloorsInformation" controls to my custom control after click the "OK" button of the Tab "CollectionEditor" window.
I have "AddTabs" method for doing that. However, I can't call it in the right place. I must call "AddTabs" method in "set accessor" of the "Tab" property, but it never occurs.
I also can call this method from "get accessor" of the "Tab" property, but calling this method in "get accessor" of the "Tab" property will result in an error, because the program access to the "get accessor" of the "Tab" property continuously.
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
[ToolboxItem(true), ToolboxBitmap(typeof(ToolboxIconResourceFinder), "FloorsGrouping.bmp")]
[DisplayName("Floors Group")]
[Editor("WindowsFormsControlLibrary2.FloorsGrouping, WindowsFormsControlLibrary2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=197889249da45bfc", typeof(UITypeEditor))]
[Description("Floorssssssss")]
[Category("Saino")]
[DefaultProperty("Text")]
[DesignerCategory("Component")] //Form //Designer //Empty String ("")
public partial class FloorsGrouping : Bar
{
private Tabs tabs = new Tabs();
public FloorsGrouping()
{
InitializeComponent();
this.AutoHide = true;
}
[Category("Data")]
[DisplayName("Tabs")]
[Description("Tabsssssssssssss")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(ItemsCollectionEditor), typeof(UITypeEditor))]
public Tabs Tab
{
get
{
//AddTabs();
return tabs;
}
//set
//{
//AddTabs();
//}
}
public void AddTabs()
{
foreach (DockContainerItem dciItem in Tab)
{
if (!Parent.Controls.ContainsKey(dciItem.Name))
{
Items.Add(dciItem);
}
}
}
}
[DisplayName("Floors Information")]
[Description("Floors Informationnnnnnnnnnnnnnnn")]
[DefaultProperty("Text")]
[DesignerCategory("Component")]
[ToolboxItem(false)]
public class FloorsInformation : DockContainerItem
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
private SimilarFloorsInformation similarFloorsInformation = new SimilarFloorsInformation();
private AllFloorsInformation allFloorsInformation = new AllFloorsInformation();
private string text = "Floors Information";
public FloorsInformation()
{
}
[Category("Data")]
[DisplayName("Similar Floors Panel")]
[Description("Similar Floors Panellllllllllllllllllll")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SimilarFloorsInformation SimilarFloorsInfo
{
get
{
return similarFloorsInformation;
}
set
{
similarFloorsInformation = value;
}
}
[Category("Data")]
[DisplayName("All Floors Group")]
[Description("All Floors Groupppppppppppppp")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public AllFloorsInformation AllFloorsInfo
{
get
{
return allFloorsInformation;
}
set
{
allFloorsInformation = value;
}
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
}
public class Tabs : CollectionBase
{
public FloorsInformation this[int intIndex]
{
get
{
return (FloorsInformation)InnerList[intIndex];
}
set
{
InnerList[intIndex] = value;
}
}
public int Add(FloorsInformation finfItemType)
{
return InnerList.Add(finfItemType);
}
public bool Contains(FloorsInformation finfItemType)
{
return InnerList.Contains(finfItemType);
}
public void Remove(FloorsInformation finfItemType)
{
InnerList.Remove(finfItemType);
}
public void Insert(int intIndex, FloorsInformation finfItemType)
{
InnerList.Insert(intIndex, finfItemType);
}
public FloorsInformation[] GetValues()
{
FloorsInformation[] finfItemType = new FloorsInformation[InnerList.Count];
InnerList.CopyTo(0, finfItemType, 0, InnerList.Count);
return finfItemType;
}
}
By the way, I can call this method in "SetItems" overrode method of the "ItemsCollectionEditor" class that is inherited from "CollectionEditor" class; nevertheless, I can't access to "AddTabs" method without create a new instance of my custom control class. If I create a new instance of my custom control, "AddTabs" method applies changes on a new control of my custom control and not on the current added custom control in the WinForm.
public class ItemsCollectionEditor : CollectionEditor
{
private Type[] typItems;
public ItemsCollectionEditor(Type typItem)
: base(typItem)
{
typItems = new Type[] { typeof(FloorsInformation) };
}
protected override Type[] CreateNewItemTypes()
{
return typItems;
}
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
collectionForm.Text = "Tabs Collection Editor";
return collectionForm;
//return base.CreateCollectionForm();
}
protected override object SetItems(object editValue, object[] value)
{
return base.SetItems(editValue, value);
}
}
What must I do for achieve to my goal?
You have a couple of options.
Option 1:
If you are just wanting to expose the FloorsGrouping.Items property at design time, you can change the Tab property's type to SubItemsCollection and return the Items property. In this case, you won't have to worry about intercepting any collection change events, it will happen for you automatically.
[Category("Data")]
[DisplayName("Tabs")]
[Description("Tabsssssssssssss")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(ItemsCollectionEditor), typeof(UITypeEditor))]
public SubItemsCollection Tab {
get {
return Items;
}
}
Option 2:
If you are needing to intercept the collection change events, modify the Tabs class to inherit from ObservableCollection<FloorsInformation>, which implements INotifyCollectionChanged.
public class Tabs : System.Collections.ObjectModel.ObservableCollection<FloorsInformation> {
}
And in your FloorsGrouping constructor, subscribe to the CollectionChanged event.
public FloorsGrouping() {
...
tabs.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(tabs_CollectionChanged);
}
Finally, in your event handler, process the collection change.
private void tabs_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
foreach (DockContainerItem dciItem in e.NewItems) {
if (!Parent.Controls.ContainsKey(dciItem.Name))
Items.Add(dciItem);
}
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
Items.Clear();
break;
}
}
What you'll notice with option 2 is that the CollectionChanged event fires in real time with the editing in the collection editor, not specifically when the OK button is clicked. However, when the user finally clicks the OK or Cancel button in the collection editor, the collection's state is accurate.