1

I have classes called ButtonDesign, TextBoxDesign, and a few more. Everyone’s realization is exactly the same. I add a number of properties and functions to each control, the question is how can this be achieved without duplicate code, i.e .: is there a way to create only one class with the same attributes - and these attributes will be added to all the controls I want? . If I create a primary class that inherits from the Control class - then these attributes will only be in the Control class and not in all the controls I want.

This is the code I'm trying to:

  class ButtonDesign : Button
{
    private Control save_properties = new Control();

    public Color OnMouseHoverColor { get; set; }

    public ButtonDesign()
    {
        this.MouseEnter += ButtonDesign_MouseEnter;
        this.MouseLeave += ButtonDesign_MouseLeave;
    }

    private void ButtonDesign_MouseEnter(object sender, EventArgs e)
    {
        save_properties.BackColor = this.BackColor;
        this.BackColor = this.OnMouseHvetColor;
    }

    private void ButtonDesign_MouseLeave(object sender, EventArgs e)
    {
        this.BackColor = save_properties.BackColor;
    }
}
class TextBoxDesign : TextBox
{
    private Control save_properties = new Control();

    public Color OnMouseHoverColor { get; set; }

    public TextBoxDesign()
    {
        this.MouseEnter += ButtonDesign_MouseEnter;
        this.MouseLeave += ButtonDesign_MouseLeave;
    }

    private void TextBoxDesign_MouseEnter(object sender, EventArgs e)
    {
        save_properties.BackColor = this.BackColor;
        this.BackColor = this.OnMouseHvetColor;
    }

    private void TextBoxDesign_MouseLeave(object sender, EventArgs e)
    {
        this.BackColor = save_properties.BackColor;
    }
}
  • 1
    Use a common base class. BTW, When you build a Custom Control, you don't subscribe to the events, you override the Methods. E.g., not this: `this.MouseEnter += ButtonDesign_MouseEnter;` + this: `private void ButtonDesign_MouseLeave(object sender, EventArgs e) { }`, but just add code to `protected override void OnMouseEnter(EventArgs e) { }`. – Jimi Aug 13 '21 at 11:11
  • Just start writing `protected override`, hit space and Intellisense will show you all the protected methods you can override. You can also override some public methods. – Jimi Aug 13 '21 at 11:16
  • 1
    I think you need to realize something like AttachedProperty (like in WPF), but surely you need and mechanism to process such "virtual" properties for your control. If you need same properties, but different behaviour, you can create interfaces and implement it on your controls. – Spawn Aug 13 '21 at 11:18
  • For example, [here](https://stackoverflow.com/a/56533229/7444103) is a Form class derived from a base class that inherits `Form`. You can do the same with your Controls, using a common class that inherits Control. Most of the WinForms Framework is built like that. All Properties / Methods (and their functionality) that you add to the base class are inherited by the classes that are derived from this base class. If the functionality is common, you can use the base class, otherwise you can override methods of the base class to create a custom behavior. – Jimi Aug 13 '21 at 11:26
  • Also, read the notes here: [How to get the size of an inherited form in the base form?](https://stackoverflow.com/a/55760332/7444103) about the initialization sequence of derived classes, in relation to the initialization phase of the base class (just some basic description). – Jimi Aug 13 '21 at 11:31
  • 1
    I'd suggest using [extender providers](https://stackoverflow.com/a/42078809/3110834) for this requirement. – Reza Aghaei Aug 13 '21 at 11:47
  • As another option, you have a ThemeRenderer component and add the common properties and behaviors to the ThemeRenderer. Then each derived control can have a property of ThemeRenderer type and they know how to use the renderer's properties and methods. (Somehow similar to ToolStripRenderer. ) – Reza Aghaei Aug 13 '21 at 14:08

2 Answers2

1

Congratulations, you've encountered the diamond inheritance problem. C# doesn't support this scenario, so what you're attempting to do is unfortunately not possible.

Your only options are:

  • Create a subclass of Control and make your custom controls inherit from it. This means you have to recreate the behaviour of the standard WinForms controls in your subclasses, which is painful at best.
public class MyControl : Control {}

public class MyButton : MyControl {}
  • Create an interface that your common controls implement. Have the interface implementations delegate to a shared library that does what needs to be done:
public interface IMyControl
{
    void MySharedOperation();
}

public class MyButton : Button, IMyControl
{
    public void MySharedOperation()
        => MySharedOperationHandler.MySharedOperation(this);
}

public static class MySharedOperationHandler
{
    public static void MySharedOperation(Control control) {}
}

You'll end up with a fair amount of method implementations that do nothing more than delegate, but IMO this is far better than reinventing the control wheel as in the previous option.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
0

If you want this, your class ButtonDesign is not a Button, it has a Button and a Layout. Similarly your class TextBoxDesign has a TextBox and a Layout.

In other words: don't use inheritance, use aggregation!

Every property that are both in Buttons, TextBoxes, and other Controls that have the properties that you want to change for all items in one statement, create a class that contains all these properties, with the proper events.

For every property you need a private member, a public get/set property and an event that will be raised when the property changes. Something like this:

class Layout
{
    private Color backColor;    // TODO: give proper initial values
    private Color foreColor;
    ... // other properties that you want to change for all Controls

    // Events:
    public event EventHander BackColorChanged;
    public event EventHandler ForeColorChanged;
    ... // etc

    // Event raisers:
    protected virtual void OnBackColorChanged()
    {
         this.BackColorChanged?.Invoke(this, EventArgs.Empty);
    }
    protected virtual void OnForColorChanged()
    {
         this.ForeColorChanged?.Invoke(this, EventArgs.Empty);
    }
    ...

    // Properties:
    public Color BackColor
    {
        get => this.backColor;
        set => if (this.BackColor != value) this.OnBackColorChanged();
    }
    public Color ForeColor ... 
    public Size Size ...
}

Your DesignButton will be a UserControl that has a Docked Button and a Layout. The constructor subscribes to the events. When raise the corresponding property on the button is set.

Use the visual studio designer to create the UserControl. Code will be similar to the following:

class MyButton : UserControl
{
    private Button button; // visual studio designer will create this
    private Layout layout;

    public Layout Layout
    {
        get => this.layout;
        set => if (this.Layout != value) this.ChangeLayout;
    }

    // you can't set the design properties. Only get.
    protected Button Button => this.button;
    Color BackColor
    {
        public get => this.Button.BackColor;
        private set => this.Button.BackColor = value;
    }
    // etc for ForeColor, Text, ...

    protected virtual void ChangeLayout(Layout newLayout)
    {
        // TODO: if there is an old Layout: desubscribe from all events from old Layout
        
        this.layout = newLayout;
        // subscribe to all events:
        this.layout.BackColorChanged += this.BackColor_Changed;
        this.layout.forColorChanged += this.ForColor_Changed;
        ...

        this.BackColor = this.BackColor;
        

        // TODO: if desired, for completeness add an event: LayoutChanged
    }

Now whenever Layout raises event BackColorChanged you handle this event and assign the value to the Button. You'll get the gist.

Do something similar for TextBoxes, ComboBoxes, etc

Usage:

Layout commonLayout = new Layout
{
    BackColor = Color.Yellow,
    ForeColor = color.Black,
    ...
};
MyButton button1 = new MyButton
{
    Layout = commonLayout,
};
MyTextBox textBox1 = new MyTextBox
{
    Layout = commonLayout,
}
MyComboBox comboBox1 = ...

// Change the backgroundColor for all items:
commonLayout.BackColor = Color.Red;

If you have a lot of properties, consider to use generic classes

class LayoutProperty<T>
{
    private T propertyValue;
    public event eventHandler PropertyValueChanged

    protected void OnPropertyValueChanged()
    {
        this.PropertyValueChanged?.Invoke(this, EventArgs.Empty);
    }

    public T PropertyValue
    {
        get => this.propertyValue;
        set => if (this.PropertyValue != value) this.OnPropertyValueChanged();
    }
}

class Layout
{
     private PropertyValue<Color> backColor;
     private PropertyValue<Color> foreColor;

     // etc, see above for subscribtion and raising events.

And for all Controls:

public MyControl
{
    private Control control;
    private Layout layout;

    // etc, see above for the event handling.
}

public MyButton : MyControl {Control = new Button()}
public MyTextBox : MyControl {Control = new Textbox()}
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116