3

I have a button which I use all the time as a little pick button next to a combobox. When I click the button I open a larger full list. This side of things work well and I do not have a problem with this..

My problem lies when someone said to me can you change that ugly icon you picked to my nice icon.

I went crap, I have hundreds of these buttons on many forms. So I thought I will create a custom control called PickButton (which is a standard button and heap of default proeprties set) and drop these on the form everywhere instead. In the code of the PickButton custom control I set some properties and the image to the customers nice icon.

So I drop the PickButton from my toolbox onto the form, so far things are looking pretty good and I am feeling a bit clever. Now I think to myself I will change back to my nice icon not the crappy one the customer picked and change the code in the PickButton custom control. But I cannot get rid of that customers icon, because the code when the PickButton run happens before the code in the designer file which has the customers icon.

So my aim was to have a PickButton control and be able to change the icon and other properties in one place and all the properties would be set when an instance of the control is created and displayed on the form.

Was I not so clever and went about achieving the task the wrong way???

This is my PickButton custom control class

public class PickButton : Button
{

    public PickButton()
    {
        InitialiseButton();
    }

    internal void InitialiseButton()
    {
        this.ImageAlign = ContentAlignment.MiddleCenter;
        this.Image = WindowsFormsApplication1.Properties.Resources.Cancel.ToBitmap();
        this.Size = new Size( 28, 28 );
        this.Dock = DockStyle.Fill;
        this.Margin = new Padding( 0, 2, 2, 0 );
        this.Text = string.Empty;
    }
}

Now I drop one onto my form and the code in the designer is as follows

 // 
        // pickButton1
        // 
        this.pickButton1.Dock = System.Windows.Forms.DockStyle.Fill;
        this.pickButton1.Image = ((System.Drawing.Image)(resources.GetObject("pickButton1.Image")));
        this.pickButton1.Location = new System.Drawing.Point(0, 0);
        this.pickButton1.Margin = new System.Windows.Forms.Padding(0, 2, 2, 0);
        this.pickButton1.Name = "pickButton1";
        this.pickButton1.Size = new System.Drawing.Size(284, 262);
        this.pickButton1.TabIndex = 0;
        this.pickButton1.Text = "pickButton1";
        this.pickButton1.UseVisualStyleBackColor = true;

Now I want to change the image so I change my PickButton code to use a different icon

this.Image = WindowsFormsApplication1.Properties.Resources.Browse.ToBitmap();

Run the application andd the first icon is still the one being displayed because of this line of code in the designer file

        this.pickButton1.Image = ((System.Drawing.Image)(resources.GetObject("pickButton1.Image")));
trailerman
  • 321
  • 5
  • 15
  • 1
    I'm not entirely sure if I understand your explanation (posting code would probably be clearer), but why can't you just set the icon you want in the `PickButton` constructor? – Cody Gray - on strike Jul 26 '13 at 11:36
  • Without being able to see the code, I'd have to guess that you were too clever and exposed the icon as a property. So didn't solve the problem because now you've got hundreds of properties to set. Assign it in the constructor instead so all buttons use the same icon. – Hans Passant Jul 26 '13 at 12:40
  • @CodyGray I ahve edited the original question and added some code – trailerman Jul 26 '13 at 23:56
  • @HansPassant I have edit the original question and addded some code – trailerman Jul 27 '13 at 00:03
  • So you figured out that you'd better assign the Image property in the constructor. Too late, the form's Designer.cs file still has the Image property assignment. It is not going remove it just because you changed the constructor. Remove the button and add it back to fix. Or restore from source control. – Hans Passant Jul 27 '13 at 00:25
  • 1
    If you want to leave the customer icon assignment in the .Designer.cs file, try overriding the `OnLoad` method (or something similar, my memory is a bit fuzzy) and reassign the icon to your one there. – Sameer Singh Jul 27 '13 at 07:11
  • @SameerSingh The Button class does not have a OnLoad or Load event and neither do Controls (System.Windows.Forms.Control). UserControls have a load event though. – Scope Creep Oct 24 '13 at 13:25
  • @HansPassant Setting the image in the constructor will not do a thing to solve this problem. The designer will instantiate the instance (which calls the constructor) which will set the image if he does what you suggest. Then a few lines later, the designer code he posted will execute, which will overwrite the image you set it to in the constructor. – Scope Creep Oct 24 '13 at 14:04

2 Answers2

0

The concept of setting all the properties in one place was a good idea, it just wasn't implemented quite right. I would make this class inherit from UserControl instead of from Button. By making it a UserControl, you can use the designer to set all the properties you want, like the default Image for the button. Set that in the designer, then just drag and drop your UserControl from the toolbox onto your forms. If you are only using your "PickButton" control with comboboxes, I would put the combobox on the UserControl as well. If you ever want to change your button image in the future (or any other property for that matter), you will be able to change it in ctlPickButton and that will propogate the changes to all the instances used throughout your project(s).

ctlPickButton:

public partial class ctlPickButton : UserControl
{
    public event EventHandler pickButtonClicked;

    public ctlPickButton()
    {
        InitializeComponent();
    }

    //Allows buttons image to be set in code if necessary
    public Image Image
    {
        get
        {
            return button1.Image;
        }
        set
        {
            if (Image != null)
            {
                button1.Image = value;
            }
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        if (pickButtonClicked != null)
        {
            pickButtonClicked(sender, e);
        }
    }
}

Demo Form:

    public Form1()
    {
        InitializeComponent();

        ctlPickButton1.pickButtonClicked += new EventHandler(ctlPickButton1_pickButtonClicked);
        ctlPickButton2.pickButtonClicked += new EventHandler(ctlPickButton2_pickButtonClicked);
    }

    void ctlPickButton2_pickButtonClicked(object sender, EventArgs e)
    {
        if (comboBox2.SelectedItem != null)
        {
            MessageBox.Show(comboBox2.SelectedItem.ToString());
        }
    }

    void ctlPickButton1_pickButtonClicked(object sender, EventArgs e)
    {
        if (comboBox1.SelectedItem != null)
        {
            MessageBox.Show(comboBox1.SelectedItem.ToString());
        }
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        comboBox1.Items.Add("French");
        comboBox1.Items.Add("Spanish");
        comboBox1.Items.Add("English");
        comboBox1.Items.Add("German");

        comboBox2.Items.Add("Pizza");
        comboBox2.Items.Add("Hamburger");
        comboBox2.Items.Add("Potato");
        comboBox2.Items.Add("Chicken");

        //Shows how the default image set in the designer can be overwritten for a 
        //specific instance using the "Image" property
        ctlPickButton2.Image = Testbed.Properties.Resources.searchIcon2;
    }
}

Image of ctlPickButton in designer

enter image description here

Scope Creep
  • 547
  • 3
  • 15
  • @SameerSingh I took a look at it and I do not think it is a good approach to take. First reason is that your button cannot set itself up properly. Your solution would require calling customButton.Refresh(); for every single instance of the button every time it is used. Second, your solution does not allow for the Image to vary between individual instances since you have hardcoded the Image in the Refresh method. Third, setting properties is not what Refresh() is for, and since it doesn't call base.Refresh(), it will not cause the button to invalidate itself and be redrawn. – Scope Creep Oct 25 '13 at 13:38
  • That was a simple demo application that illustrated what you were trying to achieve. Don't want to call `Refresh ()` for every instance? Either call it in the constructor of `CustomButton` or change the image directly in its constructor, or change it in the designer (though, I thought the whole point was to override the "default" customer image). If you want the image to vary then you could set the property of each instance separately and not call `Refresh ()` or use another property and have `Refresh ()` use that. – Sameer Singh Oct 26 '13 at 05:03
  • Finally, I used `Refresh ()` because I only wanted it to be called once (other methods/events fired continuously which wasn't what I wanted) - as for invalidation, just add `base.Refresh ()` to the method. This is a quick mockup prototype application that is meant to be a starting point and is very much open to improvement and constructive criticism. – Sameer Singh Oct 26 '13 at 05:03
  • @SameerSingh At the end of the day your idea is not going to lead anyone, yourself included, down a good path. The buttons image is a property of the button and as such, according to OOP programming concepts, should be able to set its own image without REQUIRING external code. Also, as I already stated, your image setting mechanism results in every instance of the class being hardcoded to one image with no mechanism for changing it. Even if you did an if/else or switch in your refresh event to change the image, your code would still require constant modifications as more images get added. – Scope Creep Oct 28 '13 at 13:47
  • @SameerSingh I'm not trying to be a jerk, I just want you to realize your idea, while creative, is not good. Well written objects encapsulate their data and functionality and can maintain themselves. Yours does not. Using an event for something other than its intended purpose is not good. A completely configurable button could be a great start for a reusable control library (dll). But it will never become that reusable using your proposed solution. Again, I'm not ripping your idea to be a jerk, I honestly am just trying to show you why your line of thinking will bite you in the longrun. – Scope Creep Oct 28 '13 at 14:01
0

I think I've found a simple, clean solution:

In the CustomButton class (which inherits from System.Windows.Forms.Button), override the Refresh() method, and set the image of the button to the one you want to see:

public class CustomButton : Button
{
  public override void Refresh()
  {
    Image = MyResources.HappyFace;
  }
}

In the form that will hold an instance of your CustomButton, simply call customButton.Refresh() in the constructor, after InitializeComponent():

public partial class MainForm : Form
{
  public MainForm()
  {
    InitializeComponent();

    customButton.Refresh();
  }
}

I've put a demo application up on Github.

Sameer Singh
  • 1,358
  • 1
  • 19
  • 47