I've got some code that will generically get all Controls in a form and put them in a list. Here's some of the code:
private List<Control> GetControlList(Form parentForm)
{
List<Control> controlList = new List<Control>();
AddControlsToList(parentForm.Controls, controlList);
return controlList;
}
private void AddControlsToList(Control.ControlCollection rootControls, List<Control> controlList)
{
foreach (Control c in rootControls)
{
controlList.Add(c);
if (c.HasChildren)
AddControlsToList(c.Controls, controlList);
//
}
}
So I'm only able to use c.HasChildren to check and see if there's any more child controls from this root control.
What about a menuStrip, toolStrip, and statusStrip? How do I get all of the controls that are in these controls generically? Ex: MenuStripItem
I know that I could try testing the c.GetType() == typeof(MenuStrip) but I was hoping to not have to do specific type tests.
If I need to give more info, please ask.
Thanks a bunch
I believe the VS designer does it by getting an instance of the control's designer (see the Designer attribute), and, if the designer is a ComponentDesigner, getting the AssociatedComponents property.
EDIT:
Okay, I guess that's a little vague. A warning, though: what follows is a little complicated, and might not be worth the effort.
A note on nomenclature:
Below, I will be referring to both the designer within Visual Studio—which is the name used to refer to the functionality within Visual Studio by which the layout and content of forms and controls are edited visually—and to designer classes—which will be explained below. To prevent confusion as to which I am referring to at any given time, I will always refer to the designer functionality within Visual Studio as "the designer", and I will always refer to a designer class as an "IDesigner", which is the interface each must implement.
When the Visual Studio designer loads a component (usually a control, but also things like Timer and such), it looks for a custom attribute on the class of type DesignerAttribute. (Those unfamiliar with attributes might want read up on them before continuing.)
This attribute, if present, provides the name of a class—an IDesigner—the designer can use to interface with the component. In effect, this class controls certain aspects of the designer and of the design-time behavior of the component. There's indeed quite a lot you can do with an IDesigner, but right now we're only interested in one thing.
Most controls that use a custom IDesigner use one that derives from ControlDesigner, which itself derives from ComponentDesigner. The ComponentDesigner class has a public virtual property called AssociatedComponents, which is meant to be overridden in derived classes to return a collection of references to all "child" components of this one.
To be more specific, the ToolStrip control (and by inheritance, the MenuStrip control) has a DesignerAttribute that references a class called ToolStripDesigner. It looks sort of like:
/*
* note that in C#, I can refer to the "DesignerAttribute" class within the [ brackets ]
* by simply "Designer". The compiler adds the "Attribute" to the end for us (assuming
* there's no attribute class named simply "Designer").
*/
[Designer("System.Windows.Forms.Design.ToolStripDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), ...(other attributes)]
public class ToolStrip : ScrollableControl, IArrangedElement, ...(other interfaces){
...
}
The ToolStripDesigner class is not public. It's internal to System.Design.dll. But since it's specified here by it's fully qualified name, the VS designer can use Activator.CreateInstance to create an instance of it anyway.
This ToolStripDesigner class, because it inherits [indirectly] from ComponentDesigner has an AssociatedComponents property. When you call it you get a new ArrayList that contains references to all the items that have been added to the ToolStrip.
So what would your code have to look like to do the same thing? Rather convoluted, but I think I have a working example:
/*
* Some controls will require that we set their "Site" property before
* we associate a IDesigner with them. This "site" is used by the
* IDesigner to get services from the designer. Because we're not
* implementing a real designer, we'll create a dummy site that
* provides bare minimum services and which relies on the framework
* for as much of its functionality as possible.
*/
class DummySite : ISite, IDisposable{
DesignSurface designSurface;
IComponent component;
string name;
public IComponent Component {get{return component;}}
public IContainer Container {get{return designSurface.ComponentContainer;}}
public bool DesignMode{get{return false;}}
public string Name {get{return name;}set{name = value;}}
public DummySite(IComponent component){
this.component = component;
designSurface = new DesignSurface();
}
~DummySite(){Dispose(false);}
protected virtual void Dispose(bool isDisposing){
if(isDisposing)
designSurface.Dispose();
}
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
public object GetService(Type serviceType){return designSurface.GetService(serviceType);}
}
static void GetComponents(IComponent component, int level, Action<IComponent, int> action){
action(component, level);
bool visible, enabled;
Control control = component as Control;
if(control != null){
/*
* Attaching the IDesigner sets the Visible and Enabled properties to true.
* This is useful when you're designing your form in Visual Studio, but at
* runtime, we'd rather the controls maintain their state, so we'll save the
* values of these properties and restore them after we detach the IDesigner.
*/
visible = control.Visible;
enabled = control.Enabled;
foreach(Control child in control.Controls)
GetComponents(child, level + 1, action);
}else visible = enabled = false;
/*
* The TypeDescriptor class has a handy static method that gets
* the DesignerAttribute of the type of the component we pass it
* and creates an instance of the IDesigner class for us. This
* saves us a lot of trouble.
*/
ComponentDesigner des = TypeDescriptor.CreateDesigner(component, typeof(IDesigner)) as ComponentDesigner;
if(des != null)
try{
DummySite site;
if(component.Site == null)
component.Site = site = new DummySite(component);
else site = null;
try{
des.Initialize(component);
foreach(IComponent child in des.AssociatedComponents)
GetComponents(child, level + 1, action);
}finally{
if(site != null){
component.Site = null;
site.Dispose();
}
}
}finally{des.Dispose();}
if(control != null){
control.Visible = visible;
control.Enabled = enabled;
}
}
/* We'll use this in the ListComponents call */
[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
const int WM_SETREDRAW = 11;
void ListComponents(){
/*
* Invisible controls and disabled controls will be temporarily shown and enabled
* during the GetComponents call (see the comment within that call), so to keep
* them from showing up and then disappearing again (or appearing to temporarily
* change enabled state), we'll disable redrawing of our window and re-enable it
* afterwards.
*/
SendMessage(Handle, WM_SETREDRAW, 0, 0);
GetComponents(this, 0,
/* You'll want to do something more useful here */
(component, level)=>System.Diagnostics.Debug.WriteLine(new string('\t', level) + component));
SendMessage(Handle, WM_SETREDRAW, 1, 0);
}
The items such as ToolStripItem etc aren't actually controls, they are simply components that make up a ToolStrip or MenuStrip.
Which means, that if you want to include those components in your flattened list of controls then you will need to do the specific checks.
ToolStripControlHost might contain a Control:
if (c is ToolStrip)
foreach (ToolStripItem item in EnumerateTree(c, "Items"))
if (item is ToolStripControlHost)
AddControlsToList(
new Control[] { ((ToolStripControlHost)item).Control },
controlList);
...that's if you change argument 1 to type IEnumerable<Control> and write your own EnumerateTree function (I think it's great to have one good generic EnumerateTree method).
Related
I want to enable/disable controls in a Windows Forms application according to the user privileges.
Initially I thought of writing a method in each form class that would check the user credentials and then enable/disable its controls. But then I realized I could (maybe) create a static class method which would take the form as a parameter and do the job.
So I started writing it, presuming that sometimes I would like to enable the controls of just one or two panels, instead of the whole form. So, I need the parameters to be:
a varying number of panels and/or
a form class.
My difficulties with this task is that I'm getting an error trying to make the panels argument varying, and I have no idea how to set a parameter that could take any form class. All my form classes obviously inherits from Form generic class, but I don't know how to apply this.
Here's what I got:
public static void Enable(TableLayoutPanel[] containers = null)
{
if (MyOF.isEnabled)
{
return;
}
else
{
try
{
foreach (TableLayoutPanel table in containers)
{
foreach (Control control in table.Controls)
{
control.Enabled = false;
}
}
}
catch (NullReferenceException)
{
}
}
}
If we remember that the Form class derives from Control (indirectly, by deriving from ContainerControl which derives from ScrollableControl, which derives from Control), and the Enabled property belongs to the Control class, we can write a method that will enable any control's children (including the Form or TableLayoutPanel controls), since the Controls collection also belongs to the Control class:
public static void EnableChildren(Control control, bool enabled = true)
{
foreach (Control child in control.Controls)
{
child.Enabled = enabled;
}
}
And then if we also want to be able to use this with a collection of controls (as in your example), we can write an overload that takes a collection:
public static void EnableChildren(IEnumerable<Control> controls = null,
bool enabled = true)
{
if (controls == null) return;
foreach (var control in controls)
{
EnableChildren(control, enabled);
}
}
Now we can use this with a Form or a collection of TableLayoutPanel controls (or any control that has controls in it's Controls collection).
Examples of usage:
var myForm = new Form1();
EnableChildren(this); // 'this' is the current form
EnableChildren(myForm); // a separate instance of a form control
EnableChildren(tableLayoutPanel1, false); // A single TableLayoutPanel control
var tableLayoutPanels = new [] {tableLayoutPanel1, tableLayoutPanel2, tableLayoutPanel3};
EnableChildren(tableLayoutPanels); // An array of tableLayoutPanel controls
One of the simple ways I can think about what you are trying to do, is this. Let me get away for a sec here. I worked on projects where all form controls were built from Metadata. And meta came with licensing info. So, when control was placed where it should, it also was disabled or set read-only based on Metadata but the whole feature would be hidden if licensing info was restricting access to it. Coming back to your approach, this is not a bad approach and I see that this is can be done. And it can be done in 2 ways, (quickly from my head).
Use user controls as surface for the components you want to enable/disable. Create an interface
public interface IDisableableControl // make your fine name, no methods needed - marker interface
. . . . .
public class MyFineUserControl : UserControl, IDisableableControl
And in your static method that you're going to write pass the form, and find all controls that implement this interface and work them the way you want.
2.
Similarly, you can use property Tag, which is available on each control. With that, you can actually set your complex security object that can come from DB-stored metadata and then you evaluate this object stored in Tag to apply your configuration
Your method needs to be recursive
internal static void SetAllControls(Control parent)
{
// Do something with control, for example parent.Enabled = false
if (parent is IDisableableControl)
{
// here you use your logic, evaluate your parent you're dialing with and
// enable/disable correspondingly
parent.Enabled = false;
return;
}
foreach(var c in parent.Controls)
SetAllControls(c);
}
In real life, your TOP parent will be a form and will not need to be disabled, but it's certain children will. In fact, most of the time, once you found a UserControl which implements IDisableableControl that should be end of line, means, you don't need to go into children controls as they all sit on this parent and all will be disabled
I manage to accomplish what I was trying to do with the code below, which is pretty much a blend of all the helpful answers I got:
public static void EnableContainer(params Control[] containers)
{
if(containers.Count() == 0) { return; }
if (MyOF.isEnabled)
{
return;
}
else
{
try
{
foreach (var container in containers)
{
foreach (Control control in container.Controls)
{
control.Enabled = false;
}
}
}
catch (NullReferenceException)
{
}
}
}
public static void EnableForm<form>(form f) where form : Form
{
if (MyOF.isEnabled)
{
return;
}
else
{
foreach(Control control in f.Controls)
{
control.Enabled = false;
}
}
}
The community is welcome to suggest improvements as I am far from being a professional programmer. Thanks everyone once again.
This is probably a basic question, but I can't find answers because the terms are generic.
I am building a WinForm aplication. Its purpose is to set up memory in a certain chip. I think the best way to organize the application is to have a user control for each chip type, derived from a generic parent class. Think of the children as "iphone," "android" and "blackberry," derived from a parent class "phone".
VS2017 Designer has a Panel where I want the control to be. On startup, I generate an object of the base class and add it to the panel. When I press a button, the old object is deleted and replaced with a new one. Each class has just one control, a label with distinctive text.
The problem is, after I press the button, I see both texts. The panel's Controls collection has just one element, but I see the text from both objects. I have tried Refresh, Update and Invalidate withe the same results.
What do I have to do to make the old text "go away" so the only thing I see is the latest object?
private ChipMemBase ChipMemControl = new ChipMemBase();
public Form1()
{
InitializeComponent();
//tbFeedback.Text = string.Format(fmtString, 0, 1, 2, 3, 4, 5);
cbChipName.SelectedIndex = 0;
tbVersion.Text = Version;
OriginalWindowColor = tbFeedback.BackColor;
ShowChipMemControl();
PrintToFeedback(Version);
}
private void ShowChipMemControl()
{
var ctl = pnlChipMem.GetChildAtPoint(new Point(5,5));
if (null != ctl)
{
if (ctl != ChipMemControl)
{
pnlChipMem.Controls.Remove(ctl);
ctl.Dispose();
pnlChipMem.Update();
Refresh();
}
}
if (null != ChipMemControl)
{
pnlChipMem.Controls.Add(ChipMemControl);
}
}
private void btnMakeChipMemory_Click(object sender, EventArgs e)
{
ChipMemControl = new ChipMemGen2();
ShowChipMemControl();
}
Screenshots before and after clicking Create
Your ShowChipMemControl gets the control at point 5,5 and checks if it's a ChipMemControl then removes it.
I'm guessing that the reason it's not getting removed is that the control at point 5,5 is not a ChipMemControl.
You can use:
pnlChipMem.Controls.Clear()
to remove all the controls
Or:
ChipMemControl cmc = pnlChipMem.Controls.OfType<ChipMemBase>().FirstOrDefault();
if (cmc != null)
{
pnlChipMem.Controls.Remove(cmc);
cmc.Dispose();
}
To only remove the first instance of ChipMemBase on your pnlChipMem panel.
Got it. The problem was from inheritance, not window behavior. Control lblDefault in the base class, carrying the inconvenient text, was still present in the child class. I had to make it Public in the base class and remove it in the child class constructor:
InitializeComponent();
Controls.Remove(lblDefault);
lblDefault.Dispose();
lblDefault = null;
The clue was this article and project:
dynamically-and-remove-a-user-control
I wrote an Visual Studio 2013 extension that observes Windows.Forms designer windows. When a developer is changing controls in the designer window, the extension tries to verify that the result is consistent with our ui style guidelines. If possible violations are found they are listed in a tool window. This all works fine.
But now I would like to mark the inconsistent controls in the designer window, for example with a red frame or something like this.
Unfortunately, I did not find a way to draw adornments on controls in a designer window. I know that you can draw those adornments if you develop your own ControlDesigner, but I need to do it from "outside" the control's designer. I only have the IDesignerHost from the Dte2.ActiveWindow and can access the Controls and ControlDesigners via that host. I could not find any way to add adornments from "outside" the ControlDesigners.
My workaround for now is to catch the Paint-Events of the controls and try to draw my adornments from there. This doesn't work well for all controls (i.e. ComboBoxes etc), because not all controls let you draw on them. So I had to use their parent control's Paint event. And there are other drawbacks to this solution.
I hope someone can tell me if there is a better way. I'm pretty sure that there has to be one: If you use Menu->View->Tab Order (not sure about the correct english menu title, I'm using a german IDE), you can see that the IDE itself is able to adorn controls (no screenshot because it's my first post on SO), and I'm sure it is not using a work around like me. How does it do that?
I've been googling that for weeks now. Thanks for any help, advice, research starting points....
UPDATE:
Maybe it gets a little bit clearer with this screenshot:
Those blue numbered carets is what Visual Studio shows when selecting Tab order from the View menu. And my question is how this is done by the IDE.
As mentioned I tried to do it in the Paint event of the controls, but e.g. ComboBox doesn't actually support that event. And if I use the parent's Paint event I can only draw "around" the child controls because they are painted after the parent.
I also thought about using reflection on the controls or the ControlDesigners, but am not sure how to hook on the protected OnPaintAdornments method. And I don't think the IDE developers used those "dirty" tricks.
I believe you are seeking for BehaviorService architecture. The architecture with supporting parts like Behavior, Adorner and Glyph and some examples is explained here Behavior Service Overview. For instance
Extending the Design-Time User Interface
The BehaviorService model enables new functionality to be easily layered on an existing designer user interface. New UI remains independent of other previously defined Glyph and Behavior objects. For example, the smart tags on some controls are accessed by a Glyph in the upper-right-hand corner of the control (Smart Tag Glyph).
The smart tag code creates its own Adorner layer and adds Glyph objects to this layer. This keeps the smart tag Glyph objects separate from the selection Glyph objects. The necessary code for adding a new Adorner to the Adorners collection is straightforward.
etc.
Hope that helps.
I finally had the time to implement my solution and want to show it for completeness.
Of course I reduced the code to show only the relevant parts.
1. Obtaining the BehaviorService
This is one of the reasons why I don't like the service locator (anti) pattern. Though reading a lot of articles, I didn't came to my mind that I can obtain a BehaviorService from my IDesignerHost.
I now have something like this data class:
public class DesignerIssuesModel
{
private readonly BehaviorService m_BehaviorService;
private readonly Adorner m_Adorner = new Adorner();
private readonly Dictionary<Control, MyGlyph> m_Glyphs = new Dictionary<Control, MyGlyph>();
public IDesignerHost DesignerHost { get; private set; }
public DesignerIssuesModel(IDesignerHost designerHost)
{
DesignerHost = designerHost;
m_BehaviorService = (BehaviorService)DesignerHost.RootComponent.Site.GetService(typeof(BehaviorService));
m_BehaviorService.Adornders.Add(m_Adorner);
}
public void AddIssue(Control control)
{
if (!m_Glyphs.ContainsKey(control))
{
MyGlyph g = new MyGlyph(m_BehaviorService, control);
m_Glyphs[control] = g;
m_Adorner.Glyphs.Add(g);
}
m_Glyphs[control].Issues += 1;
}
public void RemoveIssue(Control control)
{
if (!m_Glyphs.ContainsKey(control)) return;
MyGlyph g = m_Glyphs[control];
g.Issues -= 1;
if (g.Issues > 0) return;
m_Glyphs.Remove(control);
m_Adorner.Glyphs.Remove(g);
}
}
So I obtain the BehaviorService from the RootComponent of the IDesignerHost and add a new System.Windows.Forms.Design.Behavior.Adorner to it. Then I can use my AddIssue and RemoveIssue methods to add and modify my glyphs to the Adorner.
2. My Glyph implementation
Here is the implementation of MyGlyph, a class inherited from System.Windows.Forms.Design.Behavior.Glyph:
public class MyGlyph : Glyph
{
private readonly BehaviorService m_BehaviorService;
private readonly Control m_Control;
public int Issues { get; set; }
public Control Control { get { return m_Control; } }
public VolkerIssueGlyph(BehaviorService behaviorService, Control control) : base(new MyBehavior())
{
m_Control = control;
m_BehaviorService = behaviorService;
}
public override Rectangle Bounds
{
get
{
Point p = m_BehaviorService.ControlToAdornerWindow(m_Control);
Graphics g = Graphics.FromHwnd(m_Control.Handle);
SizeF size = g.MeasureString(Issues.ToString(), m_Font);
return new Rectangle(p.X + 1, p.Y + m_Control.Height - (int)size.Height - 2, (int)size.Width + 1, (int)size.Height + 1);
}
}
public override Cursor GetHitTest(Point p)
{
return m_Control.Visible && Bounds.Contains(p) ? Cursors.Cross : null;
}
public override void Paint(PaintEventArgs pe)
{
if (!m_Control.Visible) return;
Point topLeft = m_BehaviorService.ControlToAdornerWindow(m_Control);
using (Pen pen = new Pen(Color.Red, 2))
pe.Graphics.DrawRectangle(pen, topLeft.X, topLeft.Y, m_Control.Width, m_Control.Height);
Rectangle bounds = Bounds;
pe.Graphics.FillRectangle(Brushes.Red, bounds);
pe.Graphics.DrawString(Issues.ToString(), m_Font, Brushes.Black, bounds);
}
}
The details of the overrides can be studied in the links posted in the accepted answer.
I draw a red border around (but inside) the control and add a little rectangle containing the number of found issues.
One thing to note is that I check if Control.Visible is true. So I can avoid to draw the adornment when the control is - for example - on a TabPage that is currently not selected.
3. My Behavior implementation
Since the constructor of the Glyph base class needs an instance of a class inherited from Behavior, I needed to create a new class. This can be left empty, but I used it to show a tooltip when the mouse enters the rectangle showing the number of issues:
public class MyBehavior : Behavior
{
private static readonly ToolTip ToolTip = new ToolTip
{
ToolTipTitle = "UI guide line issues found",
ToolTipIcon = ToolTipIcon.Warning
};
public override bool OnMouseEnter(Glyph g)
{
MyGlyph glyph = (MyGlyph)g;
if (!glyph.Control.Visible) return false;
lock(ToolTip)
ToolTip.Show(GetText(glyph), glyph.Control, glyph.Control.PointToClient(Control.MousePosition), 2000);
return true;
}
public override bool OnMouseLeave(Glyph g)
{
lock (ToolTip)
ToolTip.Hide(((MyGlyph)g).Control);
return true;
}
private static string GetText(MyGlyph glyph)
{
return string.Format("{0} has {1} conflicts!", glyph.Control.Name, glyph.Issues);
}
}
The overrides are called when the mouse enters/leaves the Bounds returned by the MyGlyph implementation.
4. Results
Finally I show screenshot of a example result. Since this was done by the real implementation, the tooltip is a little more advanced. The button is misaligned to all the comboboxes, because it's a little too left:
Thanks again to Ivan Stoev for pointing me to the right solution. I hope I could make clear how I implemented it.
Use the System.Drawing.Graphics.FromHwnd method, passing in the HWND for the designer window.
Get the HWND by drilling down into the window handles for visual studio, via pinvoke. Perhaps use tools like Inspect to find window classes and other information that might help you identify the correct (designer) window.
I've written a C# program to get you started here.
I am writing a coded ui test for my project, and i'd like to test if a label is visible or not.
However, the auto-generated code uses WinText, which does not contain any visibility option.
Is there a way to check visibility of the ui element behind WinText's or get the actual label instead ?
The easiest and most straightforward way is to call WinText's TryGetClickablePoint. If it returns false that means the control is either invisible, offscreen or blocked by an other control.
Point p = new Point();
bool isVisible = UIMap.UIForm1Window.UILabel1Window.UILabel1Text.TryGetClickablePoint(out p);
If you want to fiddle with the "ui element behind WinText's" then you have to get its native element and cast it to the appropriate class (IAccessible if winform) and get the visibility state from there. Be careful tho: you'll probably have to get the label's window's native element and not the label's.
IAccessible acc = (UIMap.UIForm1Window.UILabel1Window.NativeElement as object[])[id] as IAccessible; //id is most probably 0, check with AccExplorer
bool isVisible = (((AccessibleStates)acc.accState & AccessibleStates.Invisible) == 0) && (((AccessibleStates)acc.accState & AccessibleStates.Offscreen) == 0);
I'm using the following extension method for the UITestControl class, and so far it has been pretty bullet proof:
using Microsoft.VisualStudio.TestTools.UITesting;
namespace CodedUIExtensions
{
public static class UITestControlExtensions
{
public static bool IsElementVisible(this UITestControl control)
{
// Assume the control is invisible
bool visible = false;
System.Drawing.Point p;
try
{
// If the control is offscreen, bring it into the viewport
control.EnsureClickable();
// Now check the coordinates of the clickable point
visible = control.TryGetClickablePoint(out p)
&& (p.X > 0 || p.Y > 0);
}
catch (Exception ex)
{
// Boom goes the dynamite! Control is not visible.
// Log to stdout for debugging.
Console.WriteLine(ex);
}
return visible;
}
}
}
My requirements were:
Ensure the control is inside the viewport so a control that is visible, but offscreen doesn't trigger a false negative
Then assert the control is visible
Do not throw exceptions
If an exception occurs, do something with it for debugging purposes
And to use it:
using CodedUIExtensions;
[CodedUITest]
class MyTest
{
[TestMethod]
public void SomeVisibilityTest()
{
UITestControl control = // ...
HtmlDiv div = // ...
Assert.IsTrue(control.IsElementVisible(), "Not visible");
Asset.IsFalse(div.IsElementVisible(), "Visible");
}
}
As long as the Coded UI control class inherits from UITestControl and you have using CodedUIExtensions in your C# file, every control will have an IsElementVisible() method. This should work for web pages as well as GUI applications, such as WinForms.
You can try this:
Control.WaitForControlPropertyNotEqual(UITestControl.PropertyNames.State, ControlStates.Invisible);
This one worked for me (and also the method with .TryGetClickablePoint() ):
while (myControl.State != ControlStates.Offscreen) { }
I need to have a MaskedTextBox in a ToolStrip, which isn't included by default, so I followed some advice I found online, and created custom control that inherits from ToolStripControlHost. What I've created works great when I'm running the application, but it really messes up the designer. By "messes up", I mean the custom control (Along with some others) disappear from the ToolStrip. Also I can no longer add new controls to the ToolStrip, and I can't select the existing controls on the ToolStrip to edit them.
Here's my class.
[DesignerCategory("code")]
[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.StatusStrip)]
public partial class ToolStripMaskedTextBox : ToolStripControlHost
{
public MaskedTextBox MaskedTextBox
{
get { return Control as MaskedTextBox; }
}
public ToolStripMaskedTextBox()
: base(CreateControlInstance()) { }
private static Control CreateControlInstance()
{
MaskedTextBox mtb = new MaskedTextBox();
mtb.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
mtb.MinimumSize = new System.Drawing.Size(100, 16);
mtb.PasswordChar = '*';
return mtb;
}
}
Any help on what I might be doing wrong that's giving the designer a hard time would be appreciated.
Addition Info
Now when I open my class file in Visual Studio, I get a warning page with the following error:
Constructor on type 'System.Windows.Forms.ToolStripControlHost' not found.
Addition Info 2
The problem only occurs after building the solution. I can get the designer working correctly by modifying the Form.Designer.cs file in even the smallest way. Like adding a single space. From there on out the designer will work fine. That is until I build the solution. Then the designer freezes up again. None of the controls on the form can be edited.
According to the exception
Constructor on type 'System.Windows.Forms.ToolStripControlHost' not found.
I found some information on the MSDN Forum.
This happends because the ToolStripControlHost class does not have a constructor with no parameter.
To solve this problem, you can create your own ToolStripControlHost with a none-parameter constructor and make the ToolStripMaskedTextBox inherited from your ToolStripControlHost. Try something like the following
//Declare a class that inherits from ToolStripControlHost.
[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.StatusStrip)]
public class ToolStripMaskedTextBox : MyCustomToolStripControlHost
{
// Call the base constructor passing in a MaskedTextBox instance.
public ToolStripMaskedTextBox() : base(CreateControlInstance()) { }
public MaskedTextBox MaskedTextBox
{
get
{
return Control as MaskedTextBox;
}
}
private static Control CreateControlInstance()
{
MaskedTextBox mtb = new MaskedTextBox();
mtb.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
mtb.MinimumSize = new System.Drawing.Size(100, 16);
mtb.PasswordChar = '*';
return mtb;
}
}
public class MyCustomToolStripControlHost : ToolStripControlHost
{
public MyCustomToolStripControlHost()
: base(new Control())
{
}
public MyCustomToolStripControlHost(Control c)
: base(c)
{
}
}
This will fix the problem with your exception.
The Problem with the Forms Designer (ToolStripMaskedTextBox is not visible after running the app) is not solved but you can close the designer and open the file again.
Then you can go on without any problems.
Hope this helps
I've used dknaack's solution, but placed MyCustomToolStripControlHost class in a separate file in System.Windows.Forms namespace. And...
First: it works - no exception.
Then: my control is visible in designer as well, so it's a jackpot.
In this link, the answer was that the objects that implement "blah" interface must have a parameter-less constructor. Give it a try.
FWIW: I also succeeded with dknaack's solution above, but only after I realized I was looking for the custom ToolStrip Control in the wrong place. The custom control doesn't show up in the Toolbox itself. Rather it shows up in the dropdown list of components under the "Add ToolStripButton" that appears on the ToolStrip when it is selected (in the designer).
I found a solution of designer's problem.
https://dobon.net/vb/dotnet/control/tschdesigneravail.html#tips_comment (Japanese)
All you need is just make a class derived from ToolStrip and substitute it.
class DesignerFriendlyToolStrip : ToolStrip { }
var ts = new DesignerFriendlyToolStrip();
ts.Items.Add(toolStripMaskedTextBox);
form.Controls.Add(ts);
I don't know why this is effective. Anyone knows...?