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...?
Related
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 created a custom Button, called AcceptButton, inheriting from System.Windows.Forms.Button
On the constructor I set a few properties, but most important, an image (A green checkmark), like this:
this.Image = Proyecto.Controls.Bases.Properties.Resources.ok_16;
When I add this control using VS2013 form designer, in another project that references the DLL I just created, the image is displayed correctly. But if I go into my control, and change the image in code, for example, to:
this.Image = Proyecto.Controls.Bases.Properties.Resources.ok_32;
The image is not changed in the projects that use this control (even if the solution is cleaned and regenerated). I followed the code generated by VS2013 and I found that the designer adds this line:
this.botonAceptar1.Image = ((System.Drawing.Image)(resources.GetObject("botonAceptar1.Image")));
For some reason, this resource is "hardcoded" in a resource file generated by VS, but it's not updated when I regenerate the solution.
Removing this line makes it work as expected (I can change the image in the "upstream" class and it'll be updated when the solution is regenerated).
Why is this happening?
How can I avoid this?
This happens due to the DesignerSerializationVisibility (MSDN) attribute. Try adding this property and these methods (MSDN) to your class:
public class MyButton : System.Windows.Forms.Button
{
public bool ShouldSerializeImage()
{
return !object.ReferenceEquals(this.Image, _BaseImage);
}
public void ResetImage()
{
this.Image = _BaseImage;
}
[System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Visible)]
public new Image Image
{
get { return base.Image; }
set { base.Image = value; }
}
private Bitmap _BaseImage;
public MyButton()
{
_BaseImage = Proyecto.Controls.Bases.Properties.Resources.ok_16;
this.Image = _BaseImage;
}
}
This replaces the default Image property and prevents the serialization you encountered. Furthermore it allows the designer to check if the property has it's default value and if it needs to be serialized. The default value is stored in a private field in the button class. This should correctly serialize (or not serialize) the properties.
Remove all buttons you have, recompile, readd the buttons to make sure.
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 designing a comparison dialog (shows several widgets with their characteristics in a grid). There is a features section where all available features are listed with a check box for each one. If the part has that feature, the checkbox is checked. These checkboxes need to be read-only so I've isEnabled=false. However visually the checkboxes (and the label content) show as greyed out.
Here are some important points:
The checkbox is a visual indicator of whether a part has a feature. There is no requirement for interaction.
The requirement is for a checkbox; I'd have to convince the powers that be to use something different.
What I want is an easy way to style/controltemplate a checkbox (and it's content) so it looks enabled, but doesn't react to user input.
Microsoft provides some of their default styles on MSDN and you can find the default style for a checkbox here: http://msdn.microsoft.com/en-us/library/ms752319(v=vs.85).aspx. Copy this style into your project, remove the Trigger for IsEnabled and set the style for your checkboxes to this new style.
On a side note, I'd recommend copying the style into a separate ResourceDictionary for reusablitiy and to keep the style from cluttering up your xaml files.
Create a custom control by inheriting from CheckBox, and in its constructor create a Click handler for it. Within that Click handler, put the following code:
((CheckBox)sender).Checked = !((CheckBox)sender).Checked;
Here's a complete example.
For Windows Forms:
namespace System.Windows.Forms
{
public class UnChangingCheckBox : System.Windows.Forms.CheckBox
{
public UnChangingCheckBox()
{
this.Click += new EventHandler(UnChangingCheckBox_Click);
}
void UnChangingCheckBox_Click(object sender, EventArgs e)
{
((CheckBox)sender).Checked = !((CheckBox)sender).Checked;
}
}
}
For WPF:
namespace System.Windows.Controls
{
public class UnchangingCheckBox : System.Windows.Controls.CheckBox
{
public UnchangingCheckBox()
{
this.Click += new System.Windows.RoutedEventHandler(UnchangingCheckBox_Click);
}
void UnchangingCheckBox_Click(object sender, System.Windows.RoutedEventArgs e)
{
if (((CheckBox)sender).IsChecked.HasValue)
((CheckBox)sender).IsChecked = !((CheckBox)sender).IsChecked;
}
}
}
If you place the above code in a new class in your Windows Forms or WPF project, they'll appear as new tools in your toolbox. Then all you need to do is drag your new "UnchangingCheckBox" control onto your form where you were using a CheckBox. You don't need to do any coding on your form.
Using this approach your code will still be able to do everything you could do to a CheckBox (set its value, etc). It's only user interaction that's been disabled in a way that doesn't interfere with the visual style.
The solution suggested above works well for Windows Forms, but I see what you mean about WPF and the check mark appearing for a second.
Try this instead:
namespace System.Windows.Controls
{
public class UnchangingCheckbox : CheckBox
{
public UnchangingCheckbox()
{
this.IsReadOnly = true;
}
public bool IsReadOnly
{
get { return !this.IsHitTestVisible && !this.Focusable; }
set
{
this.IsHitTestVisible = !value;
this.Focusable = !value;
}
}
}
}
You acquire a property called "IsReadOnly", which by default is set to true, and has the behaviour you require without the annoying "checkmark appears for a second" behaviour.
I am having trouble causing an 'autohide' dock to appear programmatically.
Couldn't find any answer around the net, though the following SO Question suggested that .Show() should have done the trick
I've tried this on the latest NuGet version of the code.
My test code is below.
Anyone know how to do it? or what I'm doing wrong?
Update: apparently this is a bug in 2.7.0, I've opened an issue for it with the project.
#roken's answer is an excellent workaround, so I've updated the code below to reflect it.
My test Code
Create a simple Visual Studio Windows Form application, and replace the main form's source file content with this code:
using System;
using System.Windows.Forms;
using dps = WeifenLuo.WinFormsUI.Docking;
namespace testDockPanel
{
public partial class Form1 : Form
{
private dps.DockPanel dockPanel;
private dps.DockContent dc;
private Control innerCtrl;
public Form1()
{
InitializeComponent();
dockPanel = new dps.DockPanel();
dockPanel.Dock = DockStyle.Fill;
dockPanel.DocumentStyle = dps.DocumentStyle.DockingWindow;
toolStripContainer1.ContentPanel.Controls.Add(dockPanel);
dc = new dps.DockContent();
dc.DockPanel = dockPanel;
dc.DockState = dps.DockState.DockRightAutoHide;
innerCtrl = new WebBrowser() { Dock = DockStyle.Fill };
dc.Controls.Add( innerCtrl );
This is the part of the code that didn't work:
// This SHOULD show the autohide-dock, but NOTHING happens.
dc.Show();
I've replaced it with #roken's suggestion and it now works:
dockPanel.ActiveAutoHideContent = dc;
innerCtrl.Focus(); // This is required otherwise it will autohide quickly.
}
}
}
To show a hidden autohide content, you can set the active auto content directly:
dockPanel.ActiveAutoHideContent = dc;
It's not clear to me if the inability to activate the content via Show() is a bug that has been introduced. If you have a free moment could you try running the code you provided against version 2.5.0 to see if Show() activates the content like you expect?