Need a better way to Iterate through Form Controls - c#

In my WinForms application written in C# there is a Button on one Form which needs to slightly alter the appearance of a second Form (just change the Text on a Button).
I have managed to do this, but the code is horribly long, and I believe there must be a much more concise way of achieving the same thing.
Here is my code for the Button on Form frmConflicts and how it changes the Text on the Button btnAddCase on Form frmAdmin (works, but seems too long) -
private void btnNoConflicts_Click(object sender, EventArgs e)
{
try
{
foreach (Form f in Application.OpenForms)
{
if (f.Name == "frmAdmin")
{
frmAdmin a = (frmAdmin)f;
a.conflictsClear = true;
foreach (Control ctrl in a.Controls)
{
if (ctrl.Name == "panAdmin")
{
foreach (Control ctrl2 in ctrl.Controls)
{
if (ctrl2.Name == "tabControlAdmin")
{
TabControl tab = (TabControl)ctrl2;
foreach(TabPage page in tab.TabPages)
{
if (page.Name == "pageNewCase")
{
foreach (Control ctrl3 in page.Controls)
{
if (ctrl3.Name == "panCaseDetails")
{
foreach (Control ctrl4 in ctrl3.Controls)
{
if (ctrl4.Name == "btnAddCase")
{
ctrl4.Text = "Add Case";
}
}
}
}
}
}
}
}
}
}
}
}
this.Close();
}
catch (Exception eX)
{
MessageBox.Show("frmConflicts: btnNoConflicts()" + Environment.NewLine + eX.Message);
}
Any help to significantly reduce the amount of code would be much appreciated, as I am going to need to do similar interactions between Forms elsewhere in my application.

If your button is added through designer and is not dynamically created the solution is simple: add a method inside your frmAdmin like
public void ChangeCaption(string caption)
{
btnAddCase.Text = "Add case";
}
and then
var frmAdmin = Application.OpenForms.OfType<Form>().FirstOrDefault(x => x.GetType() == typeof(frmAdmin));
if (frmAdmin != null)
{
frmAdmin.ChangeCaption("Add case");
}

I think it`s help to you
foreach (Form f in Application.OpenForms)
{
var controls =this.Controls.Find("btnAddCase", true);
if(controls!=null)
foreach(var control in controls)
{
control.Text="Add case";
}
}

If the the appearance of second from require a change on first from you should solve this in another way.
The best is that your button that require a change should be open for capture the event of form two open and then apply required change.
In the place where you declare your button you should assign to it a listener that will capture the Form2 opening and then apply action.
so in the method private void btnNoConflicts_Click(object sender, EventArgs e) you should trigger event for that button to capture instead off searching it.

You could use LINQ + ControlCollection.Find:
Control btnAddCase = Application.OpenForms.Cast<Form>()
.Where(f => f.Name == "frmAdmin")
.SelectMany(f => f.Controls.Find("btnAddCase", true)) // true means recursive
.FirstOrDefault();
if(btnAddCase != null)
btnAddCase.Text = "Add Case";

You could create a public property and subscribe to a PropertyChanged event from your form, you'll need your class that has the public variable to extend INotifyPropertyChanged.
//Your class
public class ButtonText : INotifyPropertyChanged
{
private string _buttonText;
public string ButtonValue
{
get{ return _buttonText; }
set
{
//Sets the value of _buttonText to the value passed in an argument
_buttonText = value;
RaisePropertyChanged("ButtonValue");
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In your form class you'd bind to the property ButtonValue property of the ButtonText class like so:
ButtonText buttonObj = new ButtonText();
//Property field to bind, object to bind, property to bind
btnAddCase.DataBindings.Add("Text", buttonObj,"ButtonValue");
buttonObj.ButtonText = "Your text to bind.";
Because the btnAddCase.Text property is bound to the ButtonValue property of the ButtonText class, your btnAddCase.Text property will reflect the value of your ButtonText.ButtonValue property at all times, it's also a two way binding.

Related

How to enable a button to when one of my textbox changed in C#?

Today I got a problem in my development.
I have a Windows Form like this :
I need to enable the button "Appliquer" when the content of one of my textbox change.
I know that I can put the KeyPress event on each textbox and enable my button with that. In this window it can be easy to do that because there is only 10 textbox but I have an other window with more of 100 textbox and I think there is a better solution.
I tried to put the Keydown event directly in my windows form but it doesn't work.
So my question is, how can I do this. If someone have an idea ?
Thank you in advance !
Thomas
Since you already have 100+ textboxes in your form. I am assuming performance is not an issue for you.
In your form constructor, call this method. It will attach the event to all the textbox controls present in your form & inside sub controls such as groupbox, panel etc. (if you require)
There could be better ways of iteration..
public Form1()//your constructor
{
InitializeComponent();
AttachEvent(this);
}
void AttachEvent(Control CTrl)
{
foreach (Control c in CTrl.Controls)
{
if (c is TextBox)
{
c.TextChanged += new EventHandler(c_TextChanged);
continue;
}
if (c.HasChildren)
{
AttachEvent(c);
}
}
}
void c_TextChanged(object sender, EventArgs e)
{
//Your Code here btnGo.Enabled = !btnGo.Enabled;
}
What you can do is to extend TextBox make a field ( accessible from the designer ) to bind that TextBox into some other control.
public class MeTextBox
: TextBox
{
public override string Text
{
get { return base.Text; }
set
{
if ( m_DependantControl != null )
{
m_DependantControl.Enabled = !string.IsNullOrWhiteSpace(value);
}
base.Text = value;
}
}
Control m_DependantControl;
[Browsable(true)]
public Control DependantControl
{
get { return m_DependantControl; }
set { m_DependantControl = value; }
}
}
Now you can use MeTextBox as a regular TextBox. And if you want to make it control Enabled flag of some other Control you can just specify DependantControl property which will be accessible in the designer.
Fitting this into your example (code):
// assume you have a Button named btnConfirm
// and want to enable this button only when your `TextBox` has some text
MeTextBox mtb = new MeTextBox();
mtb.DependantControl = btnConfirm;
And if you do not want to make it in the code you can use designer directly.
To make it other way around ( one button dependant on many text boxes ) you can extend Button object :
public class MeButton
: Button
{
List<TextBox> m_DependantOn = new List<Control>();
[Browsable(true)]
public List<TextBox> DependantOn
{
get { return m_DependantOn; }
set { RemoveEvents(); m_DependantOn = value; AssignEvents(); }
}
void RemoveEvents()
{
foreach(TextBox ctrl in m_DependantOn)
ctrl.TextChanged -= WhenTextChanged;
}
void AssignEvents()
{
foreach(TextBox.ctrl in m_DependantOn)
ctrl.TextChanged += WhenTextChanged;
}
void WhenTextChanged(object sender, TextChangedEventArgs e)
{
this.Enabled = true;
}
}

NumericUpDown differentiate up and down

I have a WinForm with some numreicUpDown Controls, i want to know if the value has been incremented or decremented. the control fires the event value changed for both situations, and as far as i can understand the programm calls the methods UpButton and DownButton. Is there any other way to know how the value has been changed or do i have to do this with this methods(like firing eventor implementig my code in Up-Down-Button)
There is no standart way to do this.
I sugest to remember the old value and compare it with new one
decimal oldValue;
private void ValueChanged(object sender, EventArgs e)
{
if (numericUpDown.Value > oldValue)
{
}
else
{
}
oldValue = numericUpDown.Value;
}
Create your own control that overrides those UpButton and DownButton methods:
using System.Windows.Forms;
public class EnhancedNUD : NumericUpDown
{
public event EventHandler BeforeUpButtoning;
public event EventHandler BeforeDownButtoning;
public event EventHandler AfterUpButtoning;
public event EventHandler AfterDownButtoning;
public override void UpButton()
{
if (BeforeUpButtoning != null) BeforeUpButtoning.Invoke(this, new EventArgs());
//Do what you want here...
//Or comment out the line below and do your own thing
base.UpButton();
if (AfterUpButtoning != null) AfterUpButtoning.Invoke(this, new EventArgs());
}
public override void DownButton()
{
if (BeforeDownButtoning != null) BeforeDownButtoning.Invoke(this, new EventArgs());
//Do what you want here...
//Or comment out the line below and do your own thing
base.DownButton();
if (AfterDownButtoning != null) AfterDownButtoning.Invoke(this, new EventArgs());
}
}
Then when you implement the control on your form, you can hook up some of the events to let you know which button was clicked or key (up/down) hit.

Form Validation based on both Combo boxes and TextBoxes

Currently I am using a class as follows to check if yhe TextBoxes on the form that I register to it, all have a non-blank text or not and it work fine, But now I want to also add a ComboBox to this validation so that validation should be done when none of the registered textboxes AND Combobxes on the form are blank.
So if I want to add a Combobx to this class, how should it look like? what is the best pracitce to do it?
public class InputValidator
{
public delegate void ValidationDoneDelegate(bool enable);
public event ValidationDoneDelegate ValidationDone;
public void RegisterTextBox(TextBox tb)
{
tb.TextChanged += (s, e) => this.Validate(s);
}
private void Validate(object sender)
{
var t = sender as TextBox;
if (t == null)
{
return;
}
var validationDone = ValidationDone;
if (validationDone != null)
{
validationDone(!string.IsNullOrEmpty(t.Text));
}
}
}
I have two lists setup which will hold all the TextBox and ComboBox references. When it is time to validate, we will check all of the registered controls and if ANY of them are empty, we will be invalid. I think you will also be able to see how this can easily be extended to support additional control types.
public class InputValidator
{
public delegate void ValidationDoneDelegate(bool enable);
public event ValidationDoneDelegate ValidationDone;
private List<TextBox> textBoxes = new List<TextBox>();
private List<ComboBox> comboBoxes = new List<ComboBox>();
public void RegisterTextBox(TextBox tb)
{
tb.TextChanged += (s, e) => this.Validate();
textBoxes.Add(tb);
}
public void RegisterComboBox(ComboBox cb)
{
cb.SelectedValueChanged += (s, e) => this.Validate();
comboBoxes.Add(cb);
}
private void Validate()
{
bool isValid = true;
foreach (var tb in textBoxes)
{
if (string.IsNullOrEmpty(tb.Text))
isValid = false;
}
if (isValid)
{
foreach (var cb in comboBoxes)
{
if (cb.SelectedItem == null)
isValid = false;
}
}
var validationDone = ValidationDone;
if (validationDone != null)
{
validationDone(isValid);
}
}
}
Now I'm not sure exactly what you consider to be invalid input for the ComboBox. So you may need to tweak this line to meet your needs: isValid = cb.SelectedItem != null;. I have assemed that as long as something is selected that the selection is valid.
EDIT: I had forgotten to switch the last line to validationDone(isValid);

Bind Data to Windows Form TabControl

I'm attempting my first Windows Form project, having been entirely web based previously and experiencing some issues. I want to bind a list of objects to a TabControl and have this create the Tabs and then have a databound value accessible from the click event of each tab.
The Object I'm wanting to bind is
public class TreeNodeItem
{
private NTree<string> node;
public TreeNodeItem(NTree<string> node)
{
this.node = node;
}
public string Value
{
get { return this.node.data; }
}
}
The NTree node represents a node in an object that models data in a tree structure. I want to create a tab for each object in the list with the Value property being bound to the Tab Text property. Other posts mention binding to the ItemsSource property of the control, but Visual Studio is not giving me this property.
Any help will be greatly appreciated.
Cheers
Stewart
Okay, I was unaware of that the binding was a must. Although I have never seen something like this being done in a Windows Forms Application, I've decided to create a class that does this for us.
It uses the ObservableCollection<T> to keep track whether an object / property has been changed inside its list.
public class ObservableList<T> : ObservableCollection<T>
{
public ObservableList() : base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(nObservableCollection_CollectionChanged);
}
public event PropertyChangedEventHandler OnPropertyChanged;
void nObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (OnPropertyChanged != null)
{
OnPropertyChanged(new object[] { e.OldItems, e.NewItems }, null); // Call method to let it change the tabpages
}
}
}
Now, we have to create a helper class that helps us keeping track:
public class TabControlBind
{
public TabControlBind(TabControl tabControl)
{
// Create a new TabPageCollection and bind it to our tabcontrol
this._tabPages = new TabControl.TabPageCollection(tabControl);
}
// Fields
private ObservableList<TreeNodeItem> _treeNodeItems;
private TabControl.TabPageCollection _tabPages;
// Properties
public ObservableList<TreeNodeItem> TreeNodeItems
{
get { return _treeNodeItems; }
set
{
if (_treeNodeItems != value)
{
_treeNodeItems = value;
_treeNodeItems.OnPropertyChanged += OnPropretyChanged;
OnPropretyChanged(null, null);
}
}
}
public TabControl.TabPageCollection TabPages
{
get
{
return this._tabPages;
}
}
// Events
private void OnPropretyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender == null) // If list got set
{
// Remove existing tabpages
this._tabPages.Clear();
// Loop through all items inside the ObservableList object and add them to the Tabpage
foreach (TreeNodeItem _treeNodeItem in this._treeNodeItems)
{
TabPage tabPage = new TabPage() { Text = _treeNodeItem.Value, Tag = _treeNodeItems };
this._tabPages.Add(tabPage);
}
}
else if (sender is object[]) // If only one (or multiple) objects have been changed
{
// Get OldItems and NewItems
object[] changedItems = (object[])sender;
// Remove OldItems
if (changedItems[0] != null)
{
foreach (dynamic oldItems in (IList)changedItems[0])
{
foreach (TabPage tab in this._tabPages)
{
if (tab.Text == oldItems.Value)
{
this._tabPages.Remove(tab);
break;
}
}
}
}
// Add OldItems
if (changedItems[1] != null)
{
foreach (dynamic newItems in (IList)changedItems[1])
{
TabPage tabPage = new TabPage() { Text = newItems.Value, Tag = newItems };
this._tabPages.Add(tabPage);
}
}
}
}
}
This is a sample on how to use it:
TabControlBind tabControlBinder;
ObservableList<TreeNodeItem> treeNodeItems;
private void btnAdd_Click(object sender, EventArgs e)
{
// This will automatically update the TabControl
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test3" }));
}
private void frmMain_Load(object sender, EventArgs e)
{
// Create a new list object an add items to it
treeNodeItems = new ObservableList<TreeNodeItem>();
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test" }));
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test2" }));
// Create a new instance of the TabControlBind class, set it to our TabControl
tabControlBinder = new TabControlBind(tabControl);
tabControlBinder.TreeNodeItems = treeNodeItems;
}

Programmatically click on a CheckBox

Is there a way to programmatically generate a click event on a CheckBox? I am looking for an equivalent to Button.PerformClick();
Why do you need to simulate a click, doesn't this line of code fits your need?
myCheckBox.Checked = !myCheckBox.Checked;
If you need to execute logic when the state of the CheckBox changes, you should use CheckedChanged event instead of Click.
private void CheckBox1_CheckedChanged(Object sender, EventArgs e)
{
MessageBox.Show("You are in the CheckBox.CheckedChanged event.");
}
Those solutions above calls Checkbox.CheckedChanged event.
If you want to explicitly call Click event you can this:
checkBox1_Click(checkBox1, null);
Why do you want to generate a click event on the CheckBox?
If you want to toggle it's value:
theCheckBox.Checked = !theCheckBox.Checked;
If you want to trigger some functionality that is connected to the Click event, it's a better idea to move the code out from the Click event handler into a separate method that can be called from anywhere:
private void theCheckBox_Click(object sender, EventArgs e)
{
HandleCheckBoxClick((CheckBox)sender);
}
private void HandleCheckBoxClick(CheckBox sender)
{
// do what is needed here
}
When you design your code like that, you can easily invoke the functionality from anywhere:
HandleCheckBoxClick(theCheckBox);
The same approach can (and perhaps should) be used for most control event handlers; move as much code as possible out from event handlers and into methods that are more reusable.
I'm still setting up a new workstation so I can't research this properly at the moment, but with UI Automation maybe it's possible that the checkbox supports the IInvokeProvider and you can use the Invoke method?
I don't think you can generate a click event in that way without calling the checkBox_Click event handler directly. But you can do this:
checkBox.Checked = !checkBox.Checked;
The CheckedChanged handler will still be called even if you do this.
The Button PerformClick() method validates the active control, testing whether the active control can lose the current focus. There are two ways to possibly do the same thing for a CheckBox. Approach #1 is to use reflection to call the methods that are internal to the Control class:
public class CheckBoxPerformClick : CheckBox {
private readonly static MethodInfo callValidateActiveControl;
private readonly static PropertyInfo propValidationCancelled;
static CheckBoxPerformClick() {
try {
Type ty = typeof(Control);
Type outBool = Type.GetType("System.Boolean&");
callValidateActiveControl = ty.GetMethod("ValidateActiveControl", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { outBool }, null);
propValidationCancelled = ty.GetProperty("ValidationCancelled", BindingFlags.Instance | BindingFlags.NonPublic);
} catch {}
}
public CheckBoxPerformClick() : base() {
this.Text = "Checkbox";
this.Appearance = Appearance.Button;
}
public void PerformClick() {
if (callValidateActiveControl != null && propValidationCancelled != null) {
try {
Object[] args = new Object[1];
bool validate = (bool) callValidateActiveControl.Invoke(this, args);
bool validatedControlAllowsFocusChange = (bool) args[0];
if (validate || validatedControlAllowsFocusChange) {
bool cancelled = (bool) propValidationCancelled.GetValue(this);
if (!cancelled) {
ResetFlagsandPaint();
OnClick(EventArgs.Empty);
}
}
} catch {
}
}
}
}
Approach #2 tries to do the same thing, but without reflection:
public class CheckBoxPerformClick2 : CheckBox {
public CheckBoxPerformClick2() : base() {
this.Text = "Checkbox";
this.Appearance = Appearance.Button;
}
public void PerformClick() {
bool validate = CanPerformClick();
if (validate) {
ResetFlagsandPaint();
OnClick(EventArgs.Empty);
}
}
// before allowing a click, make sure this control can receive the focus, and that other controls don't require validation
public bool CanPerformClick() {
if (!CanSelect)
return false;
Control c = this.Parent;
while (c != null) {
if (c is ContainerControl)
break;
c = c.Parent;
}
bool valid = true;
if (c is ContainerControl) {
var cc = (ContainerControl) c;
valid = cc.Validate(true);
}
return valid;
}
}

Categories

Resources