Prevent Height control resizing at design-time - c#

SCENARIO
When a user drops a TextBox control on the WindowsForms designer, the designer shows only two sizing selectors to resize the width of the control:
...Unless the TextBox.MultiLine property is manually enabled.
But if we add a RichTextBox, it shows 8 sizing selectors:
...even when the RichTextBox.MultiLine property is enabled.
QUESTION
What I would like to do is subclass a RichTextBox class to mimic the sizing behavior that a TextBox has by default at design-time, this means prevent height/corner sizing if the RichTextBox is not multiline.
To be exact, I would like to REMOVE/HIDE the height and corner sizing selectors at design-time, so the subclassed RichTextBox should show only two sizing selectors to resize the width of the control, like in the image above of the TextBox.
I'm aware of the methodology to override SetBoundsCore method to prevent height resizing at design-time, however I would like to go a little bit more far than that solution, because that solution does not remove those sizing selectors ...and just letting the size selectors visible is a ugly and confussing behavior at design-time.
I inspected the official TextBox class source-code to see what happens when the TextBox.MultiLine property value is changed, but I didn't seen anything relevant.
Maybe the DesignerAttribute() class assigned to the TextBox class (System.Windows.Forms.Design.TextBoxBaseDesigner) is involved and maybe it is who decides the sizing behavior at design-time?, in that case what I could do and how to do it?.

Those are called Sizing Handles and are determined by the SelectionRules() method in the designer associated with your control. One thing to keep in mind is that the default for a regular TextBox is MultiLine = False but it is the opposite for a RichTextBox.
The reason you could not find anything relevant in the Reference Source is because the System.Windows.Forms.Design.TextBoxDesigner is internal / Friend. Note also that changing the MultiLine property causes the control to be recreated (RecreateHandle(); in the source).
Imports System.Windows.Forms.Design
<Designer(GetType(RTBElektroDesigner))>
Public Class RTBElektro
Inherits RichTextBox
Public Sub New()
End Sub
End Class
Public Class RTBElektroDesigner
Inherits System.Windows.Forms.Design.ControlDesigner
Public Overrides ReadOnly Property SelectionRules() As SelectionRules
Get
Dim rtb = TryCast(MyBase.Control, RTBElektro)
If rtb Is Nothing Then
Return MyBase.SelectionRules
Else
If rtb.Multiline Then
Return SelectionRules.AllSizeable Or
SelectionRules.Moveable
Else
Return SelectionRules.LeftSizeable Or
SelectionRules.RightSizeable Or
SelectionRules.Moveable
End If
End If
End Get
End Property
End Class
Result:

This behavior is implemented by the TextBoxBaseDesigner. Also the base class for the RichTextBoxDesigner so you're good with the designer. What is missing here is the AutoSize property, RichTextBox hides it. It needs to be set to True when you change the Multiline property to False. You can't do that from the designer because it is hidden and the default value is False.
That's easily fixable by deriving your own class from RichTextBox:
using System;
using System.ComponentModel;
using System.Windows.Forms;
class RichTextBoxEx : RichTextBox {
public RichTextBoxEx() {
base.AutoSize = true;
base.Multiline = false;
}
[DefaultValue(true), Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
public override bool AutoSize {
get => base.AutoSize;
set => base.AutoSize = value;
}
[DefaultValue(false)]
public override bool Multiline {
get => base.Multiline;
set {
base.Multiline = value;
base.AutoSize = !base.Multiline;
}
}
}

Related

Keeping Adorner Glyphs On Top for Selected Control in Winforms Designer

TLDR: I'm seeking the correct method for timing the rendering of a juxtaposed graphic for a particular control on a design surface so that the graphic always is painted ahead of the adornment glyphs when that control is selected.
This question concerns control designers for Winforms: When the user places a control on the design surface, I want to display a graphic above the client area of the control. I have succeeded to some extent doing that for a TableLayoutPanel (TLP) control by overriding its OnPaint event handler then using the e.Graphics object available to paint a peach-colored rectangle. Below is an image showing the results: a painted graphic that spans the width of the control and is 35 pixels high--remember, this is a designer instance of a control placed on a design surface (created with a BasicLoader):
However, within the designer, if I resize the control, the graphic always ends up below the resize glyph (the glyph that has the North/South and West/East arrows on it):
I've tried creating and maintaining various Boolean flags to suppress the OnPaint message under certain circumstances. For instance, I set a flag to indicate that the control was just resized (to see how I did that, see my recent question: BeginResize/EndResize Event for Control on WinForms Design Surface) in order to suppress the painting of the graphic, but that didn't work because an OnPaint event is inevitably raised after I've cleared a flag. I don't want saddle this question with details of all the flags and places I tried to use/set them but suffice it to say that I painstakingly spent hours experimenting--to no avail. I've concluded that there must be a better way.
How can I ensure that the glyphs remain on top when I paint my graphics?
Thank you!
I can think of a few solutions, including the followings:
Using Padding of the TableLayoutPanel
Using Adorner and Glyph
Creating a custom panel, having header and editable content
I think the first solution will suit you well, however the other solutions also some points.
I can also think of a solution based on NativeWindow like what has been implemented in ErrorProvider, but It makes the post toooooo lengthy while the existing options are good enough. So I leave it to you if you like to pursue the idea.
Solution 1 - Using Padding of the TableLayoutPanel
This solution is for both design-time and run-time
TableLayoutPanel has a Padding property and its layout engine respects to the padding well. You can use the padding area to render whatever you want:
public class MyTLP : TableLayoutPanel
{
public MyTLP()
{
Padding = new Padding(0, 30, 0, 0);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(Brushes.Orange,
new Rectangle(0, 0, ClientRectangle.Width, Padding.Top));
}
}
Solution 2 - Using Adorner and Glyph
This solution is just for design-time
For design-time rendering, I'll handle it using Adorner and Glyph.
If I was creating my custom designer, all the code belong to the control designer, but since you don't want to create a new control designer for TableLayoutPanel, then the same way that I injected a new custom action in the action lists, here I'll get BehaviorService and I'll inject an adorner to the adorners of the control at design time and this will be the result:
The behavior is quite similar to the other glyphs, it will be resized automatically when the control resizes and you don't need to do anything specific to handle the resize at design time.
Please note: It's a design-time solution and the painting is just being done at designer. In case you need a run-time solution, you need a totally different solution.
Here is the code:
using System;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Windows.Forms.Design.Behavior;
public class MyTLP : TableLayoutPanel
{
private IDesignerHost designerHost;
private BehaviorService behaviorService;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (DesignMode && Site != null)
{
designerHost = Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
behaviorService = designerHost?.GetService(typeof(BehaviorService))
as BehaviorService;
if (behaviorService != null)
{
var adorner = new Adorner();
behaviorService.Adorners.Insert(0, adorner);
adorner.Glyphs.Add(new MyTLPGlypg(behaviorService, this));
}
}
}
}
class MyTLPGlypg : Glyph
{
Control control;
BehaviorService behaviorSvc;
public MyTLPGlypg(BehaviorService behaviorSvc, Control control) :
base(new MyBehavior())
{
this.behaviorSvc = behaviorSvc;
this.control = control;
}
public override Rectangle Bounds
{
get
{
var edge = behaviorSvc.ControlToAdornerWindow(control);
var h = 30;
return new Rectangle(edge.X, edge.Y - h, control.Size.Width, h);
}
}
public override Cursor GetHitTest(Point p)
{
//Uncomment if you want to attach a specific behavior
//if (Bounds.Contains(p)) return Cursors.Hand;
return null;
}
public override void Paint(PaintEventArgs pe)
{
pe.Graphics.FillRectangle(Brushes.Orange, Bounds);
}
}
class MyBehavior : Behavior
{
public override bool OnMouseUp(Glyph g, MouseButtons button)
{
//Do something and return true, meand eventhandled
return true;
}
}
Note:
To learn more about anchors, glyphs and behavior, take a look at the following links:
How to: Extend the Appearance and Behavior of Controls in Design Mode
Adorner
Glyph
Behavior
Solution 3 - Creating a custom panel, having header and editable content
You can create a custom panel having header and editable content. Then at design time disallow user from dropping content on the header part:
To do so, you need to create a new designer which enables the inner panel on design-time by calling EnableDesignMode method. Then for the inner panel, you need to create a designer which disables moving, resizing and removes some properties from designer.
I've posted a detailed answer here: UserControl with header and content - Allow dropping controls in content panel and Prevent dropping controls in header at design time

How to get WinForms custom control's default value to be respected when first dropped on a form

I have a class library with a custom control in it:
using System.ComponentModel;
using System.Windows.Forms;
namespace ClassLibrary1
{
public sealed class CustomLabel : Label
{
[DefaultValue(false), Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override bool AutoSize
{
get => base.AutoSize;
set => base.AutoSize = value;
}
public CustomLabel()
{
AutoSize = false;
}
}
}
Notice that AutoSize is set to false in both the constructor and the designer attribute on the overridden method.
I have a winforms project where I want to use the control. I drag/drop it from the toolbox, but it doesn't have AutoSize set to false:
If I save and close the form and then re-open it, now it's set correctly:
How can I make it respect the property value when first dropped on the form?
The default values which you assign in constructor are respected in general. But for some cases the default values will be changed using designer, for example by the CreateComponentsCore method of ToolboxItem of the control.
The default value for AutoSize property for Label is false and you even don't need to override it or set it in constructor. But an AutoSizeToolboxItem has been assigned to Label which sets AutoSize to true when you drop an instance of Label on designer. To remove this behavior, it's enough to assign a new ToolboxItemto your control:
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
namespace ClassLibrary1
{
[ToolboxItem(typeof(ToolboxItem))]
public sealed class CustomLabel : Label
{
}
}
Note 1: Just for your information, the ToolboxItem has a CreateComponentsCore method which you can use it to to some initialization tasks when dropping control on design surface.
Note 2 I should also add, the CreateComponentCore method will just run when you drop the component from toolbox to design surface. It describes why after dropping it on form, it's auto-size, because it's set by CreateComponentCore after your constructor. But after you open the form again, this time, just your constructor will run and set the property to false.
The DefaultValueAttribute has no bearing on it: it mainly controls whether the property value should be serialized or not and whether the value should show in bold in the property editor window.
If you watch the designer code, initially it gets written out explicitly saving AutoSize as true. Apparently it saved the value because it doesnt match the value specified by the DefaultValue but it is saving the wrong value - apparently the base control hasnt gotten the update yet. Any change causes it to serialize the form again, this time with the correct value.
I dont know exactly why certain properties dont like being overridden and changed from the constructor, but there are a few that don't immediately take. AutoSize is one that gets handled thru SetStyle calls and/or thru some CommonProperties helper.
One way to set some of these is to implement ISupportInitialize to set the value after the control has been set from the designer properties. A simpler way is to override OnHandleCreated:
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
base.AutoSize = false;
}
Seems to work as desired.

Creating winforms base controls

We have several winforms applications we want to restyle and make consistent (styling wise). We want to create some base classes for some of the windows controls such as buttons and toolstrip. All my base classes intend to do is specify the specific styling we wish to apply. This works the first time I bring into my form but later on if I wish to update a property it doesn't apply it back to the forms that previously already have pulled it in. Anyway to get this to work? Here's an example, lets say I want to create a base class for a toolstrip. All I want to do is set the backcolor to green (I really want to set more properties but this is to demo my issue). So I create a class library called BaseControls and create a class called BaseToolStrip and set the backcolor to green in its constructor.
public class BaseToolStrip : ToolStrip
{
public BaseToolStrip()
{
BackColor = Color.Green;
}
}
Now if I create a demo project and add an instance of BaseToolStrip to the form it works. It creates a green toolstrip. Later if I decided I want my base toolstrip to have a backcolor of red if I update the constructor in my BaseToolStrip class and set the BackColor = Color.Red and run my solution the color is still green. It appears that once I've brought that control in it set backcolor = green. So even though in my base class I set backcolor = red in my form that instantiates it still has backcolor = green. Any way to override all instances and set backcolor equal to the color specified in my BaseToolStrip class?
Your problem is that the WinForms designer is saving the current value in InitializeComponent() in any designer that uses that control.
That happens after your ctor runs and replaces your value.
You can prevent that by overriding / shadowing the property and adding [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] (you'll need to reopen & resave all designers that use it to remove the property).
Try setting the default value for the property (as well as setting the actual value in the ctor):
[DefaultValue(typeof(Color), "Green")]
public override Color BackColor {
get { return base.BackColor; }
set { base.BackColor = value; }
}
I do not have VS available to test right now, but I think that will cause the designer to notice that the current value is the default, and therefore doesn't need to be saved in the Designer.cs.
So if you change the color in both places later, it should update everywhere.
Of course, this will only work for controls that you drag onto a form after you add this code, since the others already have the value saved.

Visual's bug? (a problem with overriden panel)

I've got a really weird problem and i'm wondering if it's a visual's bug or if i'm doing something wrong. Here's a simple code of an overriden Panel class:
public class MyPanel : Panel
{
private TableLayoutPanel table = new TableLayoutPanel();
public MyPanel()
{
this.Controls.Add(table);
table.BackColor = Color.Green;
}
public override System.Drawing.Color BackColor
{
get
{
return table.BackColor;
}
set
{
table.BackColor = value;
}
}
}
If i put the control on a form and build the project, visual will generate an error and opening the project again will be impossible. However if i change TableLayoutPanel to TextBox, it works fine. Also, if i set the BackColor in the constructor before adding the control to the Controls collection, it also works fine.
What is the problem? or is it just a bug?
I suspect recursion may be an issue; by default (if not set explicitly) a control inherits color from the parent. This leads to the scenario where the child's color (if not set) asks the parent, which asks the child (forever).
TextBox, however, overrides this behaviour to return SystemColors.Window if there isn't an explicit color set. Hence no recursion.
Either way, I'm not sure this is a good idea - the designer might start duplicating controls if you aren't careful.

visual studio - remove custom control from designer window

I have a user control panel that has two buttons on it. Other user controls inherit from this control and set a property to true if the buttons should be visible. Everything runs how I want it to but what I'm looking for is a way to clear these buttons from the designer window for forms where this property is left at false.
looks like:
[DefaultValue(false)]
public bool ShowButtons{
set
{
mShowButtons = value;
UpdateButtons();
}
get
{
return mShowButtons;
}
}
This property shows in the properties window and the buttons are always shown in the designer window. Is there some way to have the designer evaluate this when the property is changed to get the buttons to clear from the inheriting form? I was unable find a designer attribute to do this.
Try adding a get:
bool mShowButtons;
[DefaultValue(false)]
public bool ShowButtons
{
get
{
return mShowButtons;
}
set
{
mShowButtons = value;
UpdateButtons();
}
}
Now when editing your derived class in the Designer, you should be able to see a ShowButtons property in properties window when the derived UserControl is selected. (It will be in the "Misc" section unless you add the appropriate attribute). If you set it there, it should have the appropriate affect in the Designer (Assuming the contents of the UpdateButtons() function work correctly)).
A property must be public and have bot get and set in order to display in the Properties editor window. Once it is, then setting the value in the properties window will "save" that setting for the designed control in the control's resources/implementation.
I use this functionality quite often to specialize derived UserControls, so I know it should work for you (although there may be other issues at play).

Categories

Resources