User Control and List<Class> - c#

i am trying to use Property Editor for my user control but it doesn't work.
if i set the property in the form load if works, but if i want to use the property editor it don't save my changes (when i click again in the property editor it comes clear)
this is how i define the property in my user control:
private List<Field> _searchField;
public List<Field> SearchField
{
get { return _searchField ?? (_searchField = new List<Field>()); }
}

You need to apply DesignerSerializationVisibility attribute to your property with DesignerSerializationVisibility.Content.
This tells the code generator to produces code for the contents of the object, rather than for the object itself. It helps in code generation for types other than primitive types.
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<int> SearchField { get { return _searchField ?? (_searchField = new List<int>()); } }

Related

Resetting WPF window properties to default values set in XAML

There are default values you can set for some of window properties in WPF. For example you can set a Canvas' Width or a TextBlock's Text in XAML and then your app can change them by accessing the controls. Are there any ways of setting those values to how they were declared in XAML without saving them yourself? Are they kept somewhere so that you can access them at runtime?
My question is if there is maybe a function or some hacky way of setting all the values to default somewhere in the wpf api?
No, there isn't.
You better close the window and create a new one. Or you will have to "manually" reset all values by writing your own custom code that does this.
The framework cannot be supposed to know when you want to reset the values of your control properties without you specifying it somewhere. And the way to specify it is to write code.
I think that there is no way for WPF to know what your default values are but you can create your own attribute that contains information about default value and create method that will set that value.
First we need attribute that will hold information about default value of given property:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class DefaultAttribute : Attribute
{
public object Value;
public DefaultAttribute(object value)
{
this.Value = value;
}
}
Given this attribute we can easy declare default values for our properties like this:
class ViewModel
{
[Default("Noname")]
public string Name { get; set; }
public int Age { get; set; }
}
Then if you want to apply defaults to your view model you need some kind of method that will do that for you:
private static void ApplyDefaults(object model)
{
PropertyInfo[] properties = model.GetType().GetProperties();
foreach (var property in properties)
{
DefaultAttribute defaultAttr = property.GetCustomAttribute<DefaultAttribute>();
if (defaultAttr != null)
property.SetValue(model, defaultAttr.Value);
}
}
Example program with usage:
public static void Main()
{
var vm = new ViewModel();
Console.WriteLine($"Start - {vm}");
ApplyDefaults(vm);
Console.WriteLine($"ApplyDefaults - {vm}");
vm.Name = "Damian";
vm.Age = 23;
Console.WriteLine($"After using setters - {vm}");
ApplyDefaults(vm);
Console.WriteLine($"After ApplyDefaults - {vm}");
}
Output for the program is:
Start - - 0
ApplyDefaults - Noname - 0
After using setters - Damian - 23
After ApplyDefaults - Noname - 23
I've just checked that .NET have DefaultValue Attribute which you can use in similar way.

Attaching anonymous properties in object?

I'm working in a WPF project, it has a numerous number of user control.
for some forms , when the user close the container tab, a confirmation close is shown up, for other forms they are just closed.
so I use the
FrmAccounts FrmAcc = new FrmAccounts {Tag = "showConfirmClose"};
to decide what tab I need to close.
and in the closing event I check if the tag is set to showConfirmClose to show the confirmation message. But I don't like using Tag it's not good in C#, also I thought, what if I want to send more data (the only solution will be to comma separating them in the Tag and Split them, but this is worse).
I can't find a good, performant way to accomplish such task:
if this is possible:
FrmAccounts FrmAcc = new FrmAccounts {new{ShowConfirmClose= true }};
Attached property
You can use an AttachedProperty to store the additional information for your objects. However, these object have to be DependencyObjects, otherwise it won't work.
Here is an example.
Define your attached property in a separate class (it can be a simple static class or a DependencyObject):
namespace YourNamespace
{
public static class CloseConfirmation
{
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached(
"IsActive",
typeof(bool),
typeof(CloseConfirmation));
public static bool GetIsActive(DependencyObject obj)
{
return (bool)obj.GetValue(HiddenProperty);
}
public static void SetIsActive(DependencyObject obj, bool value)
{
obj.SetValue(HiddenProperty, value);
}
}
}
In XAML, set the attached property value for your objects (WPF Windows, Pages and other DependencyObjects):
<FrmAccounts xmlns:yns="YourNamespace"
yns:CloseConfirmation.IsActive="true">
<!-- Content -->
</FrmAccounts>
You can use this attached property in your XAML definition, e.g. in triggers.
You can also access its value in code-behind:
FrmAccounts frm = new FrmAccounts();
CloseConfirmation.SetIsActive(frm, true);
var isActive = CloseConfirmation.GetIsActive(frm); // true
You can create an attached property not only for a simple type like bool or int, but also for any custom type. So use your own container class if you need to transfer a lot of data.
Tag object
Using Tags is fully OK in .NET. Virtually any UI type has this property, so that you can attach objects of any type to your UI elements. However, these types cannot be anonymous types. You have to create your own container type:
class Container
{
public bool IsConfirmationNeeded { get; }
public IEnumerable<IItem> MyData { get; } // any data you need to pass
}
FrmAccounts frm = new FrmAccounts();
frm.Tag = new Container();
var container = (Container)frm.Tag;
var confirmation = container.IsConfirmationNeeded; // false by default

Is it posible to set the order of the serialization of a custom property of a custom component/control in the designer-generated code?

I am working on some custom (inherited) controls with custom properties for which I have added design-time support after proper attribute decoration.
All works well but my problem is that the auto-generated code in the *.Designer.cs file where the control is used, has a particular order that it sets the various properties in (both base and new properties). This order looks like it is alphabetical w.r.t. the property name.
So the auto generated code looks like this:
//
// myTabPage1
//
this.myTabPage1.Identifier = "ID";
this.myTabPage1.Name = "myTabPage1";
this.myTabPage1.Size = new System.Drawing.Size(294, 272);
this.myTabPage1.Text = "TTT";
Where I would have liked it to look like this:
//
// myTabPage1
//
this.myTabPage1.Name = "myTabPage1";
this.myTabPage1.Size = new System.Drawing.Size(294, 272);
this.myTabPage1.Text = "TTT";
this.myTabPage1.Identifier = "ID";
The reason I need this is because setting the Identifier property affects Text which is then reverted back to its design-time value, annulling the effect of setting the Identifier.
There are of course simple workarounds (the simplest of which is not to set the Text property which work well) but it would be nice if this was not a design-time "worry", as there is extensive usage of this design pattern in many inherited control types and their instances.
It is also helpful to set Text to identify the controls on the form designer (Identifier has no effect on Text at design-time).
No, you can't affect serialization order. This is otherwise a common problem with a general solution, implement the ISupportInitialize interface. Your BeginInit() method is called just before the designer starts assigning properties, you'd typically set a bool variable that ensures that property setters don't have unintended side-effects. Your EndInit() method is called when it is done and all properties have a value, you'd set the variable back to false and do whatever you need to do to use the values.
Your question isn't specific enough about the control with the problem, but a possible implementation could look like this:
public partial class CustomControl : UserControl, ISupportInitialize {
public CustomControl() {
InitializeComponent();
}
private bool initializing;
private string id = "";
public string ID {
get { return id; }
set { id = value;
if (!initializing) label1.Text = value;
}
}
[Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override string Text {
get { return base.Text; }
set {
base.Text = value;
if (!initializing && !this.DesignMode) label1.Text = value;
}
}
public void BeginInit() {
initializing = true;
}
public void EndInit() {
initializing = false;
label1.Text = ID;
}
}
Also note the [DesignerSerializationVisibility] attribute in this code, when you use Hidden then the property doesn't get serialized at all. That could be a simple solution to your problem.

Expose A Property Of Type List<class> In VS Designer Limiting/Hiding Access To Members Or Show Property As Expandable Menu?

I have created a custom tab control for my Windows application. The custom tab control extends
System.Windows.Forms.TabControl. The reason why I created a custom tab control is so I can expose a property in the Visual Studio Properties window that allows me to define individual fonts for each tab page in the custom tab control. Here is a quick look at the class definition:
[ToolboxItem(true)]
public partial class CustomTabControl : System.Windows.Forms.TabControl
To store each individual name/font pair I created a nested class inside CustomTabControl:
[TypeConverter(typeof(TabFontConverter))]
public class TabFont
{
public string Name { get; set; }
public Font Font { get; set; }
public TabFont()
{
}
public TabFont(string name, Font font)
{
this.Name = name;
this.Font = font;
}
}
(Note the use of the TypeConverter property above the TabFont class. I added this because somewhere I read online that this was required if I am going to expose this type in the Visual Studio designer.)
Here is the converter class (which is also nested inside CustomTabControl):
public class TabFontConverter : TypeConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] filter)
{
return TypeDescriptor.GetProperties(value, filter);
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
}
I defined a class variable to store the custom tab fonts as a List:
private List<TabFont> _CustomTabFonts = new List<TabFont>();
To populate this list, I added an event handler for ControlAdded:
this.ControlAdded += new ControlEventHandler(CustomTabControl_ControlAdded);
Here is how I populate the list inside the event handler:
private void CustomTabControl_ControlAdded(object sender, ControlEventArgs e)
{
if (e.Control.GetType() == typeof(TabPage))
{
TabPage newTabPage = (TabPage)e.Control;
Font newTabPageFont = newTabPage.Font;
_CustomTabFonts.Add(new TabFont(newTabPage.Text, newTabPageFont));
e.Control.Font = newTabPageFont;
}
}
And finally to tie it all up I defined the following code allowing the Visual Studio designer to access/modify the custom tab font list:
[DefaultValue(typeof(List<TabFont>))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public List<TabFont> CustomTabFonts
{
get { return _CustomTabFonts; }
set { _CustomTabFonts = value; }
}
After rebuilding I switch back to the Visual Studio design view, added a CustomTabControl to my main form by dragging one from the Toolbox., then I added 2 tab pages named "Tab 1" and "Tab 2".
This is what the properties box shows for my custom tab fonts property:
Note that it shows the type as a Collection and provides a button [...] to click for editing. When I click the button here is what I see:
I have a couple questions regarding the property editor.
The right side of the property editor shows both the Font and Name
for the selected tab. I only want to be able to change the Font, not
the name. How can I either hide the name field or at least make it
read only? (I would prefer the name field not to show there at all
because I don't want to be able to change it and it's also redundant
because the names are already shown on the left side of the property
editor.)
The left side of the property editor shows the list of tabs which is
exactly what I want. I do not, however, want to allow moving, adding,
or removing any of these members. How can I either hide or disable
the Move (up/down arrows) and Add/Remove buttons?
The left side of the property editor has a heading named "Members".
Can I change that to say whatever I want? Something like "Tab Pages",
etc.
The right side of the property editor has a heading named "Misc". Can
I change that as well?
Thank you very much.
Jan
____UPDATE____
If there is a better/different way of doing what I am trying to do I am open to all suggestions. I am new to this and what I have done so far has been based on various results from different web sites.
I would really like my property to appear in the designer similar to the way margins are shown. Instead of a popup window with a list of tab pages/properties I would like an expandable list with each list item being the tab name followed by the font, which you could then click to edit the font only. Something like the following:
I can't answer the Update question, but I'll have a go at the other two:
Changing the text "Members": The only way I can see of doing this is to create a custom CollectionEditor which opens a custom CollectionEditor.CollectionForm. I haven't tried this though.
Stopping the "Name" property from appearing in the editor: Yes, this can be done in the TypeConverter.GetProperties method by filtering the result. I didn't find the "filter" argument to the TypeDescriptor.GetProperties method any use, but that may be because I wasn't using it correctly. The problem is that, once created, a PropertyDescriptorCollection is read-only, so I copied the contents of the result but missed out the item I didn't want. This should work:
public class TabFontConverter : TypeConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] filter)
{
PropertyDescriptorCollection rawResult = TypeDescriptor.GetProperties(value, filter);
PropertyDescriptor[] arrRawResult = new PropertyDescriptor[rawResult.Count - 1];
int i = 0;
int j = 0;
while (i < rawResult.Count)
{
if (rawResult[i].Name != "Name")
{
arrRawResult[j] = rawResult[i];
j++;
}
i++;
}
PropertyDescriptorCollection filteredResult = new PropertyDescriptorCollection(arrRawResult);
return filteredResult;
}

How to make a User control property of type Collection<MyClass> editable in Form Designer?

Today at work, I stumbled upon a problem that was driving me nuts.
Basically my goal is this:
I have a UserControl1, with a field of the type Collection<Class1> and a corresponding property Collection<Class1> Prop. Like this:
public class UserControl1 : UserControl
{
private Collection<Class1> field = null;
// later changed to:
//private Collection<Class1> field = new Collection<Class1>();
[Category("Data")]
[DefaultValue(null)]
[Description("asdf")]
public Collection<Class1> prop
{
get { return field; }
set { field = value; }
}
}
// later added:
//[Serializable]
public class Class1
{
private bool booltest; public bool Booltest { get...set...}
private int inttest; public int Inttest { get...set...}
}
If you already know what I screwed up: no need to read the rest. I am going to describe what exactly I did.
Now I put the UserControl onto a random Form and change the Prop property. A generic "Collection Editor" appears, like the one used for the columns and groups in a listview control. I can enter data as expected. However, when I click OK, the data is gone.
It took me over hour to figure out that I actually have to instantiate my field: private Collection<MyClass> field = new Collection<MyClass>();. Very good, only that the designer entered superspazzing mode. Cascading nightmare error message that can be reduced to: "You must put [Serializable] before your Class1." After doing that I could actually put my UserControl1 on the Form again.
But that only works once. When opening the designer of the Form where I use the UserControl1 after editing something, it gives me an error:
Object of type 'userctltest.Class1[]' cannot be converted to type 'userctltest.Class1[]'.
Well. The Error List says:
Warning: ResX file Object of type 'userctltest.Class1[]' cannot be converted to type 'userctltest.Class1[]'. Line 134, position 5. cannot be parsed.
The designer tries to fetch the Property's data from the resx file. Removing the resx file "solves" that exactly once.
The Form can now be displayed again, with my UserControl1. The Collection property is editable, and it is being saved. It actually works. Once. Whenever I change something and then try to open the Form's designer again, the above error occurs again. I can delete the resx file, but that will of course also delete my data.
Relevant resources that helped me so far (among a ton of not so helpful search results):
http://www.codeproject.com/Answers/190675/Usercontrol-with-custom-class-property#answer1
http://www.codeproject.com/KB/cs/propertyeditor.aspx
http://www.csharpfriends.com/Articles/getArticle.aspx?articleID=94
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.aspx
(I also tried implementing ISerializable and overriding GetObjectData with
{ info.AddValue("testbool", testbool); info.AddValue("testint", testint); }
didn't help either (I also tried the property names instead of the field names))
Sorry for writing this like a bad horror novel btw.
What you want is a design time support with CodeDom serialization. You do not need SerializableAttribute or ISerializable, those are for binary serialization.
Since you want to serialize the collection, you must tell the designer to serialize it as such. That is done with the DesignerSerializationVisibiliby attribute - value of Content tells the designer to serialize property contents rather than property itself. Contents of the property should of course be CodeDom serializable, which simple classes with simple properties are by default.
So if you change your UserControl1 class like this:
public class UserControl1 : UserControl
{
private Collection<Class1> field = new Collection<Class1>();
[Category("Data")]
[Description("asdf")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Collection<Class1> prop
{
get { return field; }
}
}
... it should do the trick. Oh and collection properties are usually not writeable, although that is not mandatory. But serializer expects the collection property to be initialized, that is why you had to add initialization for the field.
Another note, if you do not want that your property is marked with bold in the property editor, you can specify a more complex "default value" through a special method ShouldSerializePropertyName, which can even be private. Like so:
private bool ShouldSerializeprop()
{
return (field.Count > 0);
}
Now your property will only be bold when it is not empty. But I digress, this was not a question :)
The perfect exemple is this:
public partial class SCon : UserControl
{
public SCon()
{
InitializeComponent();
if (Persoanas == null)
{
Persoanas = new List<Persoana>();
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<Persoan> Persoanas { get; set; }
}
[Serializable]
public class Persoan
{
public int Id { get; set; }
public String Name { get; set; }
}
Just change Collection<> to List<>

Categories

Resources