Fixing .NET code generation of properties for user controls - c#

I have a property of type IEnumerable<SomeClassIWrote> in a user control. When I use this control in a GUI, the .Designer.cs file contains the line:
theObject.TheProperty = new SomeClassIWrote[0];
Which for some reason causes a compiler warning:
Object of type 'SomeClassIWrote[]' cannot be converted to type
'System.Collections.Generic.IEnumerable`1[SomeClassIWrote]'.
Which is a mystery to me because I pass arrays as IEnumerables all the time, and the compiler has never complained.
For what it's worth, I have a default value of null specified for the property, but I got the same error before I set a default value.
How can I fix this so Visual Studio doesn't complain and ask me to ignore and continue every time I pull up the designer?
Code for the property:
[DefaultValue(null)]
public IEnumerable<SomeClassIWrote> TheProperty {
get {
return _theProperty;
}
set {
if (value == null) {
_theProperty = new SomeClassIWrote[] { };
}
else {
_theProperty = value;
}
}
}

First up, do you WANT to be able to set it in the designer?
If not, add the following attributes:
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
If you DO want to set in the designer, I'd start out by trying your class as a SomeClassIWrote[], to see if that works.
That aside, is it that important to use an IEnumerable here? As you say, you can pass arrays as IEnumerables.
I suspect there's probably some restrictions inside the designer, which wiser people than me know about...
And if you really DO want an IEnumerable property, you can expose your array as an IEnumerable, but keep your array as a designer-friendly backing field.

Related

C# .NET: Get Start-up/Designer-assigned values of Controls

I want to retrieve the designer-assigned values of controls, e.g. when I designed a TextBox with the .Text "Hello World", then change the .Text during runtime: How can I retrieve the String "Hello World" again during runtime?
My thoughts so far:
I can write a set-up routine and assign these "default" values in there alone, and call that method again when ever I want to reset the values.
Cons:
- Not as flexible (No way of resetting individual Controls)
- would need to write a routine for each implementation
- missing out on the convenience of just setting the values once in the designer
I would really like a static method/extension which would enable me to plug in my Control and get the values assigned to it within my class designer.
I started trying to implement an extension within my extension library, where I create a new instance of the main Form Class and grab the value from there - but that approach has obvious disadvantages:
- The Form Class may have different constructor signatures, like string[] args for applications with console arguments; creating instances of unknown signatures isn't trivial or a good idea in general
- It's a heavy weight approach; depending on the complexity of the project, you might not want to create piles of instances every time you want to get a Control's designer-assigned value.
- This would execute what ever code is in the constructor
So I'm really hoping I can use Reflection or something similar to get to the designer-assigned values.
But I have no idea how to go about looking for that; googling things like "visual .net get designer default control value" yielded no relevant results.
Can somebody point me in the right direction?
My test extension method currently looks something like this:
public static string getDefaultText(this Control c)
{
Form mainForm = Form.ActiveForm; // just a quick easy way to test this
Type t = mainForm.GetType();
var f = Activator.CreateInstance(t);
Control a = ((Form)f).Controls[c.Name]; // unfortunately, .Controls only contains direct children, not all descendants; recursive searching would be needed
return a.Text;
}
and works for what it is, which is to say a proof of concept of a bad approach :/
#Selvin commented with a good idea: Let me formulate a preliminary work-around from it.
Once the Form uses Localization (Set Localizable to True at design-time), a version of the Control that represents its state in the Designer is stored in the app's Resources.
This seems to include all designable properties (everything in the Properties panel/window) (Edit: apparently not).
Using a ComponentResourceManager (implementing ResourceManager), we can apply that state to a Control again during runtime using its method ApplyResources:
public static void resetControl<T>(this T c, string key = "") where T : Control // Using generic approach for the convenience of being able to use the Type T
{
Form f = c.FindForm();
ComponentResourceManager resources = new ComponentResourceManager(f.GetType()); // Manage the resources from our Form class
if (key == "")
{
resources.ApplyResources(c, c.Name); // Simply natively apply all resources
}
else // If we want to reset only one specific property...
{
// rather than trying to get to the data serialized away somewhere, I'm using this work-around
T r = (T)Activator.CreateInstance(c.GetType()); // create a new instance of the Control in question
resources.ApplyResources(r, c.Name);
setAttr(c, key, r.getAttr(key)); // setAttr and getAttr are helper extensions I always have on hand as well
}
}
public static void setAttr(this object o, string key, object val)
{
foreach (PropertyInfo prop in o.GetType().GetProperties())
{
string nam = prop.Name;
if (nam == key)
{
prop.SetValue(o, val);
return;
}
}
}
public static dynamic getAttr(this object o, string key)
{
Type myType = o.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
foreach (PropertyInfo prop in props)
{
if (prop.Name == key)
{
return prop.GetValue(o, null).ChangeType(prop.PropertyType);
}
}
return null;
}
Usage:
// Reset everything to the Designer state:
textBox1.resetControl();
// Reset only the TextAlign property:
textBox1.resetControl("TextAlign");
// Reset only the Dock property:
textBox1.resetControl("Dock");
// ... etc
This works reasonably well and I can write further abstractions making the process of resetting certain values easier.
I don't like having to create a new instance of a control, but I'm willing to live with it for this work-around. However, better approaches are definitely welcome.
Update: Shortcomings
It looks like an empty .Text from Designer will not overwrite a non-blank .Text using ApplyResources, it seems.
However, using a proxy Control instance like in the case of supplying "Text" as a key string in my extension method above, this still works
It looks like it doesn't work with most Controls. Testing so far only had it work on TextBoxes, Labels and CheckBoxes with the .Text property, but not the .Checked property of the CheckBox
Attempting to reset a NumericUpDown's .Text (for example because you're carpet-bombing everything with resetting "Text" in order to reset it for those who have a relevant .Text) will set its .Value to 0

PropertyChanged is always fired

I am using standard notification pattern with INotifyPropertyChanged. Here is my sample property:
private string fanart;
public string Fanart
{
get { return fanart; }
set
{
if (fanart != value)
{
fanart = value;
NotifyPropertyChanged("Fanart");
}
}
}
Here, I expect the setter to skip everything if new value is equal to the current value. However, when I check using debugger, fanart always equals null and consequently, the event is always fired. Any reason for the filed (and property) to be null?
Edit: This property is part of class called PlayerItem. PlayerItem is part of another class PlayerState. All these classes extend from NotifyBase. A method RefreshPlayerState periodically refreshes the PlayerState. I just noticed that new instance of PlayerItem is created every time player state is refreshed. What could be causing this?
Edit2: Creation of new instance is expected since I am deserializing the object from a JSON response. Now, how do I prevent setter from setting the value if older values are equal to values deserialized from the JSON? myObj != value will always return true since both are different objects although contained data is same.
This code is part of project here. Feel free to look in for more details.
"fanart always equals null" means most likely, you are recreating instances of the class that owns this field (which I assume is your View Model). You should keep the reference to your bound VM and work on that instance.
Edit: Currently I am working with a Windows 7 machine so I could not see your code. But you are removing those players somewhere; those that you create at PlayerHelper and Player classes.

What is difference between this.PropertyName and _PropertyName?

as I often let LinqToSql generate partial entity classes, I am wondering if my practice of adding additional properties via code is correct and if there is a better way of doing the same thing? I am also wondering what is the difference between accessing the values of other properties using this.PROPERTY_NAME vs _PROPERTY_NAME?
In my web app I keep using this.PROPERTY_NAME, but I am wondering if that is, as I already said in opening sentence, the proper approach I should be using. Also, What is _PROPERTY_NAME and when do we use it?
Example:
public partial class User
{
public bool IsThisProper {
get{
return this.SomeIntProperty == 10; // I usually use this
}
}
public bool WhenToUseThisApproach {
get{
return _SomeIntProperty == 10; // What is this in comparison to above?
}
}
}
One is the property, and the other is the private backing field in which that property stores it's value. If you want to execute whatever code the property has in it's getter/setter, then use the property, if you don't, then don't. Chances are you want to use the property, not the field, especially with setting (setting it triggers the property changed event, so about the only time to use the property is if you don't want that event raised).

How to fix 'Remove property setter' build error?

I have a property in a model which has auto property getter and setter:
[DataMember]
public Collection<DTOObjects> CollectionName { get; set; }
I get the following error when building the solution:
Microsoft.Usage : Change 'propertyname' to be read-only by removing the property setter.
However, when I remove the setter and run the code, an error occurs because it's trying to set the property! It appears it's asking me to remove the setter despite the fact it is being set somewhere in the code.
Has anyone else come accross this problem? What do I need to modify?
I'm going to guess this is a list/collection (or something similar), in which case yes - it is unusual to have a setter. A typical example might be:
private readonly List<Foo> items = new List<Foo>();
public List<Foo> Items { get { return items; } }
Most callers should not be trying to assign to that; they shouldn't need to - they can add/remove/enumerate/clear/etc the list without ever needing to assign it.
an error occurs because it's trying to set the property
Then consider changing that code so that it doesn't try to set the property. It should not need to in virtually all cases.
One solution is to initialize the Collection in the constructor...
public class Email
{
public Email()
{
To = new List<MailAddress>();
}
....
public List<MailAddress> To { get; }
}
Then just use .add in code:
Email oEmail = new Email();
oEmail.To.Add(new MailAddress("Foo#fighter.com", "Mr. Foo"));
Just suppress it, right click the error and click suppress in code & an attribute will be added to the property.
Generally you shouldn't have a public set for collections as this allows the list to be replaced, however with objects that are created or deserialized at runtime sometimes the public setter is necessary.
From the docs:
"You can suppress the warning if the property is part of a Data Transfer Object (DTO) class. Otherwise, do not suppress warnings from this rule."
If it's not part of a DTO:
"To fix a violation of this rule, make the property read-only. If the design requires it, add methods to clear and repopulate the collection."
The preferred manner of replacing a read-only collection property is to use the Clear and AddRange methods (or their equivalents).
https://learn.microsoft.com/en-us/visualstudio/code-quality/ca2227?view=vs-2019

How do I specify a required attribute in a custom .NET Web control?

private string _itemId;
[Browsable(true),
Description("Required identifier for the Item.")]
public string ItemId
{
get { return _itemId; }
set
{
if (string.IsNullOrEmpty(_itemId))
{
_itemId = value;
}
}
}
How would I actually make that required when someone uses the control? I'm trying to find an attribute that says something like Required(true).
I don't know that there's an attribute for this. I believe on the Page_Load event (or perhaps some rendering event) just check if the value has been set. If not then throw an exception.
I don't think this is possible. Consider that the designer needs to be able to create an instance of the control when it's dragged from the toolbox. At that time, it's going to have default values for properties, and these values need to be valid.

Categories