Custom control / UIView in Xamarin.iOS with design-time properties - c#

I am creating a user interface for an iOS app and I am looking for the correct way to create a reusable custom control. I got it generally working when running the app, but at design time setting my "exported" properties has no visible effect in the designer. I think I am doing something fundamentally wrong, so perhaps someone could give me guidance
What I am doing:
I have created a subclass of UIControl.
In the constructor I call an Initialize method.
In the Initialize method, I add several subviews and constraints to layout them within my control
Here is some hollowed out code that shows the above:
[Register("RangedValueSelector"), DesignTimeVisible(true)]
public sealed class RangedValueSelector : UIControl
{
public RangedValueSelector(IntPtr p)
: base(p)
{
Initialize();
}
public RangedValueSelector()
{
Initialize();
}
public int HorizontalButtonSpacing
{
get { return _horizontalButtonSpacing; }
set
{
_horizontalButtonSpacing = value;
}
}
[Export("LabelBoxVerticalInset"), Browsable(true)]
public int LabelBoxVerticalInset
{
get
{
return _labelBoxVerticalInset;
}
set
{
_labelBoxVerticalInset = value;
}
}
private void Initialize()
{
//Code that creates and add Subviews
//Code that creates and add the required constraints, some of which should depend on the design time properties
}
}
So the control works perfectly fine if I set the exported properties via the designer, however they do not have an immediate effect in the designer itself.
What is the suggested way of having design-time settable properties that change the constraint values? I would like to avoid having to recreate all the subviews each time someone in the code or in the designer sets a property.

You are missing constructor with RectangleF which is used by designer.
public RangedValueSelector(RectangleF bounds):base(bounds){}
The rest seems to be correct.

Related

How to modify comments in C# Designer.cs file

I'm currently making a custom control, and some of the properties are generating these weird blank comments when VS writes the Designer.cs file. Example:
//
// myControl
//
this.myControl.Name = "myControl";
this.myControl.Property = 30;
this.myControl.OtherProperty = 20;
//
//
//
this.myControl.Options1.Name = null;
this.myControl.Options1.Option = "example";
//
//
//
this.myControl.Options2.Name = null;
this.myControl.Options2.SomeProperty = 50;
this.myControl.Options2.SomeEvent += new System.EventHandler(this.myControl_Options2_SomeEvent);
this.myControl.OtherProperty = 10;
Does anybody know what's causing these blank comments? I'd prefer no comments at all, but if I can at least have the name "myControl.Options1" shown that would be acceptable.
Here is the rough structure of my classes (although very simplified):
[ToolboxItem(false)]
public class Options : IComponent
// I implement IComponent so this class appears in the Properties window nicely. Not sure why exactly it works though.
{
#region Implement IComponent
public ISite Site { get; set; }
public void Dispose()
{
// Nothing needs to be disposed
Disposed?.Invoke(this, EventArgs.Empty);
}
public event EventHandler Disposed;
#endregion
}
public partial class MyControl : UserControl
{
#region Options
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Options Options1 { get; private set; }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Options Options2 { get; private set; }
public MyControl
{
Options1 = new Options();
Options2 = new Options();
}
#endregion
}
Any ideas would be very helpful! I haven't been able to find this problem anywhere else online. My assumption is that I'm misusing the IComponent interface, but I'm not sure how to fix it.
Those comments are automatically generated and have no impact on the output binary. When you compile, they'll all be dropped, so you needn't worry about bloat or anything like that. For maintainability, other developers should be directed to the designer, not the outputted code. Their edits won't be preserved if someone makes a change in the designer.
I suggest ignoring what's actually emitted by the designer entirely. It's not really meant to be edited- useful to view what source actually gets created from the designer, though.
That is the default behavior of the ComponentCodeDomSerializer when serializing the component name.
You can derive from the ComponentCodeDomSerializer and override Serialize, call the base class, then remove the CodeCommentStatement objects from the returned CodeStatementCollection.
What you have linked, looks a bit weird. Well the commenting system works as follow:
Your auto generated designer 'Methods' will be signed with an xml comment which describes their behavior.
It also writes comments to define Which control properties are being set on the current block.
in your case:
//
// myControl
//
this.myControl.Name = "myControl";
this.myControl.Property = 30;
this.myControl.OtherProperty = 20;
the 'myControl' means there is a control named 'myControl' that the following lines are setting it's properties.
As u said u r gonna ship this control. I think event these short nonsense comments are useful.
Edit: Nevermind, this doesn't work. It seemed like it was working until I used the control elsewhere and the problem came back up.
I found that adding the following attribute to my options class worked for me:
[ToolboxItem(false)]
[DesignerSerializer(typeof(CodeDomSerializer), typeof(CodeDomSerializerBase))]
public class Options : IComponent
{
...
}

WinForms Inheritance InitializeComponent

I have a BaseForm that specifies several protected controls which are initialized in BaseForm.InitializeComponent(). I've made these controls protected so that I can access the values of dropdowns, etc, in my DerivedForm. Making these controls accessible to DerivedForm causes the Designer to include them in DerivedForm.InitializeComponent(), which resets them, thus undoing any additional work I've done in the BaseForm constructor.
Is there a way to access my BaseForm controls in DerivedForm, but not have them initialized a second time?
public SettingsDialogBase(Settings settings)
{
InitializeComponent();
// Additional work which initializes dropdowns, etc
InitializeSettings();
}
public SettingsDialog(Settings settings) : base(settings)
{
InitializeComponent();
// InitializeSettings() rendered useless on controls that are set to protected
// because SettingsDialog.InitializeComponent() included them automatically
}
I've made these controls protected so that I can access the values of dropdowns
There's your problem.
Don't make those controls protected. Keep them private to the base class. Expose them to subclasses exactly as you would publicly: by wrapping access to the controls in public properties that allow access to only the aspects of those controls that need to be accessed.
For example:
class BaseForm : Form
{
public string PromptText
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
public int SelectedIndex
{
get { return comboBox1.SelectedIndex; }
set { comboBox1.SelectedIndex = value; }
}
// etc.
}
Note that if things like ComboBox uses e.g. enum values, you can make a property like SelectedValue instead, having the enum type and cast when returning from the comboBox1.SelectedValue property.
Note also that another way to approach this type of design issue is to author UserControl objects instead of forms, and use composition to build up the task-specific forms. This avoids inheritance altogether.
BaseForm's implementation of InitializeSettings:
protected virtual void InitializeSettings(Settings settings)
{
//initialization of settings
}
DerivedForm's implementation of InitializeSettings:
protected override void InitializeSettings(Settings settings)
{
base.InitializeSettings(x);
//reinitialization of settings
}
And call of InitializeSettings() in your DerivedForm's constructor will set your settings.
Okay, the goal was not clear for me.
If you want to have just 1 initialization of Settings, do not apply them in constructor. Basically, you should use the
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
//initialization of settings
}
The second way to solve the problem is to not even have parameterized constructor and to call InitializeSettings outside after creation of form by default constructor.

Issues changing default backcolor in a usercontrol

I am creating a custom control with a black background but have some issues with the designer. Truth to be told I have a base control class that inherits from UserControl and then some subclasses that represent the final controls that I will use in my GUI. In that base class I override the BackColor property, add the DefaultValue attribute and set the default value to BackColor in the constructor. As an example my code looks something like this:
public partial class MyControl1 : UserControl
{
public MyControl1()
{
InitializeComponent();
BackColor = Color.Black;
}
[DefaultValue(typeof(Color),"Black")]
public override Color BackColor
{
get
{
return base.BackColor;
}
set
{
base.BackColor = value;
}
}
}
...
public partial class MyControl2 : MyControl1
{
public MyControl2()
{
InitializeComponent();
}
}
The thing is every time I open the designer for MyControl2, BackColor in the properties dialog reverts to System.Drawing.SystemColors.Control and my control is painted grey. If I invoke Reset on BackColor it properly returns to Color.Black, though. Also, the designer doesn't serialize the change to System.Drawing.SystemColors.Control until I make another change to the control.
So, what did I try?
I thought it could be related to BackColor being an ambient property so I tried adding the attribute AmbientValue(false). Of course it didn't work.
I tried erasing the overridden property, leaving only BackColor=Color.Black in the constructor. Surprisingly it fixed the problem with the designer but now resetting the property reverted it to a default value of System.Drawing.SystemColors.Control. Overriding ResetBackColor() didn't solve this last problem.
By the way, I am working under Visual Studio 2010 and my project was created as a .NET 2.0 Windows Forms Application.
I would be glad whether anyone could help me to find whatever is wrong in my code. It is not something that would prevent me from finishing the project but it is pretty annoying. Thank you very much in advance!
This may help - there appears to be some voodoo in the winforms designer (a bit like the XML serializer) that will look for properties which are named a specific way because the DefaultValue doesn't work as you might expect:
The following is an example from another post, I know you are not subclassing a DataGridView, but the principle ought to be the same.
public class MyGridView : DataGridView {
public MyGridView() {
this.BackgroundColor = DefaultBackgroundColor;
}
public new Color BackgroundColor {
get { return base.BackgroundColor; }
set { base.BackgroundColor = value; }
}
private bool ShouldSerializeBackgroundColor() {
return !this.BackgroundColor.Equals(DefaultBackgroundColor);
}
private void ResetBackgroundColor() {
this.BackgroundColor = DefaultBackgroundColor;
}
private static Color DefaultBackgroundColor {
get { return Color.Red; }
}
}
Incidently - this isn't my code - it's some more pure genius from Hans Passant... link to original with a full explanation: https://stackoverflow.com/a/20838280/685341

Custom control derived from Component - OnCreate event?

I've created simple custom control - derived from Component class:
public partial class TrialChecker : Component
{
private int _trialDays;
public int TrialDays
{
get { return _trialDays; }
set { _trialDays = value;}
}
public TrialChecker()
{
InitializeComponent();
MessageBox.Show(TrialDays.ToString());
}
public int GetTrialDays()
{
return _trialDays;
}
}
This control will be used to implement trial functionality in my application. Application (before it starts) should check trial remaining days and display notify dialog containing trial remaining days and textbox to write unlock key.
But I want to minimalise amount of code needed to wirte while using this control. So, my idea is to place trial check code inside my control and - just after control is created, it should display remaining days.
Trial period (TrialDays property) is set on user designer and it should be available to use just afeter control is created. As you can see, I tried to put this to constructor but it does not work, because constructor is called before setting TrialDays to valuje entered in user designer. And MessageBox always displays default value 0.
There is no any OnLoad or OnCreate events abailable to override. So, how can I automatically check trial status using value entered in designer?
The Component class is very simple, it just provides a way to host the component on a form at design time, giving access to its properties with the Properties window. But it has no notable useful events, using a component requires explicit code in the form. Like OpenFormDialog, nothing happens with it until you call its ShowDialog() method.
The constructor is usable but unfortunately it runs too early. The DesignMode property tells you whether or not a component runs at design time but it isn't set yet at constructor time. You'll need to delay the code and that's difficult because there are no other methods or events that run later.
A solution is to use the events of the form that you dropped the component on. Like the Load event. That requires some giddy code to coax the designer to tell you about the form. That technique is used by the ErrorProvider component, it requires exposing a property of type ContainerControl and overriding the Site property setter. Like this:
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
public partial class Component1 : Component {
private ContainerControl parent;
[Browsable(false)]
public ContainerControl ContainerControl {
get { return parent; }
set {
if (parent == null) {
var form = value.FindForm();
if (form != null) form.Load += new EventHandler(form_Load);
}
parent = value;
}
}
public override ISite Site {
set {
// Runs at design time, ensures designer initializes ContainerControl
base.Site = value;
if (value == null) return;
IDesignerHost service = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (service == null) return;
IComponent rootComponent = service.RootComponent;
this.ContainerControl = rootComponent as ContainerControl;
}
}
void form_Load(object sender, EventArgs e) {
if (!this.DesignMode) {
MessageBox.Show("Trial");
}
}
}
The code is inscrutable, but you can be pretty sure it is reliable and stable because this is what the ErrorProvider component uses. Be sure to call Environment.Exit() when the trial period has ended, an exception isn't going to work well.

Moving get/set methods in another class

Is it possible to move the get set methods in another class ?
I'm using an options form which basically reflects all the changes directly in the main form (mostly for changing controls colors,fonts and so on.
The issue starts when you start modifying quite a lot of controls since the main class fills with get set methods, so I was wondering if it's possible to refactor the code to increase the readability of the class a bit, or even better, if it's possible to move the methods in another class somehow (partial classes ?)
Here's a small example of just two controls
public Font TreeFont
{
get { return customTreeView1.Font; }
set { customTreeView1.Font = value; }
}
public Font TextBoxFont
{
get { return customTextBox1.Font; }
set { customTextBox1.Font = value; }
}
public Font MenusFont
{
get { return menuStrip1.Font; }
set
{
menuStrip1.Font = value;
statusStrip1.Font = value;
contextMenuStripForSnippetContent.Font = value;
contextMenuStripTreeViewMenu.Font = value;
}
}
public Color TreeFontForeColor
{
get { return customTreeView1.ForeColor; }
set { customTreeView1.ForeColor = value; }
}
public Color TextBoxFontForeColor
{
get { return customTextBox1.ForeColor; }
set { customTextBox1.ForeColor = value; }
}
public Color TreeFontBackgroundColor
{
get { return customTreeView1.BackColor; }
set { customTreeView1.BackColor = value; }
}
public Color TextBoxFontBackgroundColor
{
get { return customTextBox1.BackColor; }
set { customTextBox1.BackColor = value; }
}
So as you can imagine since there are quite a lot of them that need to be changed the lines just pile up.
In addition, would it be a better practice to just return the control and just work on that instead on the other form or do get/set methods considered a better practice ?
Thanks in advance.
If I understand you correctly - the problem is not the "class" but the "file". So you can simply split the class into two files (just like Visual Studio does with the InitializeComponent method) using Partial Classes.
Make sure the namespace is the same (If you create the file in a sub-folder you'll get a nested namespace. Simply change it.) Also, make sure your class begins with public partial class in both files. And don't have the same property declared in both classes.
Step by step instructions:
Right-click on your project in "Solution Explorer". Click "Add". Click "New Item". Click "class". Change class Class1 to public partial class Form1 : Form. Add using System.Windows.Forms; at the top of your file. Add your properties.
You can use either C# Regions to make a large code file manageable or you can use Partial Classes to split a large code file into multiple manageable files.
You could use a different kind of function that allows for Page.FindControl("controlNameHere"), and cast it in the right light. This is more for ASP.NET pages, not for Windows form, but you could find the same resolution here Find control by name from Windows Forms controls. This way you could pull the control name and manipulate without ever having to return anything.

Categories

Resources