DesignerHost fails to create control with Visible = false - c#

Good day!
I'm writing a .vsix to replace old controls to new ones. I have designerHost which is the current designer instance. Then I start converting like this:
foreach (OldCombo oldCmbx in OldCmbxs())
{
...
NewCombo newCmbx = designerHost.CreateComponent(NewComboType, oldcmbx.Name) as NewCmbx;
...
newCmbx.Visible = oldCmbx.Visible;
...
designerHost.DestroyComponent(oldCmbx);
}
The thing is -oldCmbx is always Visible=true, no matter how it's written in the designer.cs file. I'm always creating Visible=true newCmbx's. If I force newCmbx to be Visible=false, then designer doesn't show newCmbx after the conversion, but the visible property is still true, so Visible property is definitely not what I'm searching for. So how can I force newCmbx's to be Visible=false in designer.cs?

After digging through .NET source code I've found that ControlDesigner is shadowing Visible property of the Control, so what is going to be serialized/deserialized in InitializeComponent is far related from actual Visible property of Control.
Designer.Visible property is initialized like this:
public override void Initialize(IComponent component)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(component.GetType());
PropertyDescriptor descriptor = properties["Visible"];
if (((descriptor == null) || (descriptor.PropertyType != typeof(bool))) || !descriptor.ShouldSerializeValue(component))
{
this.Visible = true;
}
else
{
this.Visible = (bool) descriptor.GetValue(component);
}
...
}
descriptor.ShouldSerializeValue(component) for Control.Visible is always false in case of newly created control.
Designer.Visible property:
private bool Visible
{
get
{
return (bool) base.ShadowProperties["Visible"];
}
set
{
base.ShadowProperties["Visible"] = value;
}
}
In the Designer.PreFilterProperties() actual Visible property of the Control is shadowed by Visible property of the designer.
Now, when the designer is initialized(in my code that's happening when I'm creating component designerHost.CreateComponent) newCmbx.Visible is always true.
Why it is so? Because Visible property of the Control is used in painting of the control(on the designer surface as well). If I set newCmbx.Visible = false it just disappears from the design surface (but still serializes from the Visible property of the designer) - that's bad, so by design of the Control class, when Control is instantiated, it is always Visible so that it could be visible on the design surface. Any subsequent changes in Visible property influence Visible property of the designer, not Control itself (in the context of working in Designer mode).
So, what I need in order to solve that problem is Visible property of the designer.
Correct code looks like this:
foreach (OldCombo oldCmbx in OldCmbxs())
{
bool _visible = GetVisiblePropThroughReflection(designerHost.GetDesigner(oldCmbx));
...
NewCombo newCmbx = designerHost.CreateComponent(NewComboType, oldcmbx.Name) as NewCmbx;
...
SetVisiblePropThroughReflection(designerHost.GetDesigner(newCmbx), _visible);
...
designerHost.DestroyComponent(oldCmbx);
}

Related

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

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

How to disable controls until a condition is met?

Currently in my program in about 10 control event handlers I have this code:
if (!mapLoaded)
return;
When I load a map through the open file dialog I set mapLoaded to true. Another way to do this would be to just disable all the controls for startup and after loading a map to enable all the controls. Unfortunately there are 30+ controls and this is just 30 lines of..
a.Enabled = true;
b.Enabled = true;
c.Enabled = true;
I can't really do a foreach loop through this.Controls either because some of the controls are menustrip items, toolstrip items, panel items, scrollbars, splitters, et cetera and that loop doesn't cover that.
Ideally there would be a way to set every control's enabled property to true in a single and simple loop but I'm not sure of how to do that. Any ideas SO?
Use data binding:
Change mapLoaded into a property that notifies observers when its value has changed...
public bool MapLoaded
{
get
{
return mapLoaded;
}
set
{
if (value != mapLoaded)
{
mapLoaded = value;
MapLoadedChanged(this, EventArgs.Empty);
}
}
}
private bool mapLoaded;
public event EventHandler MapLoadedChanged = delegate {};
// ^ or implement INotifyPropertyChanged instead
Data-bind your controls' Enabled property to MapLoaded. You can set up the data bindings either using the Windows Forms designer, or using code, e.g. right after InitializeComponent();:
a.DataBindings.Add("Enabled", this, "MapLoaded");
b.DataBindings.Add("Enabled", this, "MapLoaded");
c.DataBindings.Add("Enabled", this, "MapLoaded");
How about changing your opening strategy, have a new form that let's your user load a map, and the simply not load your main form until one has been loaded?

Custom control resize C#

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

How do I determine visibility of a control?

I have a TabControl that contains several tabs. Each tab has one UserControl on it. I would like to check the visibility of a control x on UserControl A from UserControl B. I figured that doing x.Visible from UserControl B would be good enough. As it turns out, it was displaying false in the debugger even though I set it explicitly to true and it was never changed. Then I read on MSDN for Control.Visible that:
Even if Visible is set to true, the control might not be visible to the user if it is obscured behind other controls.
So much to my surprise, that will not work. Now I'm wondering how I can tell if the control x is visible from a different UserControl. I would like to avoid using a boolean if possible. Has anyone run into this and found a solution?
Note: It also appears that Control.IsAccessible is false in this situation.
Unfortunately the control doesn't provide anything public that will allow you to check this.
One possibility would be to set something in the controls 'Tag' property. The tag’s purpose is to associate user data with the control. So it can be anything not just a boolean.
Here is the Tag property doc
If you really want the brute force way, you can use Reflection, basically calling GetState(2):
public static bool WouldBeVisible(Control ctl)
{
// Returns true if the control would be visible if container is visible
MethodInfo mi = ctl.GetType().GetMethod("GetState", BindingFlags.Instance | BindingFlags.NonPublic);
if (mi == null) return ctl.Visible;
return (bool)(mi.Invoke(ctl, new object[] { 2 }));
}
Please try this:
bool ControlIsReallyVisible(Control C)
{
if (C.Parent == null) return C.Visible;
else return (C.Visible && ControlIsReallyVisible(C.Parent));
}
I'm using this code not only checking all the ancestors visible and also who is the root control. Checking a root is needed when the control is not added on the Mainform.
public static class StratoControlExtension
{
public static bool TruelyVisible(this Control control, Control expected_root)
{
if (control.Parent == null) { return control == expected_root && control.Visible; }
return control.Parent.TruelyVisible(expected_root) && control.Visible;
}
}

Why my control's properties won't change outside its class?

I'm new in C# but not new to coding --being doing it for almost two decades--, and have a problem with properties in a custom control I'm building, which inherits from a Panel. When I put my properties, I can see them in the Designer properties list and can even set them, but when running my little application, it seems these properties values are not used. The same if I change a property programatically: no error but my control does nothing, it is like they are not properly set. However, if I do it programatically whithin the class, they do work. My guess is that something in my properties set/get stuff is not right. Please see the following code chunk of how I'm doing it:
public class ColorStrip : Panel
{
// properties
// ------------------------------------------
// size of color clusters (boxes)
private int _clusterSize = 20;
// controls if show the buttons panel
private Boolean _showButtons;
// property setters/getters
// ------------------------------------------
// clusterSize...
public int clusterSize
{
get { return _clusterSize; }
set { _clusterSize = value; }
}
// showButtons...
public Boolean showButtons
{
get { return _showButtons; }
set { Console.Write(_showButtons); _showButtons = value; }
}
....
So in my form, for instance in the load or even in a click event somewhere, if I put colorStrip1.showButtons = false; or colorStrip1.showButtons = true; whatever (colorStrip1 would be the instance name after placing the control in the form in design mode)... console.write says always 'false'; Even if I set it in the design properties list as 'true' it will not reflect the settled value, even if I default it to true, it will never change externally. Any ideas? Non of the methods get the new and externally settled property value neither, obviously the getter/setter thing is not working. Seems to me I'm not doing right the way I set or get my properties outside the class. It works only inside it, as a charm...Any help...very appreciate!
Cheers
lithium
p.s. TO CLARIFY SOLUTION:
Setting the property in this case didn't work because I was trying to use a new set value within the constructor, which seems can't get the new values since it is, well, building the thing. If I change the property value in Design mode > Property editor or in code externally to the object, say in it's parent form's load event, it will change it but readable for all methods except the constructor, of course :)
It's likely an issue of the order of execution. Your property setter just sets a variable, but doesn't actually trigger anything on the control to update the state related to this variable (e.g. adding or showing the buttons I assume).
When you set the property befre the rest of the initialization is done, the value is being used, otherwise it isn't because during the initial go the default value is still the property value.
You need to act on the setter, here's some pseudocode to illustrate:
set {
_showButtons = value;
if (alreadyInitialized) {
UpdateButtons();
}
}
Note: make sure to first set the value, then act - otherwise you end up using the old value (just like your Console.Write() is doing).
The quoted code doesn't look problematic. Are you sure you're referencing the same instance of ColorStrip? Also, check your .Designer.cs file to ensure that the code setting the property is there.
In fact, try simplifying your code by using auto-implementing properties:
public int clusterSize { get;set;}
public Boolean showButtons {get;set;}
public ColorStrip() { ... clusterSize = 20; ... }

Categories

Resources