2

I have the following class:

public partial class RichTextBoxEx : RichTextBox
{
    public RichTextBoxEx()
    {
        InitializeComponent();
        Text = "Some Text";
    }
}

However, when I place it over a form and run the program, the RichTextBox is empty. What is the problem and how can I fix it?

enter image description here

I assume there is something basic I'm missing here, but I cannot figure out what, and I did not manage to find any information about this.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Michael Haddad
  • 4,085
  • 7
  • 42
  • 82
  • Try to call the base constructor? Instead of Initializing the components yourself – Zinov Feb 11 '19 at 17:55
  • @Zinov - Do you mean like `public RichTextBoxEx() : base() { Text = "some text"; }`? Doesn't work. – Michael Haddad Feb 11 '19 at 17:56
  • This is due to the `InitializeNewComponent` method of the RichTextBoxDesigner. Specifically, it is in the `InitializeNewComponent` method of the TextBoxBaseDesigner class that sets the `Text` property to an empty string. The proper way to address this issue is to create a designer for your custom class. Alternatively, you could hack a hook into the designer host's selection service that would set the `Text` property only while the host is not loading. This would be done via overriding the control's `Site` property setter to set the hook. – TnTinMn Feb 12 '19 at 03:02

4 Answers4

2

The property values which you set in constructor of the control are usually respected. But for Text property, the case is a bit different. I've already described about it in another answer. In fact it's the control designer which sets Text property of the control in InitializeNewComponent.

As an option, you can create and register a new control designer, override InitializeNewComponent and capture the Text property value before calling base.InitializeNewComponent method. Then after calling base method, set the Text property again to the default value.

using System.ComponentModel;
using System.Windows.Forms;
using System.Windows.Forms.Design;
[Designer(typeof(RichTextBoxExDesigner))]
public class RichTextBoxEx : RichTextBox
{
    public RichTextBoxEx ()
    {
        Text = "Some Text";
    }
}
public class RichTextBoxExDesigner : ControlDesigner
{
    public override void InitializeNewComponent(System.Collections.IDictionary defaultValues)
    {
        var txt = Control.Text;
        base.InitializeNewComponent(defaultValues);
        Control.Text = txt;
    }
}

Note: Don't forget adding reference to System.Design assembly.

Side note: Not for Text property, but for other similar cases which you see a property value is not respected when you set in constructor, another suspect is CreateComponentsCore of ToolboxItem of the control. For example for AutoSize property of the Label.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • 1
    For undetermined reasons, the WinForms' Designers are (apparently) considered an *obscure* matter. Probably, the Text property could be shadowed and the `AutoSize` property of a label could be set overriding `OnLayout`. But learning to use a custom Designer is the real deal, IMO. – Jimi Feb 12 '19 at 10:05
  • @Jimi Shadowing some times may do the trick while in general IMO shadowing is not a good idea. In this case, shadowing will not solve the problem. Because setting the `Text` value is done by the control designer when you drop the control on the form. – Reza Aghaei Feb 12 '19 at 11:04
  • 1
    Shadowing may work, it depends on the purpose. Something like this: `public new string Text { get => base.Text; set { if (string.IsNullOrEmpty(value)) { base.Text = this.defaultText; } else { base.Text = value; } } }`. It will set the Text to a default value if the Text is an empty string. It can work, in a way. But this is, at least, shallow and subject to unexpected misbehaviours. I do agree that shadowing is also not a good idea (even though you can find this applied a number ot times in the.Net Source code). Hence the comment: learning to use Custom Designers is the way to go. – Jimi Feb 12 '19 at 11:41
  • Using this code I get the following error: `The type or namespace name 'ControlDesigner' could not be found (...)`. – Michael Haddad Feb 12 '19 at 13:15
  • And also `An object reference is required for the non-static field, method, or property 'Control.Text'`. – Michael Haddad Feb 12 '19 at 13:17
  • Apparently, I needed to add a reference to `System.Design`... Anyway, when I try to add a `RichTextBoxEx` instance to the form using the VS Designer, I get a hugh error message, starting with `Failed to create component 'RichTextBoxEx` – Michael Haddad Feb 12 '19 at 13:22
  • @Sipo Add an empty contructor in the Designer class `public RichTextBoxExDesigner() { }` – Jimi Feb 12 '19 at 13:44
  • @Sipo Well, I've tested it (just to make sure there wasn't something I didn't see looking at it) and it's working as expected. No errors of any kind. It's also working as described (that was also expected :). Did you change something, trying to make it work, by chance? – Jimi Feb 12 '19 at 14:02
  • @Sipo When using a custom `ControlDesigner` for a control, make sure you close all designers and clean and rebuild the solution. (Not common, but in some rare cases, you even need to delete the `Proj‌​ectAssemblies` folder which you find under Visual Studio folder under `%userprofile%\appdata\local\Microsoft\VisualStudio\`. Just bookmark the address somewhere and some day it may help.) – Reza Aghaei Feb 12 '19 at 16:58
  • Let me know if you have any problem in applying or verifying the solution. – Reza Aghaei Feb 20 '19 at 18:06
1

The Text property gets reseted upon InitializeComponent of the Form.

When you have a look at the Designer.cs file of the Form you should find a line like the following:

private void InitializeComponent()
{
    this.richTextBoxEx1 = new WindowsFormsApp1.RichTextBoxEx(); //<-- RichTextBoxEx gets initialized and ITS constructor and InitializeComponent gets called
    this.SuspendLayout();
    // 
    // richTextBoxEx1
    // 
    this.richTextBoxEx1.Location = new System.Drawing.Point(322, 106);
    this.richTextBoxEx1.Name = "richTextBoxEx1";
    this.richTextBoxEx1.Size = new System.Drawing.Size(100, 96);
    this.richTextBoxEx1.TabIndex = 0;
    this.richTextBoxEx1.Text = ""; //<-- Text Property gets reseted
    // 
    // Form1
    // 
    this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(800, 450);
    this.Controls.Add(this.richTextBoxEx1);
    this.Name = "Form1";
    this.Text = "Form1";
    this.ResumeLayout(false);

}

You can overcome this by overriding the OnCreateControl

So change your control to this:

public class RichTextBoxEx : RichTextBox
{
    protected override void OnCreateControl()
    {
        Text = "Hello World";
        base.OnCreateControl();
    }
}

If the OnCreateControl gets called multiple times - altough the definition of it on MSDN states:

The OnCreateControl method is called when the control is first created

Then you could force it to be called once by using a boolean to track if it got called or not, so try the following:

public class RichTextBoxEx : RichTextBox
{
    private bool _initialized = false;
    protected override void OnCreateControl()
    {
        if (!_initialized)
        {
            _initialized = true;
            Text = "Hello World";
        }

        base.OnCreateControl();
    }
}
Rand Random
  • 7,300
  • 10
  • 40
  • 88
  • @Jazimov - I am not talking about the `InitializeComponent` of the `Control` but of the `InitializeComponent` of the form. - I extended the `Designer.cs` to make it clearer. - Btw, this code is tested and works on my machine. – Rand Random Feb 11 '19 at 18:39
  • This is true. It happens all the time :) – Jimi Feb 11 '19 at 18:44
  • This answer works for me, but I will check the other answers before accepting this. – Michael Haddad Feb 11 '19 at 18:45
  • @Rand--Apologies--you're right. The InitializeComponent() shown would be for the component and not for the form... – Jazimov Feb 11 '19 at 18:48
  • @RandRandom - When I add to `OnCreateControl` this line: `BorderStyle = BorderStyle.None;` - the method is called twice. How can I eliminate this? – Michael Haddad Feb 11 '19 at 20:01
  • @Sipo Much depends on the reason why you're doing this. You could also shadow (`new`) the Text property, setting it to a predefined value if the Text is empty, for example. Using the override here, without any further check, you'll also override the Text set by a User. – Jimi Feb 11 '19 at 20:12
  • @Jimi - I am not sure what do you mean, or what are you responding to. :/ – Michael Haddad Feb 11 '19 at 20:13
  • My comment is related to what you're asking to Rand Random and, overall, about the functionality you're trying to achieve. The *use case* is relevant, because the method can change depending on what this *predefined* Text is for. – Jimi Feb 11 '19 at 20:20
  • Yes, that's pretty much understood :) The reason why is not. You could simply set the Text in the Designer. Unless you're building a library that includes custom controls. Or, maybe, a sort of *watermark* instead. Or simply a Text that is presented when a User doesn't set any. Depending on these (or other) *use cases*, one method can be chosen in place of another. To note that a control's handle could be recreated multiple times in the same run. – Jimi Feb 11 '19 at 20:31
  • For example, see [here](https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/TextBoxBase.cs,388) (.Net source). When you set the BorderStyle of a TextBoxBase derived control, the property setter calls `RecreateHandle()`. Hence, both `OnHandleCreated` and `OnControlCreated` (plus `CreateParams`, but it's unrelated here) events will be rised because of this. – Jimi Feb 11 '19 at 20:44
  • This way it will always set the `Text` property to `"Some Text"`. It should behave like other properties, having a default value also let the users to change the property value at designer and not overwrite it again to the default value at run-time. I doubt the OP is looking for a read-only `Text` property, but if it's the case, then overriding the `Text` property is the right path to go. – Reza Aghaei Feb 12 '19 at 07:00
  • @RezaAghaei - don't know why you are implying that my solution makes the `Text` property read-only - even the definition found on MSDN states this `The OnCreateControl method is called when the control is first created` - so the `OnCreateControl` should only be called once - so, why should my code make it read-only? Yes, my code has no designer support and the "Default" value will be set upon run-time, but why you believe it to be read-only I don't understand. – Rand Random Feb 12 '19 at 14:55
  • @Rand Random About `OnCreateControl`, read my comment above. Simple to test, too. – Jimi Feb 12 '19 at 15:22
  • @RandRandom I'm saying it doesn't respect to the value which user enters at design time and will always replace it with default value when you run the code. – Reza Aghaei Feb 12 '19 at 16:44
0

I'm not sure how you implemented your class. When I tried to reproduce your problem, I made a class and then added the using System.Windows.Forms then created the class. Much as you did, but I didn't end up with a public partial class nor an InitializeComponent() method called in my constructor (which I had to write).

Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace FormTester
{
    public class RichTextboxEx : RichTextBox
    {
        public RichTextboxEx() : base()
        {
            Text = "Some Text";
        }
    }
}

I believe this worked as you had intended. Give it a try.

jonr79
  • 25
  • 9
0

It wasn't clear to me how you were instantiating the RichTextBox, so this might or might not be useful...

When you wrote that you placed a RichTextBox control "over a form", I assumed that meant that you drag/dropped the control from the toolbox onto the designer surface. If you had done that, then you would be getting an instance of RichTextBox and not RichTextBoxEx.

To get an instance of RichTextBoxEx, you could compile it to a DLL and add it to your toolbox.

An alternative approach, giving you some more control over object instantiation/inititialization, is to instantiate it in code and add it to your form that way. In the form's constructor you could do this:

var richTextBoxEx = new RichTextBoxEx();
// set your richTextBoxEx properties here
richTextBoxEx.Top = 100;
richTextBoxEx.Left = 100;

this.Controls.Add(richTextBoxEx);

While you could set properties as shown above, you also can do it (as you did) in the subclassed control's constructor.

I hope this provides an alternative way to approach this.

Jazimov
  • 12,626
  • 9
  • 52
  • 59