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")));
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
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.
Related
So I have built an application in C# using Winforms and my application uses a few different buttons. I'd like to have a highlight on the button that has been clicked to show what 'tab' you're in.
I've tried doing the following;
// BUTTONS //
private void dashboard_btn_Click(object sender, EventArgs e)
{
// Load Form
OpenChildForm(new FormDashboard());
dashboard_btn.FlatAppearance.BorderColor = Color.Red;
dashboard_btn.FlatAppearance.BorderSize = 1;
}
However, this of course doesn't work nicely since it adds a border around the button but when I click another button the border also stays around the previous button.
How would you implement a feature to add a border around the button that get's clicked but have the border disappear after you click another button?
Thank you for any feedback!
EDIT:
I've implemented Jimi's advice and used the Leave event to change the border around the button back to 0. However I'm not sure how to implement this in a global way so all my buttons are subscribed to this event.
My code now looks like this;
// BUTTONS //
private void dashboard_btn_Click(object sender, EventArgs e)
{
// Load Form
OpenChildForm(new FormDashboard());
// Button Highlight
dashboard_btn.FlatAppearance.BorderColor = Color.Red;
dashboard_btn.FlatAppearance.BorderSize = 1;
}
// BUTTON REMOVE HIGHLIGHT //
private void dashboard_btn_Leave(object sender, EventArgs e)
{
dashboard_btn.FlatAppearance.BorderSize = 0;
}
EDIT 2:
I ended up using Jimi's example and this worked for me :)
This might lend itself to a RadioButton style functionality because clicking a different radio button in the same container will uncheck the others. So, to implement the "generalized approach" that you mention in your comment, you could make a simple custom RadioButtonEx class where the Appearance property is set to Button then change your border style when the Checked property changes. In this example, the Click event has been changed to static so that clicking on any button directs the event to the common onAnyClick method.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
RadioButtonEx.Click += onAnyClick;
}
private void onAnyClick(object sender, EventArgs e)
{
label1.Text = ((RadioButtonEx)sender).Text;
}
}
public class RadioButtonEx : RadioButton
{
public static new event EventHandler Click;
public RadioButtonEx()
{
FlatAppearance.BorderColor = Color.Red;
FlatAppearance.BorderSize = 1;
Appearance = Appearance.Button;
}
protected override void OnCheckedChanged(EventArgs e)
{
base.OnCheckedChanged(e);
if(Checked)
{
FlatStyle = FlatStyle.Flat;
Click?.Invoke(this, EventArgs.Empty);
}
else
{
FlatStyle = FlatStyle.Standard;
}
}
}
I am using a custom control class to build similar-looking controls with different details and OnClick actions. Other than looking pretty, the controls only function is to toggle a yellow button so it looks like a modern togglebutton control (like in Android settings or whatever).
The main output looks like this:
What I'm trying to do is figure out how to bring the "onclick" logic out of the control and into the main file so I can properly respond to it. I originally had it respond to it's own click events on both the rectangle and ellipse like so:
public partial class FixerBox : UserControl
{
Thickness btnDotOn = new Thickness(-60, 0, 0, 0);
Thickness btnDotOff = new Thickness(0, 0, -60, 0);
SolidColorBrush dotOn = new SolidColorBrush(Color.FromRgb(84, 130, 53));
SolidColorBrush dotOff = new SolidColorBrush(Color.FromRgb(207, 178, 76));
SolidColorBrush bkOn = new SolidColorBrush(Color.FromRgb(226, 240, 217));
SolidColorBrush bkOff = new SolidColorBrush(Color.FromRgb(255, 242, 204));
public bool IsOn { get; set; } = false;
public FixerBox(Dictionary<string,string> deets)
{
InitializeComponent();
btnOff();
this.Name = deets["Name"];
this.FixerTitle.Text = deets["Title"];
this.FixerDesc.Text = deets["Description"];
this.FixerTags.Text = deets["Tags"];
this.FixerImg.Source = new BitmapImage(new Uri(deets["Img"], UriKind.Relative));
}
public void btnOn()
{
IsOn = true;
FixBtnBk.Fill = bkOn;
FixBtnBk.Stroke = dotOn;
FixBtnDot.Fill = dotOn;
FixBtnDot.Margin = btnDotOn;
}
public void btnOff()
{
IsOn = false;
FixBtnBk.Fill = bkOff;
FixBtnBk.Stroke = dotOff;
FixBtnDot.Fill = dotOff;
FixBtnDot.Margin = btnDotOff;
}
private void FixBtnBk_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//if (isOn)
//{
// btnOff();
//}
//else
//{
// btnOn();
//}
}
private void FixBtnDot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
FixBtnBk_MouseLeftButtonDown(sender, e);
}
}
I've commented out the click events for now so I can try to trigger the same effect from outside:
// The click event I'm trying to build
private void fixClick(object sender, EventArgs e)
{
StatusBox.Text = "CLICK WORKS";
FixerBox whichFix = (FixerBox)sender;
if (whichFix.IsOn)
{
whichFix.btnOff();
}
else
{
whichFix.btnOn();
}
}
// The constructor of my app
public MainWindow()
{
InitializeComponent();
// fixNames is a dictionary containing details of each control
var fixNames = fixers.FixerNames();
foreach (string key in fixNames)
{
var temp = fixers.GetFix(key);
// Be sure to pass this along as well
temp["Name"] = key;
fixerBoxes[key] = new FixerBox(temp);
fixerBoxes[key].FindName("FixBtnBk") = new EventHandler(fixClick);
FixersArea.Children.Add(fixerBoxes[key]);
}
}
Above is my main app constructor where I generate all the controls. The issue is that on the line where I'm setting the event handler, I can't figure out how to actually make it work. FindName should get the specific control, but it doesn't HAVE a click event or mousedown or anything. How can I attach my custom click event to each control?
The reason I'm doing it this way is because along with toggling the button, I want it to actually make system changes and registry updates in response to which control was clicked. I could keep all the responses in a class and include them in the custom control, but that seems sloppy both from a system resources viewpoint and a programming viewpoint.
Instead, if I can figure out how to access the various elements of the dynamic controls and add click events to them (or any equivalent workaround), that seems better.
how do I access elements of dynamically generated controls or otherwise add click events to them. What I'm trying to do is figure out how to bring the "onclick" logic out of the control and into the main file so I can properly respond to it.
As mentioned in my comments above, you could create public events that you could subscribe to and respond to those events as needed.
In your FixerBox class:
Add event: public event RoutedEventHandler MouseLeftBtnDown;
Invoke the event when needed:
private void FixerBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MouseLeftBtnDown?.Invoke(this, new RoutedEventArgs());
}
In your class you want to subscribe to this new event:
foreach (string key in fixNames)
{
var temp = fixers.GetFix(key);
// Be sure to pass this along as well
temp["Name"] = key;
fixerBoxes[key] = new FixerBox(temp);
fixerBoxes[key].MouseLeftBtnDown += fixClick;
FixersArea.Children.Add(fixerBoxes[key]);
}
Your routine signature would need to be changed as well:
private void fixClick(object sender, RoutedEventArgs e)
This should get you started, if you have questions please let me know.
I want to open form2 while form1 loads. Also, I want to change the Image of a PictureBox in form2 when an action is triggered in form1.
To open form2 while form1 loads I used this code:
private void MONITOR3_Load(object sender, EventArgs e)
{
MONITOR4 mo4 = new MONITOR4(this);
mo4.Show();
}
To change the Image of the PictureBox in form2 I used this code, which is must to be run after a condition is met:
if (textBox1.Text == #"QUEBEC - ALPHA - TANGO - ALPHA - ROMEO - ")
{
MONITOR4 mo4 = new MONITOR4(this);
mo4.pictureBox1.Image = Image.FromFile("D:/QResources/images/HIA.jpg");
}
There are two problems with your current code:
You don't have to create a new Form instance each time you need to set some of its properties: store a reference to this Form and use this reference to call public properties or methods of the Form.
You are trying to directly access a child Control's properties of another Form. Event though you can define a child Control public, you shouldn't and it's not necessary. A Form is a class like any other in this aspect: create a public method on the Form that provides means to modify a private property, without directly exposing a Control's property to direct access.
It's simple, safer and more portable: if a Control needs to be modified (the name is changed, the type of Control is changed etc.), you don't need to go on a hunt to find where the old name/properties have been used in other classes.
The public method will be the same and it's the only responsible to reference the current names, properties of the affected Control. A single place where the code, eventually, needs to be modified.
You could also use a public event or implement INotifyPropertyChanged to notify the subscribers that some properties have changed.
Here, I'm creating a reference to Monitor4 in the Monitor3 Form:
Monitor4 mo4 = null;
This reference will be use to call a public method (UpdatePictureBox) of Monitor4.
Monitor3 Form:
(I'm using the TextChanged event of a TextBox to select the Image to show in the Monitor4 PictureBox. Of course, it could be the Validate event or anything else that agrees with your design)
public partial class Monitor3 : Form
{
Monitor4 mo4 = null;
private void Monitor3_Load(object sender, EventArgs e)
{
mo4 = new Monitor4();
//Show Monitor4 on the right side of this Form
mo4.Location = new Point(this.Right + 10, this.Top);
mo4.Show(this);
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
string newText = textBox1.Text;
switch (newText) {
case "[Some text 1]":
mo4.UpdatePictureBox(#"[Images1 Path]");
break;
case "QUEBEC - ALPHA - TANGO - ALPHA - ROMEO - ":
mo4.UpdatePictureBox(#"[Images2 Path]");
break;
case "[Some text 3]":
mo4.UpdatePictureBox(#"[Images3 Path]");
break;
}
}
}
Monitor4 Form:
public partial class Monitor4 : Form
{
public void UpdatePictureBox(string imagePath)
{
if (File.Exists(imagePath)) {
pictureBox1.Image?.Dispose();
pictureBox1.Image = Image.FromFile(imagePath, true);
}
}
}
Sample result:
I have created a UserControl called Toggle, this is my code for it
[DefaultEvent("Click")]
public partial class Toggle : UserControl {
public bool ToggleStatus { get { return toggleStatus; } }
private bool toggleStatus { get; set; }
public Toggle() {
InitializeComponent();
toggleStatus = true;
}
private void toggleClick(object sender, EventArgs e) {
if (toggleStatus) { // currently set as "true" or "on"
this.lblSwitch.Dock = DockStyle.Right;
this.pnlBackground.BackColor = System.Drawing.Color.Red;
toggleStatus = false;
} else { // currently set as "false" or "off"
this.lblSwitch.Dock = DockStyle.Left;
this.pnlBackground.BackColor = System.Drawing.Color.Green;
toggleStatus = true;
}
}
}
The toggleClick method is tied to the click event of controls within the UserControl; this fires off just fine.
However, when I put my Toggle control on a form and attempt to tie an event to the click of it, it won't fire off.
private void toggleSoundClick(object sender, EventArgs e) {
soundToggle = !soundToggle;
}
I've made sure that the proper method is tied to the click event in my Designer.cs file of both my UserControl and my form
UserControl:
this.lblSwitch.Click += new System.EventHandler(this.toggleClick);
this.pnlBackground.Click += new System.EventHandler(this.toggleClick);
(I have it tied to two controls on my Toggle since I want it to fire no matter where you click on the control)
Form:
this.tglSound.Click += new System.EventHandler(this.toggleSoundClick);
The expected behavior for the UserControl is to fire off toggleClick (which it does) then the form should fire off toggleSoundClick (which it doesn't). I have seen this behavior work fine for other UserControls I have designed and used in this same project.
To clarify:
I have a UserControl called ServerDisplay. I have a method tied to the click event of the background panel of ServerDisplay (in the code for ServerDisplay) that shows a random MessageBox:
private void ServerDisplay_Click(object sender, EventArgs e) {
MessageBox.Show("test");
}
Then, I have a ServerDisplay control contained within my form. I have a method tied to the click event of it as well (in the code for my form)
private void serverDisplayClick(object sender, EventArgs e) {
if (loaded) {
ServerDisplay display = (ServerDisplay)sender;
this.lblLastServer.Text = "Last server joined was " + display.Server.Name + " at " + DateTime.Now.ToString("h:mm tt");
centerControl(this.lblLastServer);
}
}
When I click on the ServerDisplay control in my form, it shows the MessageBox (code from within ServerDisplay), then updates the label I specified in the code (code from form). This is the intended behavior, but it is not working for my other UserControl.
I finally figured it out! The way I had the control set up, I had the control itself, a panel filling up the entire background (I used this for the color), and then another panel inside the first panel to act as the "switch".
When I got rid of the first panel and just used the background of the control for the color and a small panel for the switch, it works when I click the background, but not when I click the "switch" panel. I guess this opens up more questions that I'll have to ask separately from this one, but at least I got my answer.
I notice all the other controls I am using such as tabs, textboxes, rich textboxes with text already inside it and everything else resize automatically when I change the main Form font. But labels are an exception to this rule is there a property I can turn on or off in the Label that will make it resize font with the form? It is a pretty straightforward yes or no question I know how to work around it I just like how all my controls are automatically resizing without any unnecessary code.
I've a solution here:
If your controls have the same font with your Form's Font, you shouldn't change the particular Font of them (via the Properties window or via code), instead you just change your Form's Font. If that has already been done, you can find the code lines (normally in Form.Designer.cs) which specify Font for your controls (something like yourControl.Font = ...) and remove those lines.
If your controls need different Font from your Form's. You can register the FontChanged event handler of your Form and change its children controls' Fonts accordingly (just the font size):
private void Form1_FontChanged(object sender, EventArgs e){
UpdateChildrenFont(this);
}
private void UpdateChildrenFont(Control parent){
foreach(Control c in parent.Controls){
c.Font = new Font(c.Font.FontFamily, parent.Font.Size, c.Font.Style);
UpdateChildrenFont(c);
}
}
The recursive method UpdateChildrenFont works in most cases but if you have some TabControl on your form, that doesn't work, because TabControl has another kind of collection to hold its tab pages called TabPages... I've encountered such a case. Another solution is to create your own Controls and override the OnParentChanged to register the Parent.FontChanged event handler accordingly, like this:
public class YourControl : TheBaseControl {
protected override void OnParentChanged(EventArgs e){
if(Parent != null){
Parent.FontChanged -= ParentFontChanged;
Parent.FontChanged += ParentFontChanged;
}
}
private void ParentFontChanged(object sender, EventArgs e){
Font = new Font(Font.FontFamily, Parent.Font.Size, Font.Style);
}
}
You should apply that model on all your controls on your form. That's very clean although requires you to have custom classes for your controls.
The last solution I can think of is playing with ControllAdded event, this is applied only on your Containers such as Form, GroupBox, Panel... and every control which has some child control on your form. Here is the code:
public class YourContainerClass: YourContainerBaseClass {
protected override void OnControlAdded(ControlEventArgs e){
Binding bind = new Binding("Font", this, "Font");
bind.Format += (s, ev) =>
{
Font inFont = (Font)ev.Value;
Binding bin = (Binding)s;
ev.Value = new Font(bin.Control.Font.FontFamily, inFont.Size, bin.Control.Font.Style);
};
e.Control.DataBindings.Clear();
e.Control.DataBindings.Add(bind);
base.OnControlAdded(e);
}
}
Or simply if that's your Form:
public class Form1 : Form {
public Form1(){
ControlAdded += FormControlAdded;
InitializeComponent();
}
private void FormControlAdded(object sender, ControlEventArgs e){
Binding bind = new Binding("Font", this, "Font");
bind.Format += (s, ev) =>
{
Font inFont = (Font)ev.Value;
Binding bin = (Binding)s;
ev.Value = new Font(bin.Control.Font.FontFamily, inFont.Size, bin.Control.Font.Style);
};
e.Control.DataBindings.Clear();
e.Control.DataBindings.Add(bind);
}
}